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 2884x 59x 160x 160x   160x 160x 160x 160x   59x 59x 2884x 2884x   1322x 2986x 2986x 2986x 2986x 2986x 2986x   1322x 14789x 14699x 14699x   11839x 14789x 84x 84x 84x   90x 14789x 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 }
    }
}