@quan-erp/shared-frontend-core 1.0.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 +92 -0
- package/dist/index.js +2921 -0
- package/dist/types/core/accelerometer.d.ts +47 -0
- package/dist/types/core/assets.d.ts +13 -0
- package/dist/types/core/backend-api.d.ts +2 -0
- package/dist/types/core/background-location.d.ts +1 -0
- package/dist/types/core/battery.d.ts +40 -0
- package/dist/types/core/bluetooth.d.ts +77 -0
- package/dist/types/core/camera.d.ts +83 -0
- package/dist/types/core/clipboard.d.ts +56 -0
- package/dist/types/core/database.d.ts +13 -0
- package/dist/types/core/device-class-tier.d.ts +19 -0
- package/dist/types/core/device-fingerprint.d.ts +29 -0
- package/dist/types/core/device.d.ts +33 -0
- package/dist/types/core/filesystem.d.ts +85 -0
- package/dist/types/core/firebase.d.ts +5 -0
- package/dist/types/core/gyroscope.d.ts +52 -0
- package/dist/types/core/index.d.ts +32 -0
- package/dist/types/core/location.d.ts +87 -0
- package/dist/types/core/microphone.d.ts +54 -0
- package/dist/types/core/morientation.d.ts +14 -0
- package/dist/types/core/motion-orientation.d.ts +25 -0
- package/dist/types/core/network.d.ts +15 -0
- package/dist/types/core/notification.d.ts +113 -0
- package/dist/types/core/number-formatter.d.ts +20 -0
- package/dist/types/core/orientation.d.ts +49 -0
- package/dist/types/core/permissions.d.ts +6 -0
- package/dist/types/core/platform.d.ts +27 -0
- package/dist/types/core/plugin-api.d.ts +33 -0
- package/dist/types/core/printer.d.ts +54 -0
- package/dist/types/core/safearea.d.ts +30 -0
- package/dist/types/core/shared-preferences.d.ts +61 -0
- package/dist/types/core/sqlite.d.ts +18 -0
- package/dist/types/core/system-locale.d.ts +24 -0
- package/dist/types/core/system.d.ts +109 -0
- package/dist/types/core/user-behavior.d.ts +24 -0
- package/dist/types/core/user-hand-behavior.d.ts +90 -0
- package/dist/types/core/vibration.d.ts +78 -0
- package/dist/types/dto/dto.d.ts +9 -0
- package/dist/types/dto/index.d.ts +2 -0
- package/dist/types/dto/pagination.dto.d.ts +29 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/security/websecurity.d.ts +4 -0
- package/dist/types/utilities/index.d.ts +1 -0
- package/dist/types/utilities/plugin-expose.d.ts +5 -0
- package/dist/types/utilities/version.util.d.ts +53 -0
- package/external-global-rollup-config.json +61 -0
- package/package.json +73 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2921 @@
|
|
|
1
|
+
import { Camera as Camera$1, CameraResultType, CameraSource } from '@capacitor/camera';
|
|
2
|
+
import { Device as Device$1 } from '@capacitor/device';
|
|
3
|
+
import { LocalNotifications } from '@capacitor/local-notifications';
|
|
4
|
+
import { Filesystem, Directory } from '@capacitor/filesystem';
|
|
5
|
+
import { SQLiteConnection, CapacitorSQLite } from '@capacitor-community/sqlite';
|
|
6
|
+
import { Geolocation } from '@capacitor/geolocation';
|
|
7
|
+
import { Network as Network$1 } from '@capacitor/network';
|
|
8
|
+
import { BleClient } from '@capacitor-community/bluetooth-le';
|
|
9
|
+
import { Clipboard as Clipboard$1 } from '@capacitor/clipboard';
|
|
10
|
+
import { Haptics, ImpactStyle, NotificationType } from '@capacitor/haptics';
|
|
11
|
+
import { WebHaptics } from 'web-haptics';
|
|
12
|
+
import { ScreenOrientation } from '@awesome-cordova-plugins/screen-orientation';
|
|
13
|
+
import FingerprintJS from '@fingerprintjs/fingerprintjs';
|
|
14
|
+
|
|
15
|
+
const browserOrignalNavigator = window.navigator;
|
|
16
|
+
const browserOriginalLocalStorage = window.localStorage;
|
|
17
|
+
// Freeze the original originalNavigator to prevent tampering
|
|
18
|
+
Object.defineProperty(window, 'navigator', {
|
|
19
|
+
get: () => {
|
|
20
|
+
console.warn('Access to originalNavigator is blocked. Use the provided API.');
|
|
21
|
+
return browserOrignalNavigator;
|
|
22
|
+
},
|
|
23
|
+
configurable: false,
|
|
24
|
+
enumerable: true,
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(window, 'localStorage', {
|
|
27
|
+
get: () => {
|
|
28
|
+
console.warn('Access to originalNavigator is blocked. Use the provided API.');
|
|
29
|
+
return browserOriginalLocalStorage;
|
|
30
|
+
},
|
|
31
|
+
configurable: false,
|
|
32
|
+
enumerable: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Utility service to identify the active application shell context (Desktop, Mobile, or Web).
|
|
37
|
+
* Used for dynamic feature detection and directing API requests to Capacitor, Electron, or browser-native APIs.
|
|
38
|
+
*/
|
|
39
|
+
class Platforms {
|
|
40
|
+
/**
|
|
41
|
+
* Determines if the application is running inside an Electron desktop window context.
|
|
42
|
+
* Checks if the `electron` IPC bridge object has been injected into the global `window` scope.
|
|
43
|
+
*
|
|
44
|
+
* @returns `true` if executed in an Electron shell environment, `false` otherwise.
|
|
45
|
+
*/
|
|
46
|
+
static isDesktop() {
|
|
47
|
+
return typeof window !== "undefined" && typeof window?.electron !== "undefined";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Determines if the application is running as a native mobile application (Android/iOS).
|
|
51
|
+
* Inspects the global Capacitor plugin bridge interface properties.
|
|
52
|
+
*
|
|
53
|
+
* @returns `true` if executed within a native mobile app container, `false` otherwise.
|
|
54
|
+
*/
|
|
55
|
+
static isMobile() {
|
|
56
|
+
const Capacitor = window.Capacitor;
|
|
57
|
+
return typeof Capacitor !== "undefined" && Capacitor.isNativePlatform();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Determines if the application is running in a traditional web browser environment.
|
|
61
|
+
* Checks that neither Electron desktop bindings nor Capacitor native app plugins are initialized.
|
|
62
|
+
*
|
|
63
|
+
* @returns `true` if running strictly on a standard web page, `false` otherwise.
|
|
64
|
+
*/
|
|
65
|
+
static isWeb() {
|
|
66
|
+
return !this.isDesktop() && !this.isMobile();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class CapacitorCamera {
|
|
71
|
+
videoEl;
|
|
72
|
+
stream;
|
|
73
|
+
async requestPermission() {
|
|
74
|
+
try {
|
|
75
|
+
const status = await Camera$1.checkPermissions();
|
|
76
|
+
if (status.camera === 'granted')
|
|
77
|
+
return true;
|
|
78
|
+
// request permission if not granted
|
|
79
|
+
const request = await Camera$1.requestPermissions({ permissions: ['camera'] });
|
|
80
|
+
return request.camera === 'granted';
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.warn('Camera permission denied:', err);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async takePicture() {
|
|
88
|
+
const result = await Camera$1.getPhoto({
|
|
89
|
+
quality: 80,
|
|
90
|
+
resultType: CameraResultType.DataUrl
|
|
91
|
+
});
|
|
92
|
+
return result.dataUrl;
|
|
93
|
+
}
|
|
94
|
+
async startPreview(videoElement) {
|
|
95
|
+
this.videoEl = videoElement;
|
|
96
|
+
console.log(this.videoEl);
|
|
97
|
+
console.log(browserOrignalNavigator);
|
|
98
|
+
const info = await Device$1.getInfo();
|
|
99
|
+
console.log('WebView version:', info.webViewVersion);
|
|
100
|
+
console.log('medias', browserOrignalNavigator.mediaDevices);
|
|
101
|
+
try {
|
|
102
|
+
this.stream = await browserOrignalNavigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } });
|
|
103
|
+
this.videoEl.srcObject = this.stream;
|
|
104
|
+
await this.videoEl.play();
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error("Failed to start camera preview:", err);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
stopPreview() {
|
|
111
|
+
if (this.stream) {
|
|
112
|
+
this.stream.getTracks().forEach(track => track.stop());
|
|
113
|
+
this.stream = undefined;
|
|
114
|
+
}
|
|
115
|
+
if (this.videoEl) {
|
|
116
|
+
this.videoEl.pause();
|
|
117
|
+
this.videoEl.srcObject = null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async pickImageFromGallery({ resultType = CameraResultType.Uri }) {
|
|
121
|
+
const result = await Camera$1.getPhoto({
|
|
122
|
+
quality: 80,
|
|
123
|
+
resultType: resultType, // or Uri
|
|
124
|
+
source: CameraSource.Photos // <-- gallery
|
|
125
|
+
});
|
|
126
|
+
return result.dataUrl;
|
|
127
|
+
}
|
|
128
|
+
pickVideoFromGallery() {
|
|
129
|
+
// TODO: ADD SUPPORT FOR pick from gallery
|
|
130
|
+
throw new Error("no support yet");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
class WebCamera {
|
|
134
|
+
stream;
|
|
135
|
+
videoEl;
|
|
136
|
+
async takePicture() {
|
|
137
|
+
return new Promise(async (resolve, reject) => {
|
|
138
|
+
try {
|
|
139
|
+
const stream = await browserOrignalNavigator.mediaDevices.getUserMedia({ video: true });
|
|
140
|
+
const track = stream.getVideoTracks()[0];
|
|
141
|
+
const imageCapture = new ImageCapture(track);
|
|
142
|
+
const blob = await imageCapture.takePhoto();
|
|
143
|
+
const reader = new FileReader();
|
|
144
|
+
reader.onloadend = () => resolve(reader.result);
|
|
145
|
+
reader.readAsDataURL(blob);
|
|
146
|
+
track.stop();
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
reject(err);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async startPreview(videoElement) {
|
|
154
|
+
this.videoEl = videoElement;
|
|
155
|
+
this.stream = await browserOrignalNavigator.mediaDevices.getUserMedia({ video: true });
|
|
156
|
+
videoElement.srcObject = this.stream;
|
|
157
|
+
await videoElement.play();
|
|
158
|
+
}
|
|
159
|
+
stopPreview() {
|
|
160
|
+
if (this.stream) {
|
|
161
|
+
this.stream.getTracks().forEach(track => track.stop());
|
|
162
|
+
this.stream = undefined;
|
|
163
|
+
}
|
|
164
|
+
if (this.videoEl) {
|
|
165
|
+
this.videoEl.pause();
|
|
166
|
+
this.videoEl.srcObject = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async requestPermission() {
|
|
170
|
+
try {
|
|
171
|
+
// This must be triggered by a user gesture (click)
|
|
172
|
+
const stream = await browserOrignalNavigator.mediaDevices.getUserMedia({ video: true });
|
|
173
|
+
// immediately stop the stream; we just wanted permission
|
|
174
|
+
stream.getTracks().forEach(track => track.stop());
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
console.warn("Camera permission denied:", err);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
pickImageFromGallery({ resultType }) {
|
|
183
|
+
// TODO: ADD SUPPORT FOR pick from gallery
|
|
184
|
+
throw new Error("no support yet");
|
|
185
|
+
}
|
|
186
|
+
pickVideoFromGallery() {
|
|
187
|
+
// TODO: ADD SUPPORT FOR pick from gallery
|
|
188
|
+
throw new Error("no support yet");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
class ElectronCamera extends WebCamera {
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Cross-platform Camera tracking utility that delegates permission requests, image capturing,
|
|
195
|
+
* live video element previews, and photo/video gallery selection to Capacitor Camera,
|
|
196
|
+
* Electron, or HTML5 MediaStream APIs depending on the active platform.
|
|
197
|
+
*/
|
|
198
|
+
class Camera {
|
|
199
|
+
static _instance;
|
|
200
|
+
impl;
|
|
201
|
+
// Private constructor to prevent direct instantiation
|
|
202
|
+
constructor() {
|
|
203
|
+
if (Platforms.isMobile()) {
|
|
204
|
+
this.impl = new CapacitorCamera();
|
|
205
|
+
}
|
|
206
|
+
else if (Platforms.isDesktop()) {
|
|
207
|
+
this.impl = new ElectronCamera();
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
this.impl = new WebCamera();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Singleton instance of the Camera utility.
|
|
215
|
+
*/
|
|
216
|
+
static get instance() {
|
|
217
|
+
if (!this._instance) {
|
|
218
|
+
this._instance = new Camera();
|
|
219
|
+
}
|
|
220
|
+
return this._instance;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Checks and requests permission to access the camera hardware.
|
|
224
|
+
* @returns A promise resolving to true if access was successfully granted.
|
|
225
|
+
*/
|
|
226
|
+
requestPermission() {
|
|
227
|
+
return this.impl.requestPermission();
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Captures a single image using the device camera.
|
|
231
|
+
* @returns A promise resolving to the picture data encoded as a Base64 string or file URL.
|
|
232
|
+
*/
|
|
233
|
+
takePicture() {
|
|
234
|
+
return this.impl.takePicture();
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Binds a real-time camera stream to a given HTMLVideoElement for a custom camera preview layout.
|
|
238
|
+
* @param videoElement The HTML5 Video element to bind the streaming media tracks to.
|
|
239
|
+
*/
|
|
240
|
+
startPreview(videoElement) {
|
|
241
|
+
return this.impl.startPreview(videoElement);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Disconnects and releases the active camera streams, stopping the custom preview window.
|
|
245
|
+
*/
|
|
246
|
+
stopPreview() {
|
|
247
|
+
this.impl.stopPreview();
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Triggers the platform's image picker to select a photo from the local photo gallery.
|
|
251
|
+
* @param options Target result layout configurations.
|
|
252
|
+
* @returns A promise resolving to the chosen image's Base64 data or URI path.
|
|
253
|
+
*/
|
|
254
|
+
pickImageFromGallery(options) {
|
|
255
|
+
return this.impl.pickImageFromGallery(options);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Triggers the platform's video picker to select a video file from the local library.
|
|
259
|
+
* @returns A promise resolving to the chosen video's file path or URL.
|
|
260
|
+
*/
|
|
261
|
+
pickVideoFromGallery() {
|
|
262
|
+
return this.impl.pickVideoFromGallery();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// notifications.ts
|
|
267
|
+
/**
|
|
268
|
+
* Capacitor-specific implementation of INotifications for mobile platforms (Android/iOS).
|
|
269
|
+
* Uses `@capacitor/local-notifications` to schedule push-like local system notifications.
|
|
270
|
+
*/
|
|
271
|
+
class CapacitorNotifications {
|
|
272
|
+
permissionGranted = false;
|
|
273
|
+
channelCreated = false;
|
|
274
|
+
/**
|
|
275
|
+
* Requests notification permissions from the Capacitor local notification manager.
|
|
276
|
+
* @returns A promise resolving to `true` if display permissions are granted, `false` otherwise.
|
|
277
|
+
*/
|
|
278
|
+
async requestPermission() {
|
|
279
|
+
try {
|
|
280
|
+
const status = await LocalNotifications.requestPermissions();
|
|
281
|
+
this.permissionGranted = status.display === 'granted';
|
|
282
|
+
return this.permissionGranted;
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
console.error('Capacitor local notification permission error:', err);
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Sends a local notification on mobile platforms.
|
|
291
|
+
* Automatically registers a default notification channel if not already created.
|
|
292
|
+
* @param title The notification title.
|
|
293
|
+
* @param options Additional options containing body string and custom channel ID.
|
|
294
|
+
*/
|
|
295
|
+
async sendNotification(title, options) {
|
|
296
|
+
if (!this.permissionGranted) {
|
|
297
|
+
console.warn('Notifications permission not granted');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (!this.channelCreated) {
|
|
301
|
+
console.log('create notification channel');
|
|
302
|
+
await LocalNotifications.createChannel({
|
|
303
|
+
id: 'default',
|
|
304
|
+
name: 'Default Channel',
|
|
305
|
+
importance: 5, // max importance
|
|
306
|
+
sound: 'default',
|
|
307
|
+
visibility: 1, // public
|
|
308
|
+
});
|
|
309
|
+
this.channelCreated = true;
|
|
310
|
+
}
|
|
311
|
+
console.log('scheduled');
|
|
312
|
+
await LocalNotifications.schedule({
|
|
313
|
+
notifications: [
|
|
314
|
+
{
|
|
315
|
+
title,
|
|
316
|
+
body: options?.body || '',
|
|
317
|
+
id: options?.id ?? (new Date().getTime() % 2147483647), // unique ID
|
|
318
|
+
schedule: { at: new Date(Date.now() + 1000) }, // slight delay to show immediately
|
|
319
|
+
// sound: 'default',
|
|
320
|
+
smallIcon: 'ic_stat_icon',
|
|
321
|
+
channelId: 'default',
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
});
|
|
325
|
+
console.log('permission pushed');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Electron-specific notification implementation for desktop environments.
|
|
330
|
+
* Delegates to the default system-level Chromium Notification API.
|
|
331
|
+
*/
|
|
332
|
+
class ElectronNotifications {
|
|
333
|
+
/**
|
|
334
|
+
* Requests notification permission. Electron apps usually have notification rights by default.
|
|
335
|
+
* @returns A promise resolving to `true`.
|
|
336
|
+
*/
|
|
337
|
+
async requestPermission() {
|
|
338
|
+
return true; // Electron notifications are usually allowed by default
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Triggers a desktop notification using Chromium's HTML5 Notification API.
|
|
342
|
+
* @param title The title of the desktop notification.
|
|
343
|
+
* @param options Options containing the body of the notification.
|
|
344
|
+
*/
|
|
345
|
+
async sendNotification(title, options) {
|
|
346
|
+
try {
|
|
347
|
+
new Notification(title, { body: options?.body });
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
console.error('Electron notification error:', err);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Standard browser/web notification implementation.
|
|
356
|
+
* Wraps browser-native HTML5 Web Notification API.
|
|
357
|
+
*/
|
|
358
|
+
class WebNotifications {
|
|
359
|
+
/**
|
|
360
|
+
* Requests permission using the standard browser Web Notification API.
|
|
361
|
+
* @returns A promise resolving to `true` if permission is granted, `false` otherwise.
|
|
362
|
+
*/
|
|
363
|
+
async requestPermission() {
|
|
364
|
+
try {
|
|
365
|
+
const result = await Notification.requestPermission();
|
|
366
|
+
return result === 'granted';
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
console.error('Web notification permission error:', err);
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Triggers a standard browser notification when permitted.
|
|
375
|
+
* @param title The notification title.
|
|
376
|
+
* @param options Options containing the body of the notification.
|
|
377
|
+
*/
|
|
378
|
+
async sendNotification(title, options) {
|
|
379
|
+
try {
|
|
380
|
+
if ('serviceWorker' in navigator) {
|
|
381
|
+
const registration = await navigator.serviceWorker.getRegistration();
|
|
382
|
+
if (registration) {
|
|
383
|
+
await registration.showNotification(title, { body: options?.body });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
new Notification(title, { body: options?.body });
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
console.error('Web notification error:', err);
|
|
391
|
+
try {
|
|
392
|
+
new Notification(title, { body: options?.body });
|
|
393
|
+
}
|
|
394
|
+
catch (fallbackErr) {
|
|
395
|
+
console.error('Fallback web notification error:', fallbackErr);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Singleton wrapper and router for cross-platform local notification features.
|
|
402
|
+
* Automatically delegates calls to the appropriate platform-specific implementation.
|
|
403
|
+
*/
|
|
404
|
+
class Notifications {
|
|
405
|
+
static _instance;
|
|
406
|
+
impl;
|
|
407
|
+
/**
|
|
408
|
+
* Private constructor to enforce singleton usage pattern.
|
|
409
|
+
* Selects and instantiates the underlying notification engine based on platform type.
|
|
410
|
+
*/
|
|
411
|
+
constructor() {
|
|
412
|
+
if (Platforms.isMobile()) {
|
|
413
|
+
this.impl = new CapacitorNotifications();
|
|
414
|
+
}
|
|
415
|
+
else if (Platforms.isDesktop()) {
|
|
416
|
+
this.impl = new ElectronNotifications();
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
this.impl = new WebNotifications();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Gets the current active global singleton instance of `Notifications`.
|
|
424
|
+
*/
|
|
425
|
+
static get instance() {
|
|
426
|
+
if (!this._instance) {
|
|
427
|
+
this._instance = new Notifications();
|
|
428
|
+
}
|
|
429
|
+
return this._instance;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Requests authorization/permission from the user to display notifications.
|
|
433
|
+
* Delegates to the active platform implementation.
|
|
434
|
+
* @returns A promise resolving to `true` if permission was granted, `false` otherwise.
|
|
435
|
+
*/
|
|
436
|
+
requestPermission() {
|
|
437
|
+
return this.impl.requestPermission();
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Triggers or schedules a local notification.
|
|
441
|
+
* Delegates to the active platform implementation.
|
|
442
|
+
* @param title The title text of the notification.
|
|
443
|
+
* @param options Configuration options including message body and unique ID.
|
|
444
|
+
*/
|
|
445
|
+
sendNotification(title, options) {
|
|
446
|
+
return this.impl.sendNotification(title, options);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
class WebFileSystem {
|
|
451
|
+
async requestPermission() {
|
|
452
|
+
console.log('WebFileSystem: requestPermission');
|
|
453
|
+
}
|
|
454
|
+
async write(path, data) {
|
|
455
|
+
localStorage.setItem(path, data);
|
|
456
|
+
}
|
|
457
|
+
async read(path) {
|
|
458
|
+
const data = localStorage.getItem(path);
|
|
459
|
+
if (!data)
|
|
460
|
+
throw new Error("File not found");
|
|
461
|
+
return data;
|
|
462
|
+
}
|
|
463
|
+
async delete(path) {
|
|
464
|
+
localStorage.removeItem(path);
|
|
465
|
+
}
|
|
466
|
+
async exists(path) {
|
|
467
|
+
return localStorage.getItem(path) !== null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
class CapacitorFileSystem {
|
|
471
|
+
async requestPermission() {
|
|
472
|
+
const status = await Filesystem.requestPermissions();
|
|
473
|
+
if (status.publicStorage === "granted") {
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
async write(options) {
|
|
479
|
+
await Filesystem.writeFile(options);
|
|
480
|
+
}
|
|
481
|
+
async read(options) {
|
|
482
|
+
const result = await Filesystem.readFile(options);
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
async delete(path) {
|
|
486
|
+
await Filesystem.deleteFile({
|
|
487
|
+
path,
|
|
488
|
+
directory: Directory.Data,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
async exists(path) {
|
|
492
|
+
try {
|
|
493
|
+
await Filesystem.stat({
|
|
494
|
+
path,
|
|
495
|
+
directory: Directory.Data,
|
|
496
|
+
});
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async getUri(options) {
|
|
504
|
+
return await Filesystem.getUri(options);
|
|
505
|
+
}
|
|
506
|
+
async stat(option) {
|
|
507
|
+
return await Filesystem.stat(option);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
class ElectronFileSystem {
|
|
511
|
+
async requestPermission() {
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
async write(path, data) {
|
|
515
|
+
window.FileSystem.writeFile(path, data);
|
|
516
|
+
}
|
|
517
|
+
async read(path) {
|
|
518
|
+
return window.FileSystem.readFile(path);
|
|
519
|
+
}
|
|
520
|
+
async delete(path) {
|
|
521
|
+
window.FileSystem.deleteFile(path);
|
|
522
|
+
}
|
|
523
|
+
async exists(path) {
|
|
524
|
+
return window.FileSystem.fileExists(path);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Cross-platform FileSystem utility that delegates persistent local read/write file transactions
|
|
529
|
+
* to native Capacitor plugins, Electron preload APIs, or falling back to Web localStorage.
|
|
530
|
+
*/
|
|
531
|
+
class FileSystem {
|
|
532
|
+
impl;
|
|
533
|
+
static _instance;
|
|
534
|
+
/**
|
|
535
|
+
* Singleton instance of the FileSystem utility.
|
|
536
|
+
*/
|
|
537
|
+
static get instance() {
|
|
538
|
+
if (!this._instance) {
|
|
539
|
+
// Capacitor native
|
|
540
|
+
if (Platforms.isMobile()) {
|
|
541
|
+
this._instance = new CapacitorFileSystem();
|
|
542
|
+
return this._instance;
|
|
543
|
+
}
|
|
544
|
+
// Electron preload
|
|
545
|
+
if (Platforms.isDesktop()) {
|
|
546
|
+
this._instance = new ElectronFileSystem();
|
|
547
|
+
return this._instance;
|
|
548
|
+
}
|
|
549
|
+
// Web fallback
|
|
550
|
+
this._instance = new WebFileSystem();
|
|
551
|
+
}
|
|
552
|
+
return this._instance;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Requests native filesystem permissions on the current device.
|
|
556
|
+
* @returns A promise resolving to true if permissions were granted.
|
|
557
|
+
*/
|
|
558
|
+
requestPermission() {
|
|
559
|
+
return this.impl.requestPermission();
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Writes text or data to a specific file.
|
|
563
|
+
* @param path Target relative path of the file.
|
|
564
|
+
* @param data Raw content or options to write.
|
|
565
|
+
*/
|
|
566
|
+
write(path, data) {
|
|
567
|
+
return this.impl.write(path, data);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Reads text or data from a specific file.
|
|
571
|
+
* @param path Target relative path of the file.
|
|
572
|
+
* @returns A promise resolving to the read contents.
|
|
573
|
+
*/
|
|
574
|
+
read(path) {
|
|
575
|
+
return this.impl.read(path);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Deletes a file from the device storage.
|
|
579
|
+
* @param path Target relative path to delete.
|
|
580
|
+
*/
|
|
581
|
+
delete(path) {
|
|
582
|
+
return this.impl.delete(path);
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Verifies if a file exists at the specified path.
|
|
586
|
+
* @param path Target relative path to test.
|
|
587
|
+
* @returns A promise resolving to true if the file exists, false otherwise.
|
|
588
|
+
*/
|
|
589
|
+
exists(path) {
|
|
590
|
+
return this.impl.exists(path);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// /**
|
|
595
|
+
// * Retrieves a connection to the local SQLite database. Initializes schema tables if they do not exist.
|
|
596
|
+
// * *Note: Supported only on mobile and desktop platforms.*
|
|
597
|
+
// * @returns A promise resolving to the SQLiteDBConnection instance.
|
|
598
|
+
// */
|
|
599
|
+
// export async function getDB(): Promise<SQLiteDBConnection> {
|
|
600
|
+
// if (Platforms.isWeb()) throw new Error("Database is not supported in Web")
|
|
601
|
+
// if (db === null) {
|
|
602
|
+
// const sqlite = new SQLiteConnection(CapacitorSQLite);
|
|
603
|
+
// // Sync JS connection state with Native after a webview reload
|
|
604
|
+
// try {
|
|
605
|
+
// await sqlite.checkConnectionsConsistency();
|
|
606
|
+
// } catch (e) {
|
|
607
|
+
// console.error("Failed to check connection consistency", e);
|
|
608
|
+
// }
|
|
609
|
+
// // Handle webview hard reloads: check if native connection already exists
|
|
610
|
+
// const isConn = (await sqlite.isConnection('quan-erp.db', false)).result;
|
|
611
|
+
// if (isConn) {
|
|
612
|
+
// db = await sqlite.retrieveConnection('quan-erp.db', false);
|
|
613
|
+
// } else {
|
|
614
|
+
// db = await sqlite.createConnection('quan-erp.db', false, 'no-encryption', 1, false);
|
|
615
|
+
// }
|
|
616
|
+
// const isOpen = (await db.isDBOpen()).result;
|
|
617
|
+
// if (!isOpen) {
|
|
618
|
+
// await db.open();
|
|
619
|
+
// }
|
|
620
|
+
// await db.execute(`
|
|
621
|
+
// CREATE TABLE IF NOT EXISTS plugins (
|
|
622
|
+
// id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
623
|
+
// name TEXT NOT NULL,
|
|
624
|
+
// version TEXT NOT NULL
|
|
625
|
+
// )
|
|
626
|
+
// `)
|
|
627
|
+
// return db;
|
|
628
|
+
// }
|
|
629
|
+
// return db;
|
|
630
|
+
// }
|
|
631
|
+
/**
|
|
632
|
+
* Mobile-specific local SQLite database helper.
|
|
633
|
+
*/
|
|
634
|
+
class Database {
|
|
635
|
+
static _instance = null;
|
|
636
|
+
constructor() { }
|
|
637
|
+
/**
|
|
638
|
+
* Retrieves the singleton mobile SQLite database connection.
|
|
639
|
+
* @returns A promise resolving to the database connection instance.
|
|
640
|
+
*/
|
|
641
|
+
static async getInstance() {
|
|
642
|
+
if (!Platforms.isMobile())
|
|
643
|
+
throw new Error("SQLite is supported only on mobile");
|
|
644
|
+
if (!Database._instance) {
|
|
645
|
+
const sqlite = new SQLiteConnection(CapacitorSQLite);
|
|
646
|
+
try {
|
|
647
|
+
await sqlite.checkConnectionsConsistency();
|
|
648
|
+
}
|
|
649
|
+
catch (e) {
|
|
650
|
+
console.error("Failed to check connection consistency", e);
|
|
651
|
+
}
|
|
652
|
+
const isConn = (await sqlite.isConnection('quan-erp.db', false)).result;
|
|
653
|
+
if (isConn) {
|
|
654
|
+
Database._instance = await sqlite.retrieveConnection('quan-erp.db', false);
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
Database._instance = await sqlite.createConnection('quan-erp.db', false, 'no-encryption', 1, false);
|
|
658
|
+
}
|
|
659
|
+
const isOpen = (await Database._instance.isDBOpen()).result;
|
|
660
|
+
if (!isOpen) {
|
|
661
|
+
await Database._instance.open();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return Database._instance;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Web implementation
|
|
669
|
+
class WebLocation {
|
|
670
|
+
watchers = new Map();
|
|
671
|
+
nextId = 1;
|
|
672
|
+
async requestPermission() {
|
|
673
|
+
if (!browserOrignalNavigator.geolocation)
|
|
674
|
+
return false;
|
|
675
|
+
try {
|
|
676
|
+
// Prompt permission
|
|
677
|
+
await new Promise((resolve, reject) => {
|
|
678
|
+
browserOrignalNavigator.geolocation.getCurrentPosition(resolve, reject);
|
|
679
|
+
});
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
catch {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async getCurrentPosition() {
|
|
687
|
+
return new Promise((resolve, reject) => {
|
|
688
|
+
browserOrignalNavigator.geolocation.getCurrentPosition((pos) => resolve({ latitude: pos.coords.latitude, longitude: pos.coords.longitude }), reject);
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
watchPosition(callback) {
|
|
692
|
+
const id = this.nextId++;
|
|
693
|
+
const watchId = browserOrignalNavigator.geolocation.watchPosition((pos) => callback({ latitude: pos.coords.latitude, longitude: pos.coords.longitude }));
|
|
694
|
+
this.watchers.set(id, watchId);
|
|
695
|
+
return Promise.resolve(String(id));
|
|
696
|
+
}
|
|
697
|
+
clearWatch(watchId) {
|
|
698
|
+
const id = this.watchers.get(Number(watchId));
|
|
699
|
+
if (id != null)
|
|
700
|
+
browserOrignalNavigator.geolocation.clearWatch(id);
|
|
701
|
+
this.watchers.delete(Number(watchId));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
// Capacitor implementation
|
|
705
|
+
class CapacitorLocation extends WebLocation {
|
|
706
|
+
async requestPermission() {
|
|
707
|
+
try {
|
|
708
|
+
const perm = await Geolocation.requestPermissions();
|
|
709
|
+
return perm.location === 'granted';
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
async getCurrentPosition() {
|
|
716
|
+
const pos = await Geolocation.getCurrentPosition();
|
|
717
|
+
return { latitude: pos.coords.latitude, longitude: pos.coords.longitude };
|
|
718
|
+
}
|
|
719
|
+
async watchPosition(callback) {
|
|
720
|
+
const watchId = await Geolocation.watchPosition({}, (pos) => {
|
|
721
|
+
callback({ latitude: pos.coords.latitude, longitude: pos.coords.longitude });
|
|
722
|
+
});
|
|
723
|
+
return watchId;
|
|
724
|
+
}
|
|
725
|
+
clearWatch(watchId) {
|
|
726
|
+
Geolocation.clearWatch({ id: watchId });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
// Electron implementation (can fallback to Web API)
|
|
730
|
+
class ElectronLocation extends WebLocation {
|
|
731
|
+
}
|
|
732
|
+
// Singleton cross-platform wrapper
|
|
733
|
+
/**
|
|
734
|
+
* Cross-platform Geolocation utility that delegates location tracking and watch requests
|
|
735
|
+
* to native Capacitor plugins, Electron, or Web Geolocation APIs depending on the runtime environment.
|
|
736
|
+
*/
|
|
737
|
+
class Location {
|
|
738
|
+
static _instance;
|
|
739
|
+
impl;
|
|
740
|
+
// Private constructor
|
|
741
|
+
constructor() {
|
|
742
|
+
if (Platforms.isMobile()) {
|
|
743
|
+
this.impl = new CapacitorLocation();
|
|
744
|
+
}
|
|
745
|
+
else if (Platforms.isDesktop()) {
|
|
746
|
+
this.impl = new ElectronLocation();
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
this.impl = new WebLocation();
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Singleton instance of the Location utility.
|
|
754
|
+
*/
|
|
755
|
+
static get instance() {
|
|
756
|
+
if (!this._instance) {
|
|
757
|
+
this._instance = new Location();
|
|
758
|
+
}
|
|
759
|
+
return this._instance;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Requests location tracking permissions on the current device.
|
|
763
|
+
* @returns A promise resolving to true if location access was granted.
|
|
764
|
+
*/
|
|
765
|
+
requestPermission() {
|
|
766
|
+
return this.impl.requestPermission();
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Obtains the current static geolocation coordinates.
|
|
770
|
+
* @returns A promise resolving to coordinates containing latitude and longitude.
|
|
771
|
+
*/
|
|
772
|
+
getCurrentPosition() {
|
|
773
|
+
return this.impl.getCurrentPosition();
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Registers a continuous watch listener on the device's location coordinates.
|
|
777
|
+
* @param callback Callback function executed whenever location updates occur.
|
|
778
|
+
* @returns A promise resolving to a string watch ID which can be cleared.
|
|
779
|
+
*/
|
|
780
|
+
watchPosition(callback) {
|
|
781
|
+
return this.impl.watchPosition(callback);
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Cancels an ongoing geolocation watch listener session.
|
|
785
|
+
* @param watchId The string ID of the watch listener to release.
|
|
786
|
+
*/
|
|
787
|
+
clearWatch(watchId) {
|
|
788
|
+
this.impl.clearWatch(watchId);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Central system utility class for managing, requesting, and query-checking application permissions.
|
|
794
|
+
* Serves as a standard interface for handling high-security mobile device feature access (Camera, Location, Push, etc.).
|
|
795
|
+
*/
|
|
796
|
+
class Permissions {
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Cross-platform Network connection and status tracking utility.
|
|
801
|
+
*/
|
|
802
|
+
class Network {
|
|
803
|
+
/**
|
|
804
|
+
* Checks if the device is currently online.
|
|
805
|
+
* @returns A promise resolving to true if connected, false otherwise.
|
|
806
|
+
*/
|
|
807
|
+
static async isOnline() {
|
|
808
|
+
// 1. Browser fallback (works also inside Electron renderer)
|
|
809
|
+
if (typeof browserOrignalNavigator !== "undefined" && "onLine" in browserOrignalNavigator) {
|
|
810
|
+
return browserOrignalNavigator.onLine;
|
|
811
|
+
}
|
|
812
|
+
// 2. Capacitor Native (mobile / desktop)
|
|
813
|
+
try {
|
|
814
|
+
const status = await Network$1.getStatus();
|
|
815
|
+
return status.connected;
|
|
816
|
+
}
|
|
817
|
+
catch (e) {
|
|
818
|
+
// 3. Final fallback
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Registers a listener to observe changes in the device's online/offline connection state.
|
|
824
|
+
* @param callback Callback function executed whenever network state changes.
|
|
825
|
+
*/
|
|
826
|
+
static async listenNetwork(callback) {
|
|
827
|
+
try {
|
|
828
|
+
const { Network } = await import('@capacitor/network');
|
|
829
|
+
Network.addListener("networkStatusChange", status => {
|
|
830
|
+
callback(status.connected);
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
catch (e) {
|
|
834
|
+
// Web fallback
|
|
835
|
+
window.addEventListener("online", () => callback(true));
|
|
836
|
+
window.addEventListener("offline", () => callback(false));
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
class CapacitorBluetooth {
|
|
842
|
+
devices = new Map();
|
|
843
|
+
connectedDevices = new Set();
|
|
844
|
+
constructor() {
|
|
845
|
+
BleClient.initialize();
|
|
846
|
+
}
|
|
847
|
+
async requestPermission() {
|
|
848
|
+
try {
|
|
849
|
+
await BleClient.requestLEScan({ services: ['0000180f-0000-1000-8000-00805f9b34fb'] }, (result) => {
|
|
850
|
+
console.log('Device found:', result.device.name, result.device.deviceId);
|
|
851
|
+
});
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
catch (err) {
|
|
855
|
+
console.warn('Permission denied:', err);
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
async scan() {
|
|
860
|
+
console.log('Capacitor: scanning for devices...');
|
|
861
|
+
this.devices.clear();
|
|
862
|
+
try {
|
|
863
|
+
const device = await BleClient.requestDevice({
|
|
864
|
+
services: ['0000180f-0000-1000-8000-00805f9b34fb'], // example
|
|
865
|
+
});
|
|
866
|
+
console.log('Found device:', device.name, device.deviceId);
|
|
867
|
+
this.devices.set(device.deviceId, device);
|
|
868
|
+
// Return as BluetoothDeviceInfo array
|
|
869
|
+
return [{
|
|
870
|
+
id: device.deviceId,
|
|
871
|
+
name: device.name,
|
|
872
|
+
}];
|
|
873
|
+
}
|
|
874
|
+
catch (err) {
|
|
875
|
+
console.error('Scan failed or canceled:', err);
|
|
876
|
+
return []; // return empty array on failure
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
async connect(deviceId) {
|
|
880
|
+
const device = this.devices.get(deviceId);
|
|
881
|
+
if (!device)
|
|
882
|
+
throw new Error('Device not found');
|
|
883
|
+
await BleClient.connect(deviceId);
|
|
884
|
+
this.connectedDevices.add(deviceId);
|
|
885
|
+
console.log('Connected to', device.name);
|
|
886
|
+
}
|
|
887
|
+
async disconnect(deviceId) {
|
|
888
|
+
try {
|
|
889
|
+
await BleClient.disconnect(deviceId);
|
|
890
|
+
this.connectedDevices.delete(deviceId);
|
|
891
|
+
console.log('Disconnected from', deviceId);
|
|
892
|
+
}
|
|
893
|
+
catch (err) {
|
|
894
|
+
console.warn('Error disconnecting', err);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
async isConnected(deviceId) {
|
|
898
|
+
return this.connectedDevices.has(deviceId);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
class ElectronBluetooth {
|
|
902
|
+
connectedDevices = new Map();
|
|
903
|
+
async requestPermission() {
|
|
904
|
+
try {
|
|
905
|
+
// Trigger the permission prompt (must be from user gesture)
|
|
906
|
+
await browserOrignalNavigator.bluetooth.requestDevice({ acceptAllDevices: true });
|
|
907
|
+
return true;
|
|
908
|
+
}
|
|
909
|
+
catch (err) {
|
|
910
|
+
console.warn('Permission denied or canceled:', err);
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
async scan() {
|
|
915
|
+
console.log('Electron: scanning...');
|
|
916
|
+
const devices = [];
|
|
917
|
+
try {
|
|
918
|
+
const device = await browserOrignalNavigator.bluetooth.requestDevice({
|
|
919
|
+
acceptAllDevices: true,
|
|
920
|
+
optionalServices: ['battery_service'], // example
|
|
921
|
+
});
|
|
922
|
+
console.log('Found device:', device.name, device.id);
|
|
923
|
+
this.connectedDevices.set(device.id, device);
|
|
924
|
+
devices.push({
|
|
925
|
+
id: device.id,
|
|
926
|
+
name: device.name,
|
|
927
|
+
});
|
|
928
|
+
return devices;
|
|
929
|
+
}
|
|
930
|
+
catch (err) {
|
|
931
|
+
console.error('Scan failed:', err);
|
|
932
|
+
return [];
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
async connect(deviceId) {
|
|
936
|
+
const device = this.connectedDevices.get(deviceId);
|
|
937
|
+
if (!device)
|
|
938
|
+
throw new Error('Device not found');
|
|
939
|
+
const server = await device.gatt?.connect();
|
|
940
|
+
if (!server)
|
|
941
|
+
throw new Error('Failed to connect');
|
|
942
|
+
console.log('Connected to', device.name);
|
|
943
|
+
}
|
|
944
|
+
async disconnect(deviceId) {
|
|
945
|
+
const device = this.connectedDevices.get(deviceId);
|
|
946
|
+
if (device?.gatt?.connected) {
|
|
947
|
+
device.gatt.disconnect();
|
|
948
|
+
console.log('Disconnected from', device.name);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
async isConnected(deviceId) {
|
|
952
|
+
const device = this.connectedDevices.get(deviceId);
|
|
953
|
+
return !!device?.gatt?.connected;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
class WebBluetooth {
|
|
957
|
+
devices = new Map();
|
|
958
|
+
connectedDevices = new Map();
|
|
959
|
+
async requestPermission() {
|
|
960
|
+
try {
|
|
961
|
+
// Try a dummy request to trigger permission prompt
|
|
962
|
+
await browserOrignalNavigator.bluetooth.requestDevice({
|
|
963
|
+
acceptAllDevices: true,
|
|
964
|
+
});
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
catch (err) {
|
|
968
|
+
console.warn('Permission denied or user canceled:', err);
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
async scan() {
|
|
973
|
+
try {
|
|
974
|
+
const device = await browserOrignalNavigator.bluetooth.requestDevice({
|
|
975
|
+
acceptAllDevices: true,
|
|
976
|
+
optionalServices: ['battery_service'],
|
|
977
|
+
});
|
|
978
|
+
this.devices.set(device.id, device);
|
|
979
|
+
return [{ id: device.id, name: device.name }];
|
|
980
|
+
}
|
|
981
|
+
catch (err) {
|
|
982
|
+
console.error('Scan failed:', err);
|
|
983
|
+
return [];
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
async connect(deviceId) {
|
|
987
|
+
const device = this.devices.get(deviceId);
|
|
988
|
+
if (!device)
|
|
989
|
+
throw new Error('Device not found');
|
|
990
|
+
const server = await device.gatt?.connect();
|
|
991
|
+
if (!server)
|
|
992
|
+
throw new Error('Failed to connect');
|
|
993
|
+
this.connectedDevices.set(deviceId, server);
|
|
994
|
+
console.log(`Web: connected to ${device.name}`);
|
|
995
|
+
}
|
|
996
|
+
async disconnect(deviceId) {
|
|
997
|
+
const server = this.connectedDevices.get(deviceId);
|
|
998
|
+
if (server?.connected) {
|
|
999
|
+
server.disconnect();
|
|
1000
|
+
console.log(`Web: disconnected from ${deviceId}`);
|
|
1001
|
+
}
|
|
1002
|
+
this.connectedDevices.delete(deviceId);
|
|
1003
|
+
}
|
|
1004
|
+
async isConnected(deviceId) {
|
|
1005
|
+
const server = this.connectedDevices.get(deviceId);
|
|
1006
|
+
return server?.connected ?? false;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Cross-platform Bluetooth LE tracking utility that delegates permission prompts, scanning,
|
|
1011
|
+
* and connection lifecycle methods to Capacitor BLE plugins, Electron, or Web Bluetooth APIs.
|
|
1012
|
+
*/
|
|
1013
|
+
class Bluetooth {
|
|
1014
|
+
impl;
|
|
1015
|
+
// Static instance
|
|
1016
|
+
static _instance;
|
|
1017
|
+
constructor() {
|
|
1018
|
+
if (Platforms.isMobile()) {
|
|
1019
|
+
this.impl = new CapacitorBluetooth();
|
|
1020
|
+
}
|
|
1021
|
+
else if (Platforms.isDesktop()) {
|
|
1022
|
+
this.impl = new ElectronBluetooth();
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
this.impl = new WebBluetooth();
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Singleton instance of the Bluetooth LE utility.
|
|
1030
|
+
*/
|
|
1031
|
+
static get instance() {
|
|
1032
|
+
if (!Bluetooth._instance) {
|
|
1033
|
+
Bluetooth._instance = new Bluetooth();
|
|
1034
|
+
}
|
|
1035
|
+
return Bluetooth._instance;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Triggers the platform's native Bluetooth permission prompt or scan dialog.
|
|
1039
|
+
* @returns A promise resolving to true if permission was granted.
|
|
1040
|
+
*/
|
|
1041
|
+
requestPermission() {
|
|
1042
|
+
return this.impl.requestPermission();
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Initiates a scanner session to discover nearby Bluetooth LE peripherals.
|
|
1046
|
+
* @returns A promise resolving to a list of discovered devices.
|
|
1047
|
+
*/
|
|
1048
|
+
scan() {
|
|
1049
|
+
return this.impl.scan();
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Connects to a specific Bluetooth LE device.
|
|
1053
|
+
* @param deviceId Unique hardware identifier of the target device.
|
|
1054
|
+
*/
|
|
1055
|
+
connect(deviceId) {
|
|
1056
|
+
return this.impl.connect(deviceId);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Closes the active GATT connection with a specific device.
|
|
1060
|
+
* @param deviceId Unique hardware identifier of the target device.
|
|
1061
|
+
*/
|
|
1062
|
+
disconnect(deviceId) {
|
|
1063
|
+
return this.impl.disconnect(deviceId);
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Checks if a specific device is currently connected.
|
|
1067
|
+
* @param deviceId Unique hardware identifier of the target device.
|
|
1068
|
+
* @returns A promise resolving to true if connected, false otherwise.
|
|
1069
|
+
*/
|
|
1070
|
+
isConnected(deviceId) {
|
|
1071
|
+
return this.impl.isConnected(deviceId);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// clipboard.ts
|
|
1076
|
+
class CapacitorClipboard {
|
|
1077
|
+
async readText() {
|
|
1078
|
+
const { value } = await Clipboard$1.read();
|
|
1079
|
+
return value || '';
|
|
1080
|
+
}
|
|
1081
|
+
async writeText(text) {
|
|
1082
|
+
await Clipboard$1.write({ string: text });
|
|
1083
|
+
}
|
|
1084
|
+
async requestPermission() {
|
|
1085
|
+
// Capacitor usually allows clipboard by default
|
|
1086
|
+
return true;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
class ElectronClipboard {
|
|
1090
|
+
electronClipboard = window.electron?.clipboard;
|
|
1091
|
+
async readText() {
|
|
1092
|
+
return this.electronClipboard ? this.electronClipboard.readText() : '';
|
|
1093
|
+
}
|
|
1094
|
+
async writeText(text) {
|
|
1095
|
+
if (this.electronClipboard)
|
|
1096
|
+
this.electronClipboard.writeText(text);
|
|
1097
|
+
}
|
|
1098
|
+
async requestPermission() {
|
|
1099
|
+
// Electron clipboard access is unrestricted
|
|
1100
|
+
return true;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
class WebClipboard {
|
|
1104
|
+
async readText() {
|
|
1105
|
+
try {
|
|
1106
|
+
return await browserOrignalNavigator.clipboard.readText();
|
|
1107
|
+
}
|
|
1108
|
+
catch {
|
|
1109
|
+
return '';
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
async writeText(text) {
|
|
1113
|
+
try {
|
|
1114
|
+
await browserOrignalNavigator.clipboard.writeText(text);
|
|
1115
|
+
}
|
|
1116
|
+
catch { }
|
|
1117
|
+
}
|
|
1118
|
+
async requestPermission() {
|
|
1119
|
+
try {
|
|
1120
|
+
// Try a dummy write to request permission
|
|
1121
|
+
await browserOrignalNavigator.clipboard.writeText('');
|
|
1122
|
+
return true;
|
|
1123
|
+
}
|
|
1124
|
+
catch {
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Cross-platform Clipboard utility that delegates copy/paste text actions
|
|
1131
|
+
* to native Capacitor plugins, Electron, or browser Clipboard APIs depending on the environment.
|
|
1132
|
+
*/
|
|
1133
|
+
class Clipboard {
|
|
1134
|
+
static _instance;
|
|
1135
|
+
impl;
|
|
1136
|
+
constructor() {
|
|
1137
|
+
this.impl = this.detectPlatform();
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Singleton instance of the Clipboard utility.
|
|
1141
|
+
*/
|
|
1142
|
+
static get instance() {
|
|
1143
|
+
if (!Clipboard._instance) {
|
|
1144
|
+
Clipboard._instance = new Clipboard();
|
|
1145
|
+
}
|
|
1146
|
+
return Clipboard._instance;
|
|
1147
|
+
}
|
|
1148
|
+
detectPlatform() {
|
|
1149
|
+
// Capacitor
|
|
1150
|
+
if (Platforms.isMobile()) {
|
|
1151
|
+
return new CapacitorClipboard();
|
|
1152
|
+
}
|
|
1153
|
+
// Electron
|
|
1154
|
+
if (Platforms.isDesktop()) {
|
|
1155
|
+
return new ElectronClipboard();
|
|
1156
|
+
}
|
|
1157
|
+
// Web fallback
|
|
1158
|
+
return new WebClipboard();
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Reads plain text from the system clipboard.
|
|
1162
|
+
* @returns A promise resolving to the clipboard's string value.
|
|
1163
|
+
*/
|
|
1164
|
+
readText() {
|
|
1165
|
+
return this.impl.readText();
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Writes the specified text to the system clipboard.
|
|
1169
|
+
* @param text The plain text string to copy.
|
|
1170
|
+
*/
|
|
1171
|
+
writeText(text) {
|
|
1172
|
+
return this.impl.writeText(text);
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Requests system clipboard permission prompts on the current platform.
|
|
1176
|
+
* @returns A promise resolving to true if clipboard access was successfully permitted.
|
|
1177
|
+
*/
|
|
1178
|
+
requestPermission() {
|
|
1179
|
+
return this.impl.requestPermission();
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
class CapacitorBattery {
|
|
1184
|
+
async getLevel() {
|
|
1185
|
+
const info = await browserOrignalNavigator.getBattery?.(); // Capacitor/Battery plugin
|
|
1186
|
+
return info?.level ?? 1;
|
|
1187
|
+
}
|
|
1188
|
+
async isCharging() {
|
|
1189
|
+
const info = await browserOrignalNavigator.getBattery?.();
|
|
1190
|
+
return info?.charging ?? true;
|
|
1191
|
+
}
|
|
1192
|
+
onLevelChange(callback) {
|
|
1193
|
+
browserOrignalNavigator.getBattery?.()?.then((b) => b.addEventListener('levelchange', () => callback(b.level)));
|
|
1194
|
+
}
|
|
1195
|
+
onChargingChange(callback) {
|
|
1196
|
+
browserOrignalNavigator.getBattery?.()?.then((b) => b.addEventListener('chargingchange', () => callback(b.charging)));
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
class ElectronBattery extends CapacitorBattery {
|
|
1200
|
+
}
|
|
1201
|
+
class WebBattery extends CapacitorBattery {
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Cross-platform Battery tracking utility that delegates status lookups and level change event
|
|
1205
|
+
* subscriptions to native Capacitor plugins, Electron, or Web Battery APIs.
|
|
1206
|
+
*/
|
|
1207
|
+
class Battery {
|
|
1208
|
+
static _instance;
|
|
1209
|
+
constructor() { }
|
|
1210
|
+
static get impl() {
|
|
1211
|
+
if (!this._instance) {
|
|
1212
|
+
if (window.Capacitor?.isNativePlatform) {
|
|
1213
|
+
this._instance = new CapacitorBattery();
|
|
1214
|
+
}
|
|
1215
|
+
else if (typeof window.electron !== 'undefined') {
|
|
1216
|
+
this._instance = new ElectronBattery();
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
this._instance = new WebBattery();
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return this._instance;
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Get the current battery level as a number between 0 and 1.
|
|
1226
|
+
* @returns {Promise<number>} A promise that resolves with the battery level.
|
|
1227
|
+
*/
|
|
1228
|
+
static getLevel() {
|
|
1229
|
+
return this.impl.getLevel();
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Check whether the device is currently charging.
|
|
1233
|
+
* @returns {Promise<boolean>} A promise that resolves to true if charging, false otherwise.
|
|
1234
|
+
*/
|
|
1235
|
+
static isCharging() {
|
|
1236
|
+
return this.impl.isCharging();
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Subscribe to battery level changes.
|
|
1240
|
+
* @param {(level: number) => void} callback - Callback invoked when the battery level changes.
|
|
1241
|
+
* @returns {() => void} A function to unsubscribe from the level change event.
|
|
1242
|
+
*/
|
|
1243
|
+
static onLevelChange(callback) {
|
|
1244
|
+
return this.impl.onLevelChange(callback);
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Subscribe to charging status changes.
|
|
1248
|
+
* @param {(charging: boolean) => void} callback - Callback invoked when the charging status changes.
|
|
1249
|
+
* @returns {() => void} A function to unsubscribe from the charging change event.
|
|
1250
|
+
*/
|
|
1251
|
+
static onChargingChange(callback) {
|
|
1252
|
+
return this.impl.onChargingChange(callback);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* Capacitor implementation utilizing mobile shell services and browser-native APIs.
|
|
1258
|
+
*/
|
|
1259
|
+
class CapacitorSystem {
|
|
1260
|
+
/**
|
|
1261
|
+
* Fetches Capacitor device information metadata.
|
|
1262
|
+
*/
|
|
1263
|
+
getInfo() {
|
|
1264
|
+
return Device$1.getInfo();
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Desktop and device CPU usage mock fallback.
|
|
1268
|
+
*/
|
|
1269
|
+
async getCPUUsage() {
|
|
1270
|
+
// No standard Capacitor API, fallback
|
|
1271
|
+
return 0;
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Attempts to resolve memory usage using native Device parameters.
|
|
1275
|
+
*/
|
|
1276
|
+
async getMemoryUsage() {
|
|
1277
|
+
const info = await Device$1.getInfo();
|
|
1278
|
+
if (info.platform === 'android') {
|
|
1279
|
+
// Only Android exposes total memory via Device plugin
|
|
1280
|
+
return {
|
|
1281
|
+
total: 0,
|
|
1282
|
+
used: info.memUsed || 0, // no precise usage info
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
return { used: 0, total: 0 }; // fallback for iOS / web
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Resolves core thread count.
|
|
1289
|
+
*/
|
|
1290
|
+
async getCPUCoreCount() {
|
|
1291
|
+
return navigator.hardwareConcurrency;
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Resolves system physical memory capacity.
|
|
1295
|
+
*/
|
|
1296
|
+
async getDeviceMemory() {
|
|
1297
|
+
return navigator.deviceMemory;
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Resolves effective network latency profiles.
|
|
1301
|
+
*/
|
|
1302
|
+
getNetworkType() {
|
|
1303
|
+
return navigator.connection?.effectiveType;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Electron desktop system implementation.
|
|
1308
|
+
* Queries CPU, RAM, and hardware metrics via desktop-bound IPC helper bridges.
|
|
1309
|
+
*/
|
|
1310
|
+
class ElectronSystem {
|
|
1311
|
+
/**
|
|
1312
|
+
* Resolves system platform specifications.
|
|
1313
|
+
*/
|
|
1314
|
+
async getInfo() {
|
|
1315
|
+
const info = await window.electron.getSystemInfo();
|
|
1316
|
+
return info;
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Queries real-time processor load via desktop shell process monitors.
|
|
1320
|
+
*/
|
|
1321
|
+
async getCPUUsage() {
|
|
1322
|
+
const metrics = await window.electron.getMetrics();
|
|
1323
|
+
return metrics.cpu; // 0..1
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Queries real-time memory allocations.
|
|
1327
|
+
*/
|
|
1328
|
+
async getMemoryUsage() {
|
|
1329
|
+
const metrics = await window.electron.getMetrics();
|
|
1330
|
+
return metrics.memory; // { used, total }
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Queries physical thread count metrics.
|
|
1334
|
+
*/
|
|
1335
|
+
async getCPUCoreCount() {
|
|
1336
|
+
const info = await window.electron.getSystemInfo();
|
|
1337
|
+
return info.cpu?.cores;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Resolves total RAM size in Gigabytes.
|
|
1341
|
+
*/
|
|
1342
|
+
async getDeviceMemory() {
|
|
1343
|
+
const info = await window.electron.getSystemInfo();
|
|
1344
|
+
return info.memory?.total / (1024 ** 3);
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Queries effective network speed profiles.
|
|
1348
|
+
*/
|
|
1349
|
+
getNetworkType() {
|
|
1350
|
+
return navigator.connection?.effectiveType;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Browser-native web system metrics client.
|
|
1355
|
+
* Relies on standard DOM capabilities and performance parameters.
|
|
1356
|
+
*/
|
|
1357
|
+
class WebSystem {
|
|
1358
|
+
/**
|
|
1359
|
+
* Parses browser and platform credentials from the standard user-agent.
|
|
1360
|
+
*/
|
|
1361
|
+
async getInfo() {
|
|
1362
|
+
const uaData = browserOrignalNavigator.userAgentData;
|
|
1363
|
+
return {
|
|
1364
|
+
browser: uaData?.brands?.find((b) => b.brand.includes("Chrome"))?.brand || "Chrome",
|
|
1365
|
+
version: uaData?.brands?.find((b) => b.brand.includes("Chrome"))?.version,
|
|
1366
|
+
platform: uaData?.platform,
|
|
1367
|
+
mobile: uaData?.mobile,
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Process usage mock fallback.
|
|
1372
|
+
*/
|
|
1373
|
+
async getCPUUsage() {
|
|
1374
|
+
// Web has no direct CPU access; fallback to 0
|
|
1375
|
+
return 0;
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Resolves standard JS heap size allocations if performance memory APIs are supported.
|
|
1379
|
+
*/
|
|
1380
|
+
async getMemoryUsage() {
|
|
1381
|
+
const mem = performance.memory;
|
|
1382
|
+
if (mem) {
|
|
1383
|
+
return { used: mem.usedJSHeapSize, total: mem.totalJSHeapSize };
|
|
1384
|
+
}
|
|
1385
|
+
return { used: 0, total: 0 };
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Resolves browser-reported thread concurrency limits.
|
|
1389
|
+
*/
|
|
1390
|
+
async getCPUCoreCount() {
|
|
1391
|
+
return browserOrignalNavigator.hardwareConcurrency;
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Resolves browser-reported device memory size in Gigabytes.
|
|
1395
|
+
*/
|
|
1396
|
+
async getDeviceMemory() {
|
|
1397
|
+
return browserOrignalNavigator.deviceMemory;
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Resolves connection type details from browser connection descriptors.
|
|
1401
|
+
*/
|
|
1402
|
+
getNetworkType() {
|
|
1403
|
+
return browserOrignalNavigator.connection?.effectiveType;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Singleton proxy class routing diagnostic query operations to the resolved platform system helper.
|
|
1408
|
+
*/
|
|
1409
|
+
class System {
|
|
1410
|
+
static _instance;
|
|
1411
|
+
constructor() { }
|
|
1412
|
+
/**
|
|
1413
|
+
* Internal getter to resolve the target native interface service.
|
|
1414
|
+
*/
|
|
1415
|
+
static get impl() {
|
|
1416
|
+
if (!this._instance) {
|
|
1417
|
+
if (Platforms.isMobile()) {
|
|
1418
|
+
this._instance = new CapacitorSystem();
|
|
1419
|
+
}
|
|
1420
|
+
else if (Platforms.isDesktop()) {
|
|
1421
|
+
this._instance = new ElectronSystem();
|
|
1422
|
+
}
|
|
1423
|
+
else {
|
|
1424
|
+
this._instance = new WebSystem();
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
return this._instance;
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Resolves current CPU usage fraction.
|
|
1431
|
+
*/
|
|
1432
|
+
static getCPUUsage() {
|
|
1433
|
+
return this.impl.getCPUUsage();
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Resolves allocated and free heap memory details.
|
|
1437
|
+
*/
|
|
1438
|
+
static getMemoryUsage() {
|
|
1439
|
+
return this.impl.getMemoryUsage();
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Resolves host system core counts.
|
|
1443
|
+
*/
|
|
1444
|
+
static getCPUCoreCount() {
|
|
1445
|
+
return this.impl.getCPUCoreCount();
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Resolves physical device memory in GB.
|
|
1449
|
+
*/
|
|
1450
|
+
static getDeviceMemory() {
|
|
1451
|
+
return this.impl.getDeviceMemory();
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Resolves network connection speed indicator tags.
|
|
1455
|
+
*/
|
|
1456
|
+
static getNetworkType() {
|
|
1457
|
+
return this.impl.getNetworkType();
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Fetches detailed platform environment descriptors.
|
|
1461
|
+
*/
|
|
1462
|
+
static getInfo() {
|
|
1463
|
+
return System.impl.getInfo();
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
/**
|
|
1468
|
+
* Web client haptic engine utilizing browser standard `navigator.vibrate` or `web-haptics` utilities.
|
|
1469
|
+
*/
|
|
1470
|
+
class WebVibration {
|
|
1471
|
+
haptics;
|
|
1472
|
+
/**
|
|
1473
|
+
* Initializes the WebVibration instance and registers fallback web-haptics listeners.
|
|
1474
|
+
*/
|
|
1475
|
+
constructor() {
|
|
1476
|
+
this.haptics = new WebHaptics({
|
|
1477
|
+
showSwitch: false, // optional: hide/show toggle
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Triggers standard browser-native vibration patterns.
|
|
1482
|
+
* @param vibration Haptic duration options.
|
|
1483
|
+
*/
|
|
1484
|
+
async vibrate(vibration) {
|
|
1485
|
+
if ("vibrate" in browserOrignalNavigator || WebHaptics.isSupported) {
|
|
1486
|
+
await this.haptics.trigger(vibration);
|
|
1487
|
+
}
|
|
1488
|
+
else {
|
|
1489
|
+
console.warn("Vibration not supported in this browser");
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Resets browser vibration activity by sending a zero-intensity trigger.
|
|
1494
|
+
*/
|
|
1495
|
+
cancel() {
|
|
1496
|
+
browserOrignalNavigator.vibrate(0);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Mobile-specific vibration driver utilizing native system haptics via `@capacitor/haptics`.
|
|
1501
|
+
*/
|
|
1502
|
+
class CapacitorVibration {
|
|
1503
|
+
/**
|
|
1504
|
+
* Dispatches native haptic system feedback on Android and iOS devices.
|
|
1505
|
+
* Maps arrays of pulses into consecutive physical haptic impacts.
|
|
1506
|
+
* @param duration Millisecond pulse duration configurations.
|
|
1507
|
+
*/
|
|
1508
|
+
async vibrate(duration) {
|
|
1509
|
+
if (Array.isArray(duration)) {
|
|
1510
|
+
for (const dur of duration) {
|
|
1511
|
+
await Haptics.impact({ style: ImpactStyle.Medium });
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
else {
|
|
1515
|
+
// Single impact for simplicity
|
|
1516
|
+
Haptics.impact({ style: ImpactStyle.Medium });
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Cancels native haptics or triggers a subtle success alert.
|
|
1521
|
+
*/
|
|
1522
|
+
cancel() {
|
|
1523
|
+
// Trigger a neutral notification as "cancel" placeholder
|
|
1524
|
+
Haptics.notification({ type: NotificationType.Success });
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Electron desktop vibration controller fallback.
|
|
1529
|
+
*/
|
|
1530
|
+
class ElectronVibration {
|
|
1531
|
+
/**
|
|
1532
|
+
* Mock vibration method warning users that desktop hardware lacks vibration motors.
|
|
1533
|
+
*/
|
|
1534
|
+
vibrate(duration) {
|
|
1535
|
+
console.warn("Vibration not supported on desktop");
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* No-op cancel routine.
|
|
1539
|
+
*/
|
|
1540
|
+
cancel() {
|
|
1541
|
+
// noop
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Singleton wrapper and router managing device vibrations across platforms.
|
|
1546
|
+
*/
|
|
1547
|
+
class Vibration {
|
|
1548
|
+
static _instance;
|
|
1549
|
+
impl;
|
|
1550
|
+
/**
|
|
1551
|
+
* Private constructor to enforce standard singleton instantiation, resolving target driver at runtime.
|
|
1552
|
+
*/
|
|
1553
|
+
constructor() {
|
|
1554
|
+
if (Platforms.isMobile()) {
|
|
1555
|
+
this.impl = new CapacitorVibration();
|
|
1556
|
+
}
|
|
1557
|
+
else {
|
|
1558
|
+
this.impl = new WebVibration();
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Retrieves the global active, platform-resolved singleton vibration engine instance.
|
|
1563
|
+
*/
|
|
1564
|
+
static get instance() {
|
|
1565
|
+
if (!this._instance) {
|
|
1566
|
+
this._instance = new Vibration();
|
|
1567
|
+
}
|
|
1568
|
+
return this._instance;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Triggers vibration using the platform resolved driver.
|
|
1572
|
+
* @param duration A numeric duration in milliseconds or a structured pattern array.
|
|
1573
|
+
*/
|
|
1574
|
+
vibrate(duration) {
|
|
1575
|
+
this.impl.vibrate(duration);
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Cancels all active vibration outputs.
|
|
1579
|
+
*/
|
|
1580
|
+
cancel() {
|
|
1581
|
+
this.impl.cancel();
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
class WebMicrophone {
|
|
1586
|
+
stream = null;
|
|
1587
|
+
async requestPermission() {
|
|
1588
|
+
try {
|
|
1589
|
+
const status = await browserOrignalNavigator.permissions.query({ name: "microphone" });
|
|
1590
|
+
if (status.state === "granted")
|
|
1591
|
+
return true;
|
|
1592
|
+
// Request permission
|
|
1593
|
+
await browserOrignalNavigator.mediaDevices.getUserMedia({ audio: true });
|
|
1594
|
+
return true;
|
|
1595
|
+
}
|
|
1596
|
+
catch (err) {
|
|
1597
|
+
console.error("Mic permission denied:", err);
|
|
1598
|
+
return false;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
async start() {
|
|
1602
|
+
this.stream = await browserOrignalNavigator.mediaDevices.getUserMedia({ audio: true });
|
|
1603
|
+
}
|
|
1604
|
+
stop() {
|
|
1605
|
+
if (this.stream) {
|
|
1606
|
+
this.stream.getTracks().forEach(t => t.stop());
|
|
1607
|
+
this.stream = null;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
getStream() {
|
|
1611
|
+
return this.stream;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
class ElectronMicrophone extends WebMicrophone {
|
|
1615
|
+
}
|
|
1616
|
+
class CapacitorMicrophone {
|
|
1617
|
+
stream = null;
|
|
1618
|
+
async start() {
|
|
1619
|
+
// This automatically triggers OS permission UI
|
|
1620
|
+
this.stream = await browserOrignalNavigator.mediaDevices.getUserMedia({ audio: true });
|
|
1621
|
+
}
|
|
1622
|
+
async requestPermission() {
|
|
1623
|
+
return true;
|
|
1624
|
+
}
|
|
1625
|
+
stop() {
|
|
1626
|
+
if (this.stream) {
|
|
1627
|
+
this.stream.getTracks().forEach(t => t.stop());
|
|
1628
|
+
this.stream = null;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
getStream() {
|
|
1632
|
+
return this.stream;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
// Singleton wrapper
|
|
1636
|
+
/**
|
|
1637
|
+
* Cross-platform Microphone utility that delegates audio capture streams and recording permissions
|
|
1638
|
+
* to Capacitor plugins, Electron, or browser MediaStream APIs depending on the environment.
|
|
1639
|
+
*/
|
|
1640
|
+
class Microphone {
|
|
1641
|
+
static _instance;
|
|
1642
|
+
impl;
|
|
1643
|
+
constructor() {
|
|
1644
|
+
if (Platforms.isMobile()) {
|
|
1645
|
+
this.impl = new CapacitorMicrophone();
|
|
1646
|
+
}
|
|
1647
|
+
else if (Platforms.isDesktop()) {
|
|
1648
|
+
this.impl = new ElectronMicrophone();
|
|
1649
|
+
}
|
|
1650
|
+
else {
|
|
1651
|
+
this.impl = new WebMicrophone();
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Singleton instance of the Microphone utility.
|
|
1656
|
+
*/
|
|
1657
|
+
static get instance() {
|
|
1658
|
+
if (!this._instance)
|
|
1659
|
+
this._instance = new Microphone();
|
|
1660
|
+
return this._instance;
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Requests microphone permissions from the operating system or browser.
|
|
1664
|
+
* @returns A promise resolving to true if permission was granted.
|
|
1665
|
+
*/
|
|
1666
|
+
requestPermission() {
|
|
1667
|
+
return this.impl.requestPermission();
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Initiates the microphone sensor and binds it to an active audio MediaStream.
|
|
1671
|
+
*/
|
|
1672
|
+
start() {
|
|
1673
|
+
return this.impl.start();
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Halts microphone streaming and closes all associated audio media tracks.
|
|
1677
|
+
*/
|
|
1678
|
+
stop() {
|
|
1679
|
+
return this.impl.stop();
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Obtains the raw HTML5 MediaStream containing active audio tracks.
|
|
1683
|
+
* @returns The active MediaStream or null if not streaming.
|
|
1684
|
+
*/
|
|
1685
|
+
getStream() {
|
|
1686
|
+
return this.impl.getStream();
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
/**
|
|
1691
|
+
* Web-native orientation listener using viewport dimension ratios.
|
|
1692
|
+
* Listens to standard DOM window 'orientationchange' events.
|
|
1693
|
+
*/
|
|
1694
|
+
let WebOrientation$1 = class WebOrientation {
|
|
1695
|
+
listeners = new Set();
|
|
1696
|
+
/**
|
|
1697
|
+
* Initializes the WebOrientation instance and registers the global resize/orientation listener.
|
|
1698
|
+
*/
|
|
1699
|
+
constructor() {
|
|
1700
|
+
window.addEventListener("orientationchange", this.handleChange);
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Internal handler invoked when native window orientation changes.
|
|
1704
|
+
*/
|
|
1705
|
+
handleChange = () => {
|
|
1706
|
+
const o = this.getOrientation();
|
|
1707
|
+
this.listeners.forEach(cb => cb(o));
|
|
1708
|
+
};
|
|
1709
|
+
/**
|
|
1710
|
+
* Evaluates current viewport widths and heights to determine orientation.
|
|
1711
|
+
* @returns 'landscape' if viewport width > height, otherwise 'portrait'.
|
|
1712
|
+
*/
|
|
1713
|
+
getOrientation() {
|
|
1714
|
+
return window.innerWidth > window.innerHeight ? "landscape" : "portrait";
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Registers a callback listener for screen orientation updates.
|
|
1718
|
+
* @param callback Callback receiving new orientation.
|
|
1719
|
+
*/
|
|
1720
|
+
onChange(callback) {
|
|
1721
|
+
this.listeners.add(callback);
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Unregisters a callback listener.
|
|
1725
|
+
* @param callback Callback to remove.
|
|
1726
|
+
*/
|
|
1727
|
+
offChange(callback) {
|
|
1728
|
+
this.listeners.delete(callback);
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1731
|
+
/**
|
|
1732
|
+
* Capacitor-specific implementation using `@awesome-cordova-plugins/screen-orientation`
|
|
1733
|
+
* to leverage native mobile system sensors and orientation lock statuses.
|
|
1734
|
+
*/
|
|
1735
|
+
let CapacitorOrientation$1 = class CapacitorOrientation {
|
|
1736
|
+
callbackSet = new Set();
|
|
1737
|
+
/**
|
|
1738
|
+
* Initializes the mobile screen orientation event subscribers.
|
|
1739
|
+
*/
|
|
1740
|
+
constructor() {
|
|
1741
|
+
ScreenOrientation.onChange().subscribe(() => {
|
|
1742
|
+
const o = this.getOrientation();
|
|
1743
|
+
this.callbackSet.forEach(cb => cb(o));
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Checks native mobile screen orientation status.
|
|
1748
|
+
* @returns 'landscape' or 'portrait'.
|
|
1749
|
+
*/
|
|
1750
|
+
getOrientation() {
|
|
1751
|
+
const type = ScreenOrientation.type; // e.g., "portrait-primary", "landscape-secondary"
|
|
1752
|
+
return type.startsWith('landscape') ? 'landscape' : 'portrait';
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Registers a subscriber callback for orientation changes.
|
|
1756
|
+
* @param cb Callback receiving new orientation.
|
|
1757
|
+
*/
|
|
1758
|
+
onChange(cb) {
|
|
1759
|
+
this.callbackSet.add(cb);
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Unregisters a subscriber callback.
|
|
1763
|
+
* @param cb Callback to remove.
|
|
1764
|
+
*/
|
|
1765
|
+
offChange(cb) {
|
|
1766
|
+
this.callbackSet.delete(cb);
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
/**
|
|
1770
|
+
* Electron-specific desktop window orientation tracker.
|
|
1771
|
+
* Translates viewport resizing into mock orientation state updates.
|
|
1772
|
+
*/
|
|
1773
|
+
let ElectronOrientation$1 = class ElectronOrientation {
|
|
1774
|
+
callbacks = new Set();
|
|
1775
|
+
/**
|
|
1776
|
+
* Sets up resize listener on desktop shell.
|
|
1777
|
+
*/
|
|
1778
|
+
constructor() {
|
|
1779
|
+
window.addEventListener('resize', this.handleResize);
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Handles internal resize events to calculate dynamic aspect ratio modifications.
|
|
1783
|
+
*/
|
|
1784
|
+
handleResize = () => {
|
|
1785
|
+
const o = this.getOrientation();
|
|
1786
|
+
this.callbacks.forEach(cb => cb(o));
|
|
1787
|
+
};
|
|
1788
|
+
/**
|
|
1789
|
+
* Maps degrees of hardware rotation into portrait/landscape.
|
|
1790
|
+
* @param rotation The degree measurement (0, 90, 180, 270).
|
|
1791
|
+
*/
|
|
1792
|
+
getOrientationFromRotation(rotation) {
|
|
1793
|
+
return rotation === 0 || rotation === 180 ? 'portrait' : 'landscape';
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Inspects dynamic dimensions of the Electron shell window frame.
|
|
1797
|
+
* @returns 'landscape' or 'portrait'.
|
|
1798
|
+
*/
|
|
1799
|
+
getOrientation() {
|
|
1800
|
+
return window.innerWidth > window.innerHeight ? 'landscape' : 'portrait';
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Adds an event listener for orientation shifts.
|
|
1804
|
+
* @param cb Callback receiving the updated screen state.
|
|
1805
|
+
*/
|
|
1806
|
+
onChange(cb) {
|
|
1807
|
+
this.callbacks.add(cb);
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Removes an event listener.
|
|
1811
|
+
* @param cb Callback to remove.
|
|
1812
|
+
*/
|
|
1813
|
+
offChange(cb) {
|
|
1814
|
+
this.callbacks.delete(cb);
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
/**
|
|
1818
|
+
* Singleton wrapper and router for managing screen and window orientation states across platforms.
|
|
1819
|
+
*/
|
|
1820
|
+
class Orientation {
|
|
1821
|
+
static _instance;
|
|
1822
|
+
impl;
|
|
1823
|
+
/**
|
|
1824
|
+
* Enforces single instance behavior and resolves target device implementation at runtime.
|
|
1825
|
+
*/
|
|
1826
|
+
constructor() {
|
|
1827
|
+
if (Platforms.isDesktop()) {
|
|
1828
|
+
this.impl = new ElectronOrientation$1();
|
|
1829
|
+
}
|
|
1830
|
+
else if (Platforms.isMobile()) {
|
|
1831
|
+
this.impl = new CapacitorOrientation$1();
|
|
1832
|
+
}
|
|
1833
|
+
else {
|
|
1834
|
+
this.impl = new WebOrientation$1();
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Accesses the active, platform-resolved singleton orientation service.
|
|
1839
|
+
*/
|
|
1840
|
+
static get instance() {
|
|
1841
|
+
if (!this._instance) {
|
|
1842
|
+
this._instance = new Orientation();
|
|
1843
|
+
}
|
|
1844
|
+
return this._instance;
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Retrieves current viewport or physical device screen orientation.
|
|
1848
|
+
*/
|
|
1849
|
+
getOrientation() {
|
|
1850
|
+
return this.impl.getOrientation();
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Subscribes a listener to orientation modification events.
|
|
1854
|
+
* @param callback Callback receiving standard landscape/portrait codes.
|
|
1855
|
+
*/
|
|
1856
|
+
onChange(callback) {
|
|
1857
|
+
this.impl.onChange(callback);
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Unsubscribes a listener.
|
|
1861
|
+
* @param callback Callback to remove.
|
|
1862
|
+
*/
|
|
1863
|
+
offChange(callback) {
|
|
1864
|
+
this.impl.offChange(callback);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
/**
|
|
1869
|
+
* Placeholder interface and configuration utilities for SQLite database connection management.
|
|
1870
|
+
*
|
|
1871
|
+
* > [!NOTE]
|
|
1872
|
+
* > Local SQLite instance connections, initializations, and transactions on mobile and desktop platforms
|
|
1873
|
+
* > are actively managed by the dynamic helper class `Database` and the functional accessor `getDB()`
|
|
1874
|
+
* > implemented in `database.ts` (relying on `@capacitor-community/sqlite`).
|
|
1875
|
+
*/
|
|
1876
|
+
class SQLitePlaceholder {
|
|
1877
|
+
/**
|
|
1878
|
+
* Informational utility function that returns database module metadata.
|
|
1879
|
+
* @returns An object containing sqlite configuration details.
|
|
1880
|
+
*/
|
|
1881
|
+
static getMetadata() {
|
|
1882
|
+
return {
|
|
1883
|
+
name: "SQLite-Core",
|
|
1884
|
+
engine: "CapacitorSQLite"
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
/**
|
|
1890
|
+
* Web-native printer helper using browser standard `window.print()` workflows.
|
|
1891
|
+
* Opens a temporary hidden frame or window to present the content dialog to the user.
|
|
1892
|
+
*/
|
|
1893
|
+
class WebPrinter {
|
|
1894
|
+
/**
|
|
1895
|
+
* Opens a temporary tab frame and initiates the native system print pop-up.
|
|
1896
|
+
* @param html HTML text content to print.
|
|
1897
|
+
*/
|
|
1898
|
+
printHTML(html) {
|
|
1899
|
+
const win = window.open('', '_blank');
|
|
1900
|
+
if (!win)
|
|
1901
|
+
return;
|
|
1902
|
+
win.document.write(html);
|
|
1903
|
+
win.document.close();
|
|
1904
|
+
win.focus();
|
|
1905
|
+
win.print();
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Electron-specific printer helper to coordinate with physical native queues.
|
|
1910
|
+
* Currently serves as a feature placeholder.
|
|
1911
|
+
*/
|
|
1912
|
+
class ElectronPrinter {
|
|
1913
|
+
/**
|
|
1914
|
+
* Desktop platform custom print dispatcher.
|
|
1915
|
+
* @param html HTML source code to print.
|
|
1916
|
+
*/
|
|
1917
|
+
async printHTML(html) {
|
|
1918
|
+
// TODO: not supported
|
|
1919
|
+
throw new Error('not supported yet');
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Mobile-specific printer implementation utilizing native OS print spoolers.
|
|
1924
|
+
* Currently serves as a feature placeholder.
|
|
1925
|
+
*/
|
|
1926
|
+
class MobilePrinter {
|
|
1927
|
+
/**
|
|
1928
|
+
* Mobile platform custom print dispatcher.
|
|
1929
|
+
* @param html HTML source code to print.
|
|
1930
|
+
*/
|
|
1931
|
+
async printHTML(html) {
|
|
1932
|
+
// TODO: not supported
|
|
1933
|
+
throw new Error('not supported yet');
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Singleton wrapper and dynamic platform router for the system print engine services.
|
|
1938
|
+
*/
|
|
1939
|
+
class Printer {
|
|
1940
|
+
static _instance = null;
|
|
1941
|
+
/**
|
|
1942
|
+
* Retrieves the global active singleton printing service instance.
|
|
1943
|
+
* Resolves the target implementation at runtime according to the platform profile.
|
|
1944
|
+
*/
|
|
1945
|
+
static get instance() {
|
|
1946
|
+
if (this._instance === null) {
|
|
1947
|
+
if (Platforms.isDesktop()) {
|
|
1948
|
+
this._instance = new ElectronPrinter();
|
|
1949
|
+
}
|
|
1950
|
+
else if (Platforms.isMobile()) {
|
|
1951
|
+
this._instance = new MobilePrinter();
|
|
1952
|
+
}
|
|
1953
|
+
else {
|
|
1954
|
+
this._instance = new WebPrinter();
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
return this._instance;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
let backendApi = null;
|
|
1962
|
+
function setBackendAPI(url) {
|
|
1963
|
+
backendApi = url;
|
|
1964
|
+
}
|
|
1965
|
+
function getBackendAPI() {
|
|
1966
|
+
return backendApi;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
/**
|
|
1970
|
+
* Utility class for resolving static asset URLs served by plugins on the backend.
|
|
1971
|
+
*/
|
|
1972
|
+
class PluginAssets {
|
|
1973
|
+
/**
|
|
1974
|
+
* Resolves the backend network URL for a given plugin asset.
|
|
1975
|
+
* @param metadata The plugin's module metadata.
|
|
1976
|
+
* @param url The relative asset path (e.g., 'images/logo.png').
|
|
1977
|
+
* @returns The fully qualified absolute URL pointing to the backend static resource.
|
|
1978
|
+
*/
|
|
1979
|
+
static network(metadata, url) {
|
|
1980
|
+
const normalizedPath = '/' + url.replace(/^\.?\//, '');
|
|
1981
|
+
return `${getBackendAPI()}/modules/${metadata.name}/${metadata.pluginVersion}/frontend${normalizedPath}`;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
/**
|
|
1986
|
+
* Utility class for high-performance locale-aware number and currency formatting.
|
|
1987
|
+
* Features an internal cache to reuse Intl.NumberFormat instances across repetitive calls.
|
|
1988
|
+
*/
|
|
1989
|
+
class NumberFormatter {
|
|
1990
|
+
/**
|
|
1991
|
+
* Map cache key-value store holding instantiated Intl.NumberFormat objects.
|
|
1992
|
+
*/
|
|
1993
|
+
static formats = new Map();
|
|
1994
|
+
/**
|
|
1995
|
+
* Formats a numeric value according to the specified locale and formatting configuration.
|
|
1996
|
+
* Caches the underlying Intl.NumberFormat instance based on a normalized cache key.
|
|
1997
|
+
*
|
|
1998
|
+
* @param num The numeric value to format.
|
|
1999
|
+
* @param locale The RFC 5646 language tag representing the locale (e.g., 'en-US', 'my-MM'). Defaults to 'en-US'.
|
|
2000
|
+
* @param options Standard ECMAScript Internationalization API number format options.
|
|
2001
|
+
* @returns The formatted string representation of the number.
|
|
2002
|
+
*/
|
|
2003
|
+
static format(num, locale = 'en-US', options) {
|
|
2004
|
+
// Sort options keys for consistent cache key
|
|
2005
|
+
const sortedOptions = options
|
|
2006
|
+
? Object.keys(options)
|
|
2007
|
+
.sort()
|
|
2008
|
+
.reduce((acc, key) => {
|
|
2009
|
+
acc[key] = options[key];
|
|
2010
|
+
return acc;
|
|
2011
|
+
}, {})
|
|
2012
|
+
: {};
|
|
2013
|
+
const key = locale + JSON.stringify(sortedOptions);
|
|
2014
|
+
let formatter = this.formats.get(key);
|
|
2015
|
+
if (!formatter) {
|
|
2016
|
+
formatter = new Intl.NumberFormat(locale, sortedOptions);
|
|
2017
|
+
this.formats.set(key, formatter);
|
|
2018
|
+
}
|
|
2019
|
+
return formatter.format(num);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/**
|
|
2024
|
+
* Capacitor native implementation utilizing mobile system services to resolve settings.
|
|
2025
|
+
* Relies on `@capacitor/device` plugin.
|
|
2026
|
+
*
|
|
2027
|
+
* *Note: Class name spelling maintained as `CacacitorLocale` to preserve compatibility with existing references.*
|
|
2028
|
+
*/
|
|
2029
|
+
class CacacitorLocale {
|
|
2030
|
+
/**
|
|
2031
|
+
* Resolves language code on mobile system settings.
|
|
2032
|
+
* @returns E.g. "en" or "my".
|
|
2033
|
+
*/
|
|
2034
|
+
async getDeviceLocale() {
|
|
2035
|
+
const info = await Device$1.getLanguageCode();
|
|
2036
|
+
return info.value; // e.g. "en", "zh"
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* Electron desktop local shell locale resolver.
|
|
2041
|
+
* Currently serves as a feature placeholder.
|
|
2042
|
+
*/
|
|
2043
|
+
class ElectronLocale {
|
|
2044
|
+
/**
|
|
2045
|
+
* Resolves language code on desktop settings.
|
|
2046
|
+
*/
|
|
2047
|
+
async getDeviceLocale() {
|
|
2048
|
+
throw new Error("Method not implemented.");
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
/**
|
|
2052
|
+
* Web browser native environment locale resolver.
|
|
2053
|
+
* Inspects default HTML5 `navigator.language` parameters.
|
|
2054
|
+
*/
|
|
2055
|
+
class WebLocale {
|
|
2056
|
+
/**
|
|
2057
|
+
* Resolves browser-default language code.
|
|
2058
|
+
*/
|
|
2059
|
+
async getDeviceLocale() {
|
|
2060
|
+
return Promise.resolve(navigator.language);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Singleton service wrapper and router for managing, retrieving, and query-caching system locale settings.
|
|
2065
|
+
*/
|
|
2066
|
+
class SystemLocale {
|
|
2067
|
+
static _instance;
|
|
2068
|
+
/**
|
|
2069
|
+
* Accesses the active global, platform-resolved singleton locale service instance.
|
|
2070
|
+
*/
|
|
2071
|
+
static get instance() {
|
|
2072
|
+
if (Platforms.isMobile()) {
|
|
2073
|
+
this._instance = new CacacitorLocale();
|
|
2074
|
+
}
|
|
2075
|
+
else if (Platforms.isDesktop()) {
|
|
2076
|
+
this._instance = new ElectronLocale();
|
|
2077
|
+
}
|
|
2078
|
+
else {
|
|
2079
|
+
this._instance = new WebLocale();
|
|
2080
|
+
}
|
|
2081
|
+
return this._instance;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
/**
|
|
2086
|
+
* Classifies device performance capabilities into low, mid, or high tiers.
|
|
2087
|
+
* Used for dynamic feature gating and layout optimization.
|
|
2088
|
+
*/
|
|
2089
|
+
class DeviceClassDetector {
|
|
2090
|
+
static _instance;
|
|
2091
|
+
/**
|
|
2092
|
+
* Singleton instance of the DeviceClassDetector.
|
|
2093
|
+
*/
|
|
2094
|
+
static get instance() {
|
|
2095
|
+
if (!DeviceClassDetector._instance) {
|
|
2096
|
+
DeviceClassDetector._instance = new DeviceClassDetector();
|
|
2097
|
+
}
|
|
2098
|
+
return DeviceClassDetector._instance;
|
|
2099
|
+
}
|
|
2100
|
+
/**
|
|
2101
|
+
* Detects the performance tier of the current device.
|
|
2102
|
+
* Leverages RAM memory parameters, CPU core counts, and network speed signals.
|
|
2103
|
+
* @returns A promise resolving to "low", "mid", "high", or "unknown" tier.
|
|
2104
|
+
*/
|
|
2105
|
+
async detect() {
|
|
2106
|
+
const memory = await System.getDeviceMemory(); // GB
|
|
2107
|
+
const cores = await System.getCPUCoreCount(); // number
|
|
2108
|
+
const connection = System.getNetworkType(); // "4g" | "3g" | etc
|
|
2109
|
+
if (!memory && !cores && !connection) {
|
|
2110
|
+
return "unknown";
|
|
2111
|
+
}
|
|
2112
|
+
let score = 0;
|
|
2113
|
+
let signals = 0;
|
|
2114
|
+
// RAM
|
|
2115
|
+
if (memory !== undefined && memory !== null) {
|
|
2116
|
+
signals++;
|
|
2117
|
+
if (memory <= 2)
|
|
2118
|
+
score += 0;
|
|
2119
|
+
else if (memory <= 4)
|
|
2120
|
+
score += 1;
|
|
2121
|
+
else
|
|
2122
|
+
score += 2;
|
|
2123
|
+
}
|
|
2124
|
+
// CPU
|
|
2125
|
+
if (cores !== undefined && cores !== null) {
|
|
2126
|
+
signals++;
|
|
2127
|
+
if (cores <= 4)
|
|
2128
|
+
score += 0;
|
|
2129
|
+
else if (cores <= 8)
|
|
2130
|
+
score += 1;
|
|
2131
|
+
else
|
|
2132
|
+
score += 2;
|
|
2133
|
+
}
|
|
2134
|
+
// Network
|
|
2135
|
+
if (connection !== undefined && connection !== null) {
|
|
2136
|
+
signals++;
|
|
2137
|
+
if (connection === "slow-2g" || connection === "2g")
|
|
2138
|
+
score += 0;
|
|
2139
|
+
else if (connection === "3g")
|
|
2140
|
+
score += 1;
|
|
2141
|
+
else
|
|
2142
|
+
score += 2;
|
|
2143
|
+
}
|
|
2144
|
+
if (signals <= 1) {
|
|
2145
|
+
return "unknown";
|
|
2146
|
+
}
|
|
2147
|
+
if (score <= 2)
|
|
2148
|
+
return "low";
|
|
2149
|
+
if (score <= 4)
|
|
2150
|
+
return "mid";
|
|
2151
|
+
return "high";
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
class WebOrientation {
|
|
2156
|
+
handler;
|
|
2157
|
+
start(callback) {
|
|
2158
|
+
this.handler = (event) => {
|
|
2159
|
+
callback({ alpha: event.alpha, beta: event.beta, gamma: event.gamma });
|
|
2160
|
+
};
|
|
2161
|
+
window.addEventListener('deviceorientation', this.handler);
|
|
2162
|
+
}
|
|
2163
|
+
stop() {
|
|
2164
|
+
if (this.handler) {
|
|
2165
|
+
window.removeEventListener('deviceorientation', this.handler);
|
|
2166
|
+
this.handler = undefined;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
async requestOrientationPermission() {
|
|
2170
|
+
if (typeof DeviceOrientationEvent !== "undefined" &&
|
|
2171
|
+
typeof DeviceOrientationEvent.requestPermission === "function") {
|
|
2172
|
+
try {
|
|
2173
|
+
const response = await DeviceOrientationEvent.requestPermission();
|
|
2174
|
+
return response === "granted";
|
|
2175
|
+
}
|
|
2176
|
+
catch {
|
|
2177
|
+
return false;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
// Non-iOS browsers
|
|
2181
|
+
return true;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
class CapacitorOrientation extends WebOrientation {
|
|
2185
|
+
}
|
|
2186
|
+
class ElectronOrientation extends WebOrientation {
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Cross-platform Motion Orientation helper class that yields platform-specific trackers.
|
|
2190
|
+
*/
|
|
2191
|
+
class MotionOrientation {
|
|
2192
|
+
/**
|
|
2193
|
+
* Creates and returns a new instance of IMotionOrientation optimized for the current platform.
|
|
2194
|
+
* @returns A platform-specific instance of IMotionOrientation.
|
|
2195
|
+
*/
|
|
2196
|
+
static getNewinstance() {
|
|
2197
|
+
if (Platforms.isMobile()) {
|
|
2198
|
+
return new CapacitorOrientation();
|
|
2199
|
+
}
|
|
2200
|
+
else if (Platforms.isDesktop()) {
|
|
2201
|
+
return new ElectronOrientation();
|
|
2202
|
+
}
|
|
2203
|
+
else {
|
|
2204
|
+
return new WebOrientation();
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
/**
|
|
2210
|
+
* Core utility class that leverages continuous dynamic device gyroscope and accelerometer sensors
|
|
2211
|
+
* to predict if the user is holding their phone in their left or right hand.
|
|
2212
|
+
*/
|
|
2213
|
+
class UserHandBehaviorDetector {
|
|
2214
|
+
hand = "unknown";
|
|
2215
|
+
started = false;
|
|
2216
|
+
samples = [];
|
|
2217
|
+
SAMPLE_SIZE = 25;
|
|
2218
|
+
TILT_THRESHOLD = 10; // degrees
|
|
2219
|
+
CHANGE_DELAY = 500;
|
|
2220
|
+
lastReportedHand = "unknown";
|
|
2221
|
+
pendingHand = "unknown";
|
|
2222
|
+
changeTimer;
|
|
2223
|
+
motionsOrientation;
|
|
2224
|
+
isInitial = true;
|
|
2225
|
+
/**
|
|
2226
|
+
* Dynamic event listener callback called when holding posture matches change thresholds.
|
|
2227
|
+
*/
|
|
2228
|
+
onChange;
|
|
2229
|
+
/**
|
|
2230
|
+
* Private constructor initializing standard sensor listeners and checking platform permissions.
|
|
2231
|
+
*/
|
|
2232
|
+
constructor() {
|
|
2233
|
+
this.motionsOrientation = MotionOrientation.getNewinstance();
|
|
2234
|
+
this.motionsOrientation.requestOrientationPermission();
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Factory instantiator method to construct new instances of `UserHandBehaviorDetector`.
|
|
2238
|
+
*/
|
|
2239
|
+
static getNewInstance() {
|
|
2240
|
+
return new UserHandBehaviorDetector();
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Activates continuous data tracking and registers the initial posture feedback callback.
|
|
2244
|
+
* @param callback Callback receiving left/right hand classifications.
|
|
2245
|
+
*/
|
|
2246
|
+
start(callback) {
|
|
2247
|
+
if (this.started)
|
|
2248
|
+
return;
|
|
2249
|
+
this.started = true;
|
|
2250
|
+
this.onChange = callback;
|
|
2251
|
+
// Subscribe to Motion orientation events
|
|
2252
|
+
this.motionsOrientation.start(this.handleOrientation);
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Terminates continuous gyroscope updates and resets buffers.
|
|
2256
|
+
*/
|
|
2257
|
+
stop() {
|
|
2258
|
+
if (!this.started)
|
|
2259
|
+
return;
|
|
2260
|
+
this.started = false;
|
|
2261
|
+
this.motionsOrientation.stop();
|
|
2262
|
+
this.samples = [];
|
|
2263
|
+
this.hand = "unknown";
|
|
2264
|
+
this.onChange = undefined;
|
|
2265
|
+
this.lastReportedHand = "unknown";
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Resolves the current holding hand prediction.
|
|
2269
|
+
*/
|
|
2270
|
+
getHand() {
|
|
2271
|
+
return this.hand;
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Sets the classification confirmation delay.
|
|
2275
|
+
* @param delay Confirmation threshold timing in milliseconds.
|
|
2276
|
+
*/
|
|
2277
|
+
setDelay(delay) {
|
|
2278
|
+
this.CHANGE_DELAY = delay;
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Internally flags classification match transitions and dispatches event listeners.
|
|
2282
|
+
*/
|
|
2283
|
+
confirmHandChange(hand) {
|
|
2284
|
+
this.hand = hand;
|
|
2285
|
+
this.lastReportedHand = hand;
|
|
2286
|
+
if (this.onChange) {
|
|
2287
|
+
this.onChange(hand);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Throttles hand-holding prediction shifts to prevent rapid toggling noise.
|
|
2292
|
+
*/
|
|
2293
|
+
scheduleChange(hand) {
|
|
2294
|
+
if (this.pendingHand === hand)
|
|
2295
|
+
return;
|
|
2296
|
+
this.pendingHand = hand;
|
|
2297
|
+
if (this.changeTimer) {
|
|
2298
|
+
clearTimeout(this.changeTimer);
|
|
2299
|
+
}
|
|
2300
|
+
if (this.isInitial) {
|
|
2301
|
+
this.confirmHandChange(this.pendingHand);
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
this.changeTimer = window.setTimeout(() => {
|
|
2305
|
+
if (this.pendingHand !== this.lastReportedHand) {
|
|
2306
|
+
this.confirmHandChange(this.pendingHand);
|
|
2307
|
+
}
|
|
2308
|
+
}, this.CHANGE_DELAY);
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Continuous gyroscope feed event reader.
|
|
2312
|
+
* Computes orientation averages and applies geometric thresholds to resolve holding fingers/thumb angles.
|
|
2313
|
+
*/
|
|
2314
|
+
handleOrientation = ({ alpha, beta, gamma }) => {
|
|
2315
|
+
if (!this.started) {
|
|
2316
|
+
console.log('stopped returned');
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
if (gamma == null || beta == null)
|
|
2320
|
+
return;
|
|
2321
|
+
// Adjust for upside-down or flipped phone
|
|
2322
|
+
let adjustedGamma = gamma;
|
|
2323
|
+
let adjustedBeta = beta;
|
|
2324
|
+
if (beta > 90 || beta < -90) {
|
|
2325
|
+
adjustedGamma = -gamma;
|
|
2326
|
+
adjustedBeta = beta > 0 ? beta - 180 : beta + 180;
|
|
2327
|
+
}
|
|
2328
|
+
// Add to sample buffer
|
|
2329
|
+
this.samples.push({ gamma: adjustedGamma, beta: adjustedBeta });
|
|
2330
|
+
if (this.samples.length > this.SAMPLE_SIZE)
|
|
2331
|
+
this.samples.shift();
|
|
2332
|
+
// Compute average gamma and beta for smoothing
|
|
2333
|
+
const avgGamma = this.samples.reduce((sum, s) => sum + s.gamma, 0) / this.samples.length;
|
|
2334
|
+
const avgBeta = this.samples.reduce((sum, s) => sum + s.beta, 0) / this.samples.length;
|
|
2335
|
+
// Hand detection thresholds
|
|
2336
|
+
const ROLL_THRESHOLD = 2; // sensitive left/right tilt
|
|
2337
|
+
const FORWARD_TILT_MIN = 10; // minimum forward tilt to consider
|
|
2338
|
+
const FORWARD_TILT_MAX = 80; // allow lying down / sleeping positions
|
|
2339
|
+
let newHand = "unknown";
|
|
2340
|
+
// Upright / forward-tilted detection
|
|
2341
|
+
if (Math.abs(avgBeta) >= FORWARD_TILT_MIN && Math.abs(avgBeta) <= FORWARD_TILT_MAX) {
|
|
2342
|
+
if (avgGamma > ROLL_THRESHOLD)
|
|
2343
|
+
newHand = "left";
|
|
2344
|
+
else if (avgGamma < -ROLL_THRESHOLD)
|
|
2345
|
+
newHand = "right";
|
|
2346
|
+
}
|
|
2347
|
+
// Optional: horizontal / lying down detection
|
|
2348
|
+
else if (Math.abs(avgBeta) < FORWARD_TILT_MIN) {
|
|
2349
|
+
if (avgGamma > 15)
|
|
2350
|
+
newHand = "left";
|
|
2351
|
+
else if (avgGamma < -15)
|
|
2352
|
+
newHand = "right";
|
|
2353
|
+
}
|
|
2354
|
+
// Trigger onChange only if hand side changed
|
|
2355
|
+
if (newHand !== this.lastReportedHand) {
|
|
2356
|
+
this.scheduleChange(newHand);
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
class WebGyroscope {
|
|
2362
|
+
handler;
|
|
2363
|
+
start(callback) {
|
|
2364
|
+
this.handler = (event) => {
|
|
2365
|
+
if (event.rotationRate) {
|
|
2366
|
+
callback({ alpha: event.rotationRate.alpha, beta: event.rotationRate.beta, gamma: event.rotationRate.gamma });
|
|
2367
|
+
}
|
|
2368
|
+
};
|
|
2369
|
+
window.addEventListener('devicemotion', this.handler, true);
|
|
2370
|
+
}
|
|
2371
|
+
stop() {
|
|
2372
|
+
if (this.handler) {
|
|
2373
|
+
window.removeEventListener('devicemotion', this.handler);
|
|
2374
|
+
this.handler = undefined;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
class CapacitorGyroscope extends WebGyroscope {
|
|
2379
|
+
}
|
|
2380
|
+
class ElectronGyroscope extends WebGyroscope {
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Cross-platform Gyroscope sensor tracking utility that delegates angular velocity queries
|
|
2384
|
+
* to native Capacitor plugins, Electron, or Web DeviceOrientation APIs depending on the environment.
|
|
2385
|
+
*/
|
|
2386
|
+
class Gryoscope {
|
|
2387
|
+
static _instance;
|
|
2388
|
+
impl;
|
|
2389
|
+
constructor() {
|
|
2390
|
+
if (Platforms.isMobile()) {
|
|
2391
|
+
this.impl = new CapacitorGyroscope();
|
|
2392
|
+
}
|
|
2393
|
+
else if (Platforms.isDesktop()) {
|
|
2394
|
+
this.impl = new ElectronGyroscope();
|
|
2395
|
+
}
|
|
2396
|
+
else {
|
|
2397
|
+
this.impl = new WebGyroscope();
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* Singleton instance of the Gyroscope tracker.
|
|
2402
|
+
*/
|
|
2403
|
+
static get instance() {
|
|
2404
|
+
if (!this._instance)
|
|
2405
|
+
this._instance = new Gryoscope();
|
|
2406
|
+
return this._instance;
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Starts listening to gyroscope rotation rate events.
|
|
2410
|
+
* @param callback Callback function receiving rotation speed on alpha, beta, and gamma axes.
|
|
2411
|
+
*/
|
|
2412
|
+
start(callback) {
|
|
2413
|
+
this.impl.start(callback);
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Stops listening to gyroscope events and releases sensor listeners.
|
|
2417
|
+
*/
|
|
2418
|
+
stop() {
|
|
2419
|
+
this.impl.stop();
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
class WebAccelerometer {
|
|
2424
|
+
handler;
|
|
2425
|
+
start(callback) {
|
|
2426
|
+
this.handler = (event) => {
|
|
2427
|
+
if (event.acceleration) {
|
|
2428
|
+
callback({ x: event.acceleration.x, y: event.acceleration.y, z: event.acceleration.z });
|
|
2429
|
+
}
|
|
2430
|
+
};
|
|
2431
|
+
window.addEventListener('devicemotion', this.handler, true);
|
|
2432
|
+
}
|
|
2433
|
+
stop() {
|
|
2434
|
+
if (this.handler) {
|
|
2435
|
+
window.removeEventListener('devicemotion', this.handler);
|
|
2436
|
+
this.handler = undefined;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
class CapacitorAccelerometer extends WebAccelerometer {
|
|
2441
|
+
}
|
|
2442
|
+
class ElectronAccelerometer extends WebAccelerometer {
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Cross-platform Accelerometer tracking utility that delegates sensor reading requests
|
|
2446
|
+
* to native Capacitor modules, Electron, or Web APIs depending on the active platform.
|
|
2447
|
+
*/
|
|
2448
|
+
class Accelerometer {
|
|
2449
|
+
static _instance;
|
|
2450
|
+
impl;
|
|
2451
|
+
constructor() {
|
|
2452
|
+
if (Platforms.isMobile()) {
|
|
2453
|
+
this.impl = new CapacitorAccelerometer();
|
|
2454
|
+
}
|
|
2455
|
+
else if (Platforms.isDesktop()) {
|
|
2456
|
+
this.impl = new ElectronAccelerometer();
|
|
2457
|
+
}
|
|
2458
|
+
else {
|
|
2459
|
+
this.impl = new WebAccelerometer();
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Singleton instance of the Accelerometer tracker.
|
|
2464
|
+
*/
|
|
2465
|
+
static get instance() {
|
|
2466
|
+
if (!this._instance)
|
|
2467
|
+
this._instance = new Accelerometer();
|
|
2468
|
+
return this._instance;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Starts listening to accelerometer sensor data streams.
|
|
2472
|
+
* @param callback Callback function receiving acceleration data on x, y, and z axes (in m/s²).
|
|
2473
|
+
*/
|
|
2474
|
+
start(callback) {
|
|
2475
|
+
this.impl.start(callback);
|
|
2476
|
+
}
|
|
2477
|
+
/**
|
|
2478
|
+
* Stops listening to the accelerometer sensor data streams and cleans up underlying listeners.
|
|
2479
|
+
*/
|
|
2480
|
+
stop() {
|
|
2481
|
+
this.impl.stop();
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
const apis = {};
|
|
2486
|
+
/**
|
|
2487
|
+
* Registry interface for dynamic runtime plugin interoperability and component exporting/importing.
|
|
2488
|
+
* Allows plugins within the modular ERP system to expose functional APIs securely and consume external capabilities dynamically.
|
|
2489
|
+
*/
|
|
2490
|
+
class PluginAPI {
|
|
2491
|
+
/**
|
|
2492
|
+
* Exposes a specific API service from a plugin, registering it within the shared system registry.
|
|
2493
|
+
* Prevents re-definition of keys once registered to ensure runtime immutability and prevent injection attacks.
|
|
2494
|
+
*
|
|
2495
|
+
* @param pluginName The unique namespace name of the exporting plugin.
|
|
2496
|
+
* @param apiName The identifying key representing the exported service.
|
|
2497
|
+
* @param value The functional API surface, class instance, or utility being shared.
|
|
2498
|
+
* @throws Error if the plugin namespace and API key combination is already registered.
|
|
2499
|
+
*/
|
|
2500
|
+
static expose(pluginName, apiName, value) {
|
|
2501
|
+
const key = `${pluginName}|${apiName}`;
|
|
2502
|
+
if (apis[key] !== undefined) {
|
|
2503
|
+
throw new Error(`Plugin "${pluginName}" already exposed API "${apiName}".`);
|
|
2504
|
+
}
|
|
2505
|
+
// Define the property as non-writable and non-configurable to guarantee stability
|
|
2506
|
+
Object.defineProperty(apis, key, {
|
|
2507
|
+
value,
|
|
2508
|
+
writable: false, // cannot overwrite
|
|
2509
|
+
configurable: false, // cannot delete or redefine
|
|
2510
|
+
enumerable: true
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
/**
|
|
2514
|
+
* Resolves and retrieves an exposed API service from another plugin namespace.
|
|
2515
|
+
*
|
|
2516
|
+
* @param pluginName The namespace of the target plugin.
|
|
2517
|
+
* @param apiName The key identifying the requested API surface.
|
|
2518
|
+
* @returns The resolved service instance cast to generic type T.
|
|
2519
|
+
*/
|
|
2520
|
+
static use(pluginName, apiName) {
|
|
2521
|
+
return apis[`${pluginName}|${apiName}`];
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Gets the current raw internal registry map of all exposed APIs.
|
|
2525
|
+
*/
|
|
2526
|
+
static get store() {
|
|
2527
|
+
return apis;
|
|
2528
|
+
}
|
|
2529
|
+
/**
|
|
2530
|
+
* Lists the keys of all registered APIs in the system for inspection and debugging.
|
|
2531
|
+
* @returns An array of API identifiers in the format: "pluginName|apiName".
|
|
2532
|
+
*/
|
|
2533
|
+
static debug() {
|
|
2534
|
+
return Object.keys(apis);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
function uuidToMacAddress(uuid) {
|
|
2539
|
+
const clean = uuid.replace(/[^a-fA-F0-9]/g, "");
|
|
2540
|
+
if (clean.length >= 12) {
|
|
2541
|
+
const part = clean.slice(-12).toLowerCase();
|
|
2542
|
+
return part.match(/.{1,2}/g)?.join(":") || "02:00:00:00:00:00";
|
|
2543
|
+
}
|
|
2544
|
+
return "02:00:00:00:00:00";
|
|
2545
|
+
}
|
|
2546
|
+
function generateUUIDFallback() {
|
|
2547
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
2548
|
+
const r = (Math.random() * 16) | 0;
|
|
2549
|
+
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
2550
|
+
return v.toString(16);
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
class CapacitorDevice {
|
|
2554
|
+
async getDeviceId() {
|
|
2555
|
+
try {
|
|
2556
|
+
const idResult = await Device$1.getId();
|
|
2557
|
+
return idResult.identifier || idResult.uuid;
|
|
2558
|
+
}
|
|
2559
|
+
catch (err) {
|
|
2560
|
+
console.warn("Failed to get Capacitor Device ID:", err);
|
|
2561
|
+
let localId = localStorage.getItem("capacitor_device_id");
|
|
2562
|
+
if (!localId) {
|
|
2563
|
+
localId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : generateUUIDFallback();
|
|
2564
|
+
localStorage.setItem("capacitor_device_id", localId);
|
|
2565
|
+
}
|
|
2566
|
+
return localId;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
async getMacAddress() {
|
|
2570
|
+
const deviceId = await this.getDeviceId();
|
|
2571
|
+
return uuidToMacAddress(deviceId);
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
class ElectronDevice {
|
|
2575
|
+
async getDeviceId() {
|
|
2576
|
+
let localId = localStorage.getItem("desktop_device_id");
|
|
2577
|
+
if (!localId) {
|
|
2578
|
+
try {
|
|
2579
|
+
const mac = await this.getMacAddress();
|
|
2580
|
+
if (mac && mac !== "02:00:00:00:00:00") {
|
|
2581
|
+
localId = mac;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
catch (e) {
|
|
2585
|
+
// Ignore
|
|
2586
|
+
}
|
|
2587
|
+
if (!localId) {
|
|
2588
|
+
localId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : generateUUIDFallback();
|
|
2589
|
+
}
|
|
2590
|
+
localStorage.setItem("desktop_device_id", localId);
|
|
2591
|
+
}
|
|
2592
|
+
return localId;
|
|
2593
|
+
}
|
|
2594
|
+
async getMacAddress() {
|
|
2595
|
+
try {
|
|
2596
|
+
const info = await window.electron?.getSystemInfo();
|
|
2597
|
+
if (info && info.networkInterfaces) {
|
|
2598
|
+
const interfaces = info.networkInterfaces;
|
|
2599
|
+
for (const name of Object.keys(interfaces)) {
|
|
2600
|
+
const list = interfaces[name];
|
|
2601
|
+
if (Array.isArray(list)) {
|
|
2602
|
+
for (const iface of list) {
|
|
2603
|
+
if (iface && iface.mac && !iface.internal && iface.mac !== "00:00:00:00:00:00") {
|
|
2604
|
+
return iface.mac;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
catch (err) {
|
|
2612
|
+
console.warn("Failed to get MAC address from Electron system info:", err);
|
|
2613
|
+
}
|
|
2614
|
+
// Fallback to deriving from Device ID if no network interface is found
|
|
2615
|
+
try {
|
|
2616
|
+
let localId = localStorage.getItem("desktop_device_id");
|
|
2617
|
+
if (!localId) {
|
|
2618
|
+
localId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : generateUUIDFallback();
|
|
2619
|
+
localStorage.setItem("desktop_device_id", localId);
|
|
2620
|
+
}
|
|
2621
|
+
return uuidToMacAddress(localId);
|
|
2622
|
+
}
|
|
2623
|
+
catch (err) {
|
|
2624
|
+
return "02:00:00:00:00:00";
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
class WebDevice {
|
|
2629
|
+
async getDeviceId() {
|
|
2630
|
+
let localId = localStorage.getItem("web_device_id");
|
|
2631
|
+
if (!localId) {
|
|
2632
|
+
localId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : generateUUIDFallback();
|
|
2633
|
+
localStorage.setItem("web_device_id", localId);
|
|
2634
|
+
}
|
|
2635
|
+
return localId;
|
|
2636
|
+
}
|
|
2637
|
+
async getMacAddress() {
|
|
2638
|
+
const deviceId = await this.getDeviceId();
|
|
2639
|
+
return uuidToMacAddress(deviceId);
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Cross-platform Device utility that delegates unique hardware identity and address querying
|
|
2644
|
+
* to native Capacitor modules, Electron system layers, or persistent Web local storage keys.
|
|
2645
|
+
*/
|
|
2646
|
+
class Device {
|
|
2647
|
+
impl;
|
|
2648
|
+
static _instance;
|
|
2649
|
+
constructor() {
|
|
2650
|
+
if (Platforms.isMobile()) {
|
|
2651
|
+
this.impl = new CapacitorDevice();
|
|
2652
|
+
}
|
|
2653
|
+
else if (Platforms.isDesktop()) {
|
|
2654
|
+
this.impl = new ElectronDevice();
|
|
2655
|
+
}
|
|
2656
|
+
else {
|
|
2657
|
+
this.impl = new WebDevice();
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
/**
|
|
2661
|
+
* Singleton instance of the Device utility.
|
|
2662
|
+
*/
|
|
2663
|
+
static get instance() {
|
|
2664
|
+
if (!Device._instance) {
|
|
2665
|
+
Device._instance = new Device();
|
|
2666
|
+
}
|
|
2667
|
+
return Device._instance;
|
|
2668
|
+
}
|
|
2669
|
+
/**
|
|
2670
|
+
* Retrieves the physical network interface MAC address of the host machine (desktop)
|
|
2671
|
+
* or a derived stable custom MAC identifier matching the hardware device profile.
|
|
2672
|
+
* @returns A promise resolving to the MAC address string.
|
|
2673
|
+
*/
|
|
2674
|
+
getMacAddress() {
|
|
2675
|
+
return this.impl.getMacAddress();
|
|
2676
|
+
}
|
|
2677
|
+
/**
|
|
2678
|
+
* Retrieves a platform-specific persistent unique device identifier.
|
|
2679
|
+
* @returns A promise resolving to the device ID string.
|
|
2680
|
+
*/
|
|
2681
|
+
getDeviceId() {
|
|
2682
|
+
return this.impl.getDeviceId();
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
/**
|
|
2687
|
+
* Generates a highly unique, stable client fingerprint based on canvas pixel drawings
|
|
2688
|
+
* and key browser navigator settings to serve as an offline-first fallback.
|
|
2689
|
+
* @returns A positive hexadecimal hash representing the device metrics signature.
|
|
2690
|
+
*/
|
|
2691
|
+
function getLocalBrowserFingerprint() {
|
|
2692
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
2693
|
+
return "server-fingerprint";
|
|
2694
|
+
}
|
|
2695
|
+
const parts = [
|
|
2696
|
+
navigator.userAgent || "",
|
|
2697
|
+
navigator.language || "",
|
|
2698
|
+
navigator.deviceMemory || "",
|
|
2699
|
+
navigator.hardwareConcurrency || "",
|
|
2700
|
+
typeof screen !== "undefined" ? screen.width : "",
|
|
2701
|
+
typeof screen !== "undefined" ? screen.height : "",
|
|
2702
|
+
typeof screen !== "undefined" ? screen.colorDepth : "",
|
|
2703
|
+
new Date().getTimezoneOffset(),
|
|
2704
|
+
typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : "",
|
|
2705
|
+
];
|
|
2706
|
+
// Canvas fingerprint
|
|
2707
|
+
try {
|
|
2708
|
+
const canvas = document.createElement("canvas");
|
|
2709
|
+
const ctx = canvas.getContext("2d");
|
|
2710
|
+
if (ctx) {
|
|
2711
|
+
canvas.width = 200;
|
|
2712
|
+
canvas.height = 50;
|
|
2713
|
+
ctx.textBaseline = "alphabetic";
|
|
2714
|
+
ctx.fillStyle = "#f60";
|
|
2715
|
+
ctx.fillRect(125, 1, 62, 20);
|
|
2716
|
+
ctx.fillStyle = "#069";
|
|
2717
|
+
ctx.fillText("Quan ERP Fingerprint", 2, 15);
|
|
2718
|
+
ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
|
|
2719
|
+
ctx.fillText("Quan ERP Fingerprint", 4, 17);
|
|
2720
|
+
parts.push(canvas.toDataURL());
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
catch (e) {
|
|
2724
|
+
// ignore canvas failures
|
|
2725
|
+
}
|
|
2726
|
+
const str = parts.join("|||");
|
|
2727
|
+
let hash = 2166136261;
|
|
2728
|
+
for (let i = 0; i < str.length; i++) {
|
|
2729
|
+
hash ^= str.charCodeAt(i);
|
|
2730
|
+
hash = Math.imul(hash, 16777619);
|
|
2731
|
+
}
|
|
2732
|
+
return (hash >>> 0).toString(16);
|
|
2733
|
+
}
|
|
2734
|
+
class CapacitorDeviceFingerprint {
|
|
2735
|
+
async getFingerprint() {
|
|
2736
|
+
try {
|
|
2737
|
+
const fp = await FingerprintJS.load();
|
|
2738
|
+
const result = await fp.get();
|
|
2739
|
+
return result.visitorId;
|
|
2740
|
+
}
|
|
2741
|
+
catch (err) {
|
|
2742
|
+
// Fallback to local browser fingerprint combined with device ID
|
|
2743
|
+
try {
|
|
2744
|
+
const deviceId = await Device.instance.getDeviceId();
|
|
2745
|
+
return `cap-${deviceId}-${getLocalBrowserFingerprint()}`;
|
|
2746
|
+
}
|
|
2747
|
+
catch (fallbackErr) {
|
|
2748
|
+
return `cap-fallback-${getLocalBrowserFingerprint()}`;
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
class ElectronDeviceFingerprint {
|
|
2754
|
+
async getFingerprint() {
|
|
2755
|
+
// For desktop, retrieve the MAC address from Device singleton
|
|
2756
|
+
try {
|
|
2757
|
+
const mac = await Device.instance.getMacAddress();
|
|
2758
|
+
if (mac && mac !== "02:00:00:00:00:00") {
|
|
2759
|
+
return mac;
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
catch (err) {
|
|
2763
|
+
console.warn("Failed to get desktop MAC address fingerprint:", err);
|
|
2764
|
+
}
|
|
2765
|
+
// Fallback to local desktop device ID
|
|
2766
|
+
try {
|
|
2767
|
+
return await Device.instance.getDeviceId();
|
|
2768
|
+
}
|
|
2769
|
+
catch (err) {
|
|
2770
|
+
return `desktop-fallback-${getLocalBrowserFingerprint()}`;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
class WebDeviceFingerprint {
|
|
2775
|
+
async getFingerprint() {
|
|
2776
|
+
try {
|
|
2777
|
+
const fp = await FingerprintJS.load();
|
|
2778
|
+
const result = await fp.get();
|
|
2779
|
+
return result.visitorId;
|
|
2780
|
+
}
|
|
2781
|
+
catch (err) {
|
|
2782
|
+
// Fallback to local canvas fingerprint combined with local storage ID
|
|
2783
|
+
try {
|
|
2784
|
+
const deviceId = await Device.instance.getDeviceId();
|
|
2785
|
+
return `web-${deviceId}-${getLocalBrowserFingerprint()}`;
|
|
2786
|
+
}
|
|
2787
|
+
catch (fallbackErr) {
|
|
2788
|
+
return `web-fallback-${getLocalBrowserFingerprint()}`;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
/**
|
|
2794
|
+
* Cross-platform Device Fingerprint tracking utility that delegates fingerprinting queries
|
|
2795
|
+
* to native physical hardware parameters on desktop (e.g. MAC address) and FingerprintJS
|
|
2796
|
+
* npm package on web, PWA, or Capacitor.
|
|
2797
|
+
*/
|
|
2798
|
+
class DeviceFingerprint {
|
|
2799
|
+
impl;
|
|
2800
|
+
static _instance;
|
|
2801
|
+
constructor() {
|
|
2802
|
+
if (Platforms.isDesktop()) {
|
|
2803
|
+
this.impl = new ElectronDeviceFingerprint();
|
|
2804
|
+
}
|
|
2805
|
+
else if (Platforms.isMobile()) {
|
|
2806
|
+
this.impl = new CapacitorDeviceFingerprint();
|
|
2807
|
+
}
|
|
2808
|
+
else {
|
|
2809
|
+
this.impl = new WebDeviceFingerprint();
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
/**
|
|
2813
|
+
* Singleton instance of the DeviceFingerprint utility.
|
|
2814
|
+
*/
|
|
2815
|
+
static get instance() {
|
|
2816
|
+
if (!DeviceFingerprint._instance) {
|
|
2817
|
+
DeviceFingerprint._instance = new DeviceFingerprint();
|
|
2818
|
+
}
|
|
2819
|
+
return DeviceFingerprint._instance;
|
|
2820
|
+
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Retrieves the platform-optimized unique device fingerprint identifier.
|
|
2823
|
+
* @returns A promise resolving to the visitor/device fingerprint string.
|
|
2824
|
+
*/
|
|
2825
|
+
getFingerprint() {
|
|
2826
|
+
return this.impl.getFingerprint();
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
/**
|
|
2831
|
+
* Utility class for parsing, checking, and comparing Semantic Versioning (SemVer) format strings.
|
|
2832
|
+
*/
|
|
2833
|
+
class Version {
|
|
2834
|
+
/**
|
|
2835
|
+
* Compares two semantic version strings to determine their precedence.
|
|
2836
|
+
* Supports basic SemVer standards (`major.minor.patch[-prerelease]`).
|
|
2837
|
+
*
|
|
2838
|
+
* @param version The source version string to compare.
|
|
2839
|
+
* @param withVersion The target version string to compare against.
|
|
2840
|
+
* @returns `1` if the source version is newer, `-1` if the target is newer, or `0` if they are identical.
|
|
2841
|
+
*/
|
|
2842
|
+
static compare(version, withVersion) {
|
|
2843
|
+
// Strip out optional prefixes (like 'v')
|
|
2844
|
+
const cleanA = version.replace(/^v/, '');
|
|
2845
|
+
const cleanB = withVersion.replace(/^v/, '');
|
|
2846
|
+
// Split major, minor, patch and extract numeric tokens
|
|
2847
|
+
const partsA = cleanA.split('-')[0].split('.').map(Number);
|
|
2848
|
+
const partsB = cleanB.split('-')[0].split('.').map(Number);
|
|
2849
|
+
const maxLength = Math.max(partsA.length, partsB.length);
|
|
2850
|
+
for (let i = 0; i < maxLength; i++) {
|
|
2851
|
+
const valA = partsA[i] || 0;
|
|
2852
|
+
const valB = partsB[i] || 0;
|
|
2853
|
+
if (valA > valB)
|
|
2854
|
+
return 1;
|
|
2855
|
+
if (valA < valB)
|
|
2856
|
+
return -1;
|
|
2857
|
+
}
|
|
2858
|
+
// Handle optional pre-release tag comparisons (e.g. "1.0.0-beta.1" vs "1.0.0")
|
|
2859
|
+
const tagA = cleanA.includes('-') ? cleanA.substring(cleanA.indexOf('-') + 1) : '';
|
|
2860
|
+
const tagB = cleanB.includes('-') ? cleanB.substring(cleanB.indexOf('-') + 1) : '';
|
|
2861
|
+
if (tagA && !tagB) {
|
|
2862
|
+
// A has pre-release tag, B does not. Pre-release is mathematically older/lower priority
|
|
2863
|
+
return -1;
|
|
2864
|
+
}
|
|
2865
|
+
if (!tagA && tagB) {
|
|
2866
|
+
// B has pre-release tag, A does not. B is older/lower priority
|
|
2867
|
+
return 1;
|
|
2868
|
+
}
|
|
2869
|
+
if (tagA && tagB) {
|
|
2870
|
+
// Both have pre-release tags, compare lexicographically
|
|
2871
|
+
if (tagA > tagB)
|
|
2872
|
+
return 1;
|
|
2873
|
+
if (tagA < tagB)
|
|
2874
|
+
return -1;
|
|
2875
|
+
}
|
|
2876
|
+
return 0;
|
|
2877
|
+
}
|
|
2878
|
+
/**
|
|
2879
|
+
* Determines whether transitioning from an old version to a new version introduces breaking changes.
|
|
2880
|
+
* Breaking changes are defined as a change in the major version component (e.g., `1.x.x` to `2.x.x`).
|
|
2881
|
+
*
|
|
2882
|
+
* @param oldV The existing/old version string.
|
|
2883
|
+
* @param newV The incoming/new version string.
|
|
2884
|
+
* @returns `true` if a major upgrade bump occurs, `false` otherwise.
|
|
2885
|
+
*/
|
|
2886
|
+
static isBreakingChange(oldV, newV) {
|
|
2887
|
+
const [oldMajor] = oldV.split('.').map(Number);
|
|
2888
|
+
const [newMajor] = newV.split('.').map(Number);
|
|
2889
|
+
return newMajor > oldMajor;
|
|
2890
|
+
}
|
|
2891
|
+
/**
|
|
2892
|
+
* Performs a comprehensive version shift analysis, providing flag indicators.
|
|
2893
|
+
*
|
|
2894
|
+
* @param oldV The existing reference version.
|
|
2895
|
+
* @param newV The target version to analyze.
|
|
2896
|
+
* @returns A structured result state describing the upgrade relationship.
|
|
2897
|
+
*/
|
|
2898
|
+
static analyzeVersion(oldV, newV) {
|
|
2899
|
+
const comparedResult = Version.compare(oldV, newV);
|
|
2900
|
+
return {
|
|
2901
|
+
isNewer: comparedResult === -1,
|
|
2902
|
+
isOlder: comparedResult === 1,
|
|
2903
|
+
isSame: comparedResult === 0,
|
|
2904
|
+
isBreaking: Version.isBreakingChange(oldV, newV)
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
class ResponseDto {
|
|
2910
|
+
status;
|
|
2911
|
+
message;
|
|
2912
|
+
payload;
|
|
2913
|
+
}
|
|
2914
|
+
class RequestDto {
|
|
2915
|
+
payload;
|
|
2916
|
+
constructor(payload) {
|
|
2917
|
+
this.payload = payload;
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
export { Accelerometer, Battery, Bluetooth, Camera, CapacitorAccelerometer, CapacitorBluetooth, CapacitorCamera, CapacitorClipboard, CapacitorFileSystem, CapacitorGyroscope, CapacitorLocation, CapacitorNotifications, Clipboard, Database, Device, DeviceClassDetector, DeviceFingerprint, ElectronAccelerometer, ElectronBluetooth, ElectronCamera, ElectronClipboard, ElectronFileSystem, ElectronGyroscope, ElectronLocation, ElectronMicrophone, ElectronNotifications, ElectronPrinter, ElectronSystem, ElectronVibration, FileSystem, Gryoscope, Location, Microphone, MobilePrinter, MotionOrientation, Network, Notifications, NumberFormatter, Orientation, Permissions, Platforms, PluginAPI, PluginAssets, Printer, RequestDto, ResponseDto, SQLitePlaceholder, System, SystemLocale, UserHandBehaviorDetector, Version, Vibration, WebAccelerometer, WebBluetooth, WebCamera, WebClipboard, WebFileSystem, WebGyroscope, WebLocation, WebNotifications, WebPrinter, WebVibration, getBackendAPI, setBackendAPI };
|