All files / modules device.ts

100% Statements 52/52
80% Branches 12/15
100% Functions 5/5
100% Lines 50/50

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 1291x 1x   1x   1x 1x                   1x                   8x                         5x 5x     5x 5x 5x 5x 5x 5x   5x   5x 5x 5x 1x 1x 1x 1x         4x 4x     5x     8x 8x 8x                 6x   6x 6x 6x 1x       6x 5x   5x 5x 4x 2x     2x 1x         1x         6x   5x 3x         6x 5x   5x       6x      
import { Injectable } from '@angular/core';
import FingerprintJS from '@fingerprintjs/fingerprintjs';
 
import { GenericUtils } from '@unpispas/upp-defs';
 
import { adhocService } from './adhoc';
import { storeService } from './store';
 
/**
 * A service that provides device-related functionalities, including generating unique device IDs
 * and managing their storage and retrieval. This service integrates with FingerprintJS for device 
 * fingerprinting and communicates with a server for persistent storage.
 */
@Injectable({
    providedIn: 'root'
})
export class identificationService {
 
    /**
     * Constructor that initializes the platform service with required dependencies and sets up the platform configurations.
     * 
     * @param detector - Device detector service for detecting device-specific information.
     * @param platform - Ionic platform service for managing platform-specific features.
     * @param router - Angular router for navigation.
     * @param route - Activated route for accessing route-specific information.
     */    
    constructor(private adhoc: adhocService, private store: storeService) {
        // nothing to do
    }
 
    /**
     * Generates a unique visitor identifier by combining browser-specific data and a visitor fingerprint.
     * This method utilizes the FingerprintJS library to create a fingerprint for identifying the device.
     * If the library fails, it falls back to an alternative method using browser information.
     * 
     * @returns A string containing a unique visitor ID, formatted as "VISITOR [FingerprintID - BrowserID]".
     *          If FingerprintJS fails, returns only the browser-based identifier.
     */    
    private async VisitorId(): Promise <string> {
        let _fingerprt = "";
        let _browserid = "";    
 
        // https://stackoverflow.com/questions/27247806/generate-unique-id-for-each-device/27249628
        _browserid += window.navigator.mimeTypes.length.toString();
        _browserid += window.navigator.userAgent.replace(/\D+/g, '');
        _browserid += window.navigator.plugins.length;
        _browserid += window.screen.height.toString() || '';
        _browserid += window.screen.width.toString() || '';
        _browserid += window.screen.pixelDepth.toString() || '';
 
        _browserid = "BROWSER" + GenericUtils.CRC32(_browserid).toString(16);
 
        const _promise = FingerprintJS.load();
        try {
            const _fingerprint = await _promise;
            if (_fingerprint){
                const _result = await _fingerprint.get();
                if (_result){
                    _fingerprt = "VISITOR [" + _result.visitorId + " - " + _browserid + "]";
                }
            }
        } 
        catch (error) {
            console.error("[FINGERPRINT] Returned error (falling back to unsecure method)", error );
            _fingerprt = _browserid;
        }    
 
        return _fingerprt;
    }
 
    private _storeddevicekey = 'upp-stored-deviceuuid-v2';
    private _doGetDeviceUrl = 'device/getuuid';
    private _doSetDeviceUrl = 'device/setuuid';
 
    /**
     * Retrieves a unique device ID, either from local storage or the server, or calculates one if none exists.
     * The device ID is then synchronized with the server and stored locally for future use.
     * 
     * @returns A promise that resolves to a string containing the unique device ID.
     */    
    async DeviceId() : Promise <string> {
        const _caculated = await this.VisitorId();
        
        let _stored = false;
        let _deviceid = await this.store.Get(this._storeddevicekey);
        if (_deviceid){
            _stored = true;
        }
 
        // uuid not found in store: recover from server (falls to calculated value)
        if (!_deviceid){
            _deviceid = _caculated;
 
            try {
                const _response = await this.adhoc.DoRequest(this._doGetDeviceUrl, { device: _caculated });
                if (_response['errorcode'] == 0){
                    _deviceid = _response['uuid'];
                }
                else {
                    if (_response['errorcode'] != 3){
                        console.error("[DEVICE ID] GET (uuid) returned errorcode [" +_response['errorcode'] + "]");
                    }
                }
            }      
            catch {
                console.error("[DEVICE ID] GET (uuid) request to '" + this._doGetDeviceUrl + "' failed");      
            }    
        }
 
        // store the obtained deviceid into the server (related to the calculated value)
        this.adhoc.DoRequest(this._doSetDeviceUrl, { calc: _caculated, uuid: _deviceid }).then(
        (_response: any) => {
            if (_response['errorcode'] != 0) {
                console.error("[DEVICE ID] SET (uuid) returned errorcode [" + _response['errorcode'] + "]");
            }
        });        
 
        // update device id in storage (if it was not obtained from storage)
        if (!_stored) {  
            this.store.Set(this._storeddevicekey, _deviceid).then(
            () => {
                console.info("[DEVICE ID] " + _deviceid); 
            });
        } 
        
        return _deviceid;
    }
}