All files / src/PerfectWSAdvanced/transform TransformCallbacks.ts

100% Statements 68/68
100% Branches 41/41
100% Functions 9/9
100% Lines 68/68

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 862x 2x   2x   2x 1322x 1322x   1322x 1322x 1322x   1322x 1322x 330x   171x 330x 1x 1x 1x   168x 168x 164x 330x 4x 4x 1322x   1322x 333x   163x 163x   329x 2x 329x 159x 159x   161x 1322x 1322x   1322x 2914x 59x 160x 160x   160x 160x 160x 160x   59x 59x 2914x 2914x   1322x 3016x 3016x 3016x 3016x 3016x 3016x   1322x 14881x 14791x 14791x   11911x 14881x 84x 84x 84x   90x 14881x 1322x  
import { transformSendDeserializeType, transformSendRecursive } from './utils/changeType.js';
import { v4 as uuid } from 'uuid';
import { NetworkEventListener } from '../../utils/NetworkEventListener.js';
import { PerfectWSError } from '../../PerfectWSError.js';
 
export class TransformCallbacks {
    private _functions: [string, any][] = [];
    private _activeRequests = new Map<string, { resolve: (data: any) => void, reject: (error: any) => void; }>();
 
    constructor(private _events: NetworkEventListener, private _maxDepth: number = 100) {
        this._registerEvents();
    }
 
    private _registerEvents() {
        this._events.on('___callback.request', async (source, { args, funcId, requestId }) => {
            if (source === 'local') return;
 
            const func = this._functions.find(([localFuncId]) => localFuncId === funcId)?.[1];
            if (!func) {
                this._events.emit('___callback.response', { error: 'Method not found', requestId });
                return;
            }
 
            try {
                const response = await func(...args);
                this._events.emit('___callback.response', { data: response, requestId });
            } catch (error: any) {
                this._events.emit('___callback.response', { error: error.message || error, requestId });
            }
        });
 
        this._events.on('___callback.response', (source, { data, error, requestId }) => {
            if (source === 'local') return;
 
            const request = this._activeRequests.get(requestId);
            if (!request) return;
 
            if (error) {
                request.reject(new PerfectWSError(error, 'callbackError'));
            } else {
                request.resolve(data);
            }
 
            this._activeRequests.delete(requestId);
        });
    }
 
    deserialize(data: any) {
        return transformSendDeserializeType(data, 'callback', found => {
            const func = (...args: any[]) => {
                const requestId = uuid();
                this._events.emit('___callback.request', { args, funcId: found.funcId, requestId });
 
                return new Promise((resolve, reject) => {
                    this._activeRequests.set(requestId, { resolve, reject });
                });
            };
 
            Object.defineProperty(func, 'name', { value: found.funcName });
            return func;
        }, this._maxDepth);
    }
 
    serialize(obj: any) {
        return transformSendRecursive(obj, {
            maxDepth: this._maxDepth,
            processingDataType: (data) => typeof data === 'object' && data !== null || typeof data === 'function',
            transformData: (data) => this._serializeFunction(data)
        });
    }
 
    private _serializeFunction(func: any) {
        if(typeof func !== 'function') {
            return null;
        }
 
        let hasFunc = this._functions.find(([_, fn]) => fn === func)?.[0];
        if (!hasFunc) {
            hasFunc = uuid();
            this._functions.push([hasFunc, func]);
        }
 
        return { ___type: 'callback', funcId: hasFunc, funcName: func.name }
    }
}