VaKeR CYBER ARMY
Logo of a company Server : Apache/2.4.41 (Ubuntu)
System : Linux absol.cf 5.4.0-198-generic #218-Ubuntu SMP Fri Sep 27 20:18:53 UTC 2024 x86_64
User : www-data ( 33)
PHP Version : 7.4.33
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Directory :  /var/www/html/gadevoir/app/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /var/www/html/gadevoir/app/GPhone.js
/************* POLYFILL ******************/
import * as mqtt from "mqtt";

if (!navigator.getUserMedia) {
    navigator.getUserMedia =
        navigator.getUserMedia ||
        navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia ||
        navigator.msGetUserMedia;
}

/************ABSOL-LIBRARY***************************************/

function mixClass(constructor) {
    var descriptors = {};
    for (var i = 1; i < arguments.length; ++i) {
        Object.assign(descriptors, Object.getOwnPropertyDescriptors(arguments[i].prototype));
    }
    delete descriptors.constructor;
    Object.defineProperties(constructor.prototype, descriptors);
}

function safeThrow(error) {
    setTimeout(function () {
        if (error.stack) {
            try {
                error.message += '\n' + error.stack;
            } catch (e) {
                //can not modify message
            }
        }
        throw error;
    }, 0);
}


function EventEmitter() {
    if (!this._azar_extendEvents) {
        Object.defineProperty(this, '_azar_extendEvents', {
            enumerable: false,
            value: this._azar_extendEvents || { supported: {}, prioritize: {}, nonprioritize: {} }
        });
        Object.defineProperty(this, '__azar_force', {
            value: !(typeof Node === "object" ? this instanceof Node : this && typeof this === "object" && typeof this.nodeType === "number" && typeof this.nodeName === "string"),
            enumerable: false
        });
    }
}


EventEmitter.prototype.defineEvent = function (name) {
    if (name instanceof Array) {
        for (var i = 0; i < name.length; ++i)
            this._azar_extendEvents.supported[name[i]] = true;
    }
    else
        this._azar_extendEvents.supported[name] = true;
    return this;
};

EventEmitter.prototype.isSupportedEvent = function (name) {
    return this.__azar_force || !!this._azar_extendEvents.supported[name];
};


EventEmitter.prototype.emit = function (eventName, data) {
    this.fire.apply(this, arguments);
};

EventEmitter.prototype.fire = function (eventName, data) {
    var others = Array.prototype.slice.call(arguments, 1);
    var listenerList;
    var i;
    var startTime, endTime;
    if (this.isSupportedEvent(eventName)) {
        if (this._azar_extendEvents.prioritize[eventName]) {
            listenerList = this._azar_extendEvents.prioritize[eventName].slice();
            for (i = 0; i < listenerList.length; ++i) {
                try {
                    startTime = Date.now();
                    listenerList[i].wrappedCallback.apply(this, others);
                    endTime = Date.now();
                    if (endTime - startTime > 200) {
                        console.log('slow function call (' + (endTime - startTime) + ')', listenerList[i]);
                    }
                } catch (e) {
                    safeThrow(e);
                }
            }
        }

        if (this._azar_extendEvents.nonprioritize[eventName]) {
            listenerList = this._azar_extendEvents.nonprioritize[eventName].slice();
            for (i = 0; i < listenerList.length; ++i) {
                try {
                    startTime = Date.now();
                    listenerList[i].wrappedCallback.apply(this, others);
                    endTime = Date.now();
                    if (endTime - startTime > 200) {
                        console.log('slow function call (' + (endTime - startTime) + ')', listenerList[i]);
                    }
                } catch (e) {
                    safeThrow(e);
                }
            }
        }
    }
    else {
        if (this.dispatchEvent) {
            var event = new Event(eventName);
            data && Object.assign(event, data);
            this.dispatchEvent(event);
        }
        else
            throw new Error("Not support event " + eventName);
    }
    return this;
};


EventEmitter.prototype.eventEmitterOnWithTime = function (isOnce, arg0, arg1, arg2) {
    if (typeof arg0 == 'object') {
        for (var key in arg0) {
            this.eventEmitterOnWithTime(isOnce, key, arg0[key]);
        }
        return this;
    }
    else {
        if (typeof arg1 == 'object') {
            return this.eventEmitterOnWithTime(isOnce, arg0, arg1.callback, arg1.cap);
        }
        else {
            var eventArr = this._azar_extendEvents[arg2 ? 'prioritize' : 'nonprioritize'][arg0] || [];
            var eventIndex = -1;
            for (var i = 0; i < eventArr.length; ++i) {
                if (eventArr[i].wrappedCallback === arg1) {
                    eventIndex = i;
                    break;
                }
            }
            if (eventIndex < 0) {
                var event = { isOnce: isOnce, eventName: arg0, callback: arg1, cap: !!arg2 };
                //wrappedCallback will be call
                if (isOnce) {
                    event.wrappedCallback = function () {
                        this.off(event.eventName, event.wrappedCallback, event.cap);
                        event.callback.apply(this, arguments);
                    };
                }
                else {
                    event.wrappedCallback = event.callback;
                }

                if (!this.isSupportedEvent(arg0)) {
                    if (this.addEventListener) {
                        this.addEventListener(arg0, event.wrappedCallback, !!arg2);
                    }
                    else {
                        this.attachEvent('on' + arg0, arg1, !!arg2);
                    }
                }

                eventArr.push(event);
                this._azar_extendEvents[arg2 ? 'prioritize' : 'nonprioritize'][arg0] = eventArr;
            }
            else {
                console.warn("duplicate event");
            }

        }
        return this;
    }
};


EventEmitter.prototype.on = function (arg0, arg1, arg2) {
    this.eventEmitterOnWithTime(false, arg0, arg1, arg2);
    return this;
};


EventEmitter.prototype.once = function (arg0, arg1, arg2) {
    this.eventEmitterOnWithTime(true, arg0, arg1, arg2);
    return this;
};

EventEmitter.prototype.off = function (arg0, arg1, arg2) {
    if (typeof arg0 == 'object') {
        for (var key in arg0) {
            this.off(key, arg0[key]);
        }
        return this;
    }
    else {
        if (typeof arg1 == 'object') {
            return this.off(arg0, arg1.callback, arg1.cap);
        }
        else {
            var eventArr = this._azar_extendEvents[arg2 ? 'prioritize' : 'nonprioritize'][arg0] || [];
            var newEventArray = [];
            for (var i = 0; i < eventArr.length; ++i) {
                var event = eventArr[i];
                if (event.wrappedCallback == arg1) {
                    //Dont add to newEventArray
                    if (this.isSupportedEvent(arg0)) {
                    }
                    else {
                        if (this.removeEventListener) {
                            this.removeEventListener(event.eventName, event.wrappedCallback, !!event.cap);
                        }
                        else {
                            this.detachEvent('on' + event.eventName, event.wrappedCallback, !!event.cap);
                        }
                    }
                }
                else {
                    newEventArray.push(event);
                }
            }
            this._azar_extendEvents[arg2 ? 'prioritize' : 'nonprioritize'][arg0] = newEventArray;
            return this;
        }
    }

};


var identCharacters = function () {
    var chars = 'qwertyuiopasdfghjklzxcvbnm';
    chars = chars + chars.toUpperCase();
    var num = '0123456789';
    var spect = '_';
    return (chars + spect + num).split('');

}();


function randomIdent(length) {
    if (!(length > 0)) length = 4;
    var factor = identCharacters;
    return [factor[(Math.random() * (factor.length - 10)) >> 0]].concat(Array(length - 1).fill('').map(function () {
        return factor[(Math.random() * factor.length) >> 0];
    })).join('');
}

/*****************UTILS**********************/
function isObject(obj) {
    if (typeof obj == "object") {
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                return true; // search for first object prop
            }
        }
    }
    return false;
}

function mergeObject(target, add) {
    for (var key in add) {
        if (add.hasOwnProperty(key)) {
            if (target[key] && isObject(target[key]) && isObject(add[key])) {
                mergeObject(target[key], add[key]);
            }
            else {
                target[key] = add[key];
            }
        }
    }
    return target;
}

/********************************************************/
var defaultConfig = {
    media: {
        audio: {
            echoCancellationType: 'system',
            echoCancellation: true,
            noiseSuppression: true,
            // sampleRate:24000,
            // sampleSize:16,
            // channelCount:2,
            volume: 0.5
        },
        video: {
            facingMode: 'user'
        }
    },
    ws: 'wss:mqtt.absol.cf:9884',
    appChannel: 'gadevoir',
    rtc: {
        offerToReceiveAudio: true,
        offerToReceiveVideo: true,
        iceServers: [{
            "urls": "stun:absol.cf:3478"
        }]
    }
};


/***
 * @typedef {{number: string, clientId: string}} GEndPoint
 */

/***
 * @extends EventEmitter
 * @param {GPhone} phone
 * @param {GEndPoint} other
 * @param {string=} id
 * @param {("outgoing"|"income")=} type
 *
 */
function GConversation(phone, other, id, type) {
    EventEmitter.call(this);
    this.type = type || "outgoing";
    this.phone = phone;
    /***
     *
     * @type {GEndPoint}
     */
    this.me = { number: phone.number, clientId: phone.clientId };
    this.id = id || randomIdent(32);
    this.other = other;
    //video=>remote description => ice
    this.localStream = null;
    this.localStreamSync = new Promise(function (resolve) {
        this._localStreamResolveCb = resolve;
    }.bind(this));
    this.remoteStreams = null;
    this.remoteStreamSync = new Promise(function (resolve) {
        this._remoteResolveCb = resolve;
    }.bind(this));

    this.remoteDescSync = new Promise(function (resolve) {
        this._remoteDescResolveCb = resolve;
    }.bind(this));

    // IceCandidate
}

mixClass(GConversation, EventEmitter);

GConversation.prototype.startP2P = function () {
    this.pc = new RTCPeerConnection(this.phone.config.rtc);
    //ontrack not work with webrtc-adapter
    this.pc.addEventListener('track', this.ev_ontrack.bind(this));
    this.pc.addEventListener('icecandidate', this.ev_onicecandidate.bind(this));
};

GConversation.prototype.startVideo = function () {

};

GConversation.prototype.startAudio = function () {
    var thisC = this;
    return navigator.mediaDevices.getUserMedia({ audio: this.phone.config.media.audio }).then(function (stream) {
        thisC.localStream = stream;
        stream.getTracks().forEach(function (track) {
            thisC.pc.addTrack(track, stream);
        });
        thisC._localStreamResolveCb(stream);
        thisC._localStreamResolveCb = null;
    });
};

GConversation.prototype._setupAntiNoise = function () {

};


GConversation.prototype.getLocalStream = function () {
    return this.localStreamSync;
};


GConversation.prototype.getRemoteStreams = function () {
    return this.remoteStreamSync;
};


GConversation.prototype.close = function () {
    this.pc.close();
    if (this.remoteStreams) this.remoteStreams.forEach(function (stream) {
        stream.getTracks().forEach(function (track) {
            track.stop();
        });
    });
    if (this.localStream) this.localStream.getTracks().forEach(function (track) {
        track.stop();
    });
};

GConversation.prototype.setRemoteDescription = function (offer) {
    var thisC = this;
    var pc = this.pc;
    //don't set remoteDescription before audio
    return this.localStreamSync.then(function () {
        return pc.setRemoteDescription(new RTCSessionDescription(offer)).then(function () {
            thisC._remoteDescResolveCb(offer);
            thisC._remoteDescResolveCb = null;
        });
    });
};

GConversation.prototype.getRemoteDescription = function () {
    return this.remoteDescSync;
};

/***
 *
 * @return {Promise<RTCSessionDescriptionInit>}
 */
GConversation.prototype.offer = function () {
    var thisC = this;
    return this.localStreamSync.then(function () {
        return thisC.pc.createOffer();
    }).then(function (offer) {
        return thisC.pc.setLocalDescription(offer)
            .then(function () {
                return offer;
            })
    });
};

/***
 * @returns {Promise<RTCSessionDescriptionInit>}
 */
GConversation.prototype.answer = function () {
    var pc = this.pc;
    return this.remoteDescSync.then(function () {
        if (pc.remoteDescription.type !== "offer") return null;
        return pc.createAnswer()
            .then(function (answer) {
                return pc.setLocalDescription(answer)
                    .then(function () {
                        return answer;
                    });
            });
    });
};

GConversation.prototype.addIceCandidate = function (candidate) {
    var pc = this.pc;
    this.remoteDescSync.then(function () {
        return pc.addIceCandidate(
            new RTCIceCandidate(candidate)
        );
    });
};

GConversation.prototype.ev_ontrack = function (event) {
    var streams = Array.prototype.slice.call(event.streams);
    this.emit('remote_stream', { type: 'remote_stream', streams: streams, target: this }, this);
    this.remoteStreams = streams;

    if (this._remoteResolveCb) {
        this._remoteResolveCb(streams);
        this._remoteResolveCb = null;
    }
};

GConversation.prototype.ev_onicecandidate = function (event) {
    if (event.candidate)
        this.emit('icecandidate', { type: 'icecandidate', candidate: event.candidate, target: this });
};

/**
 * @typedef {Object} GMQTTConfig
 * @property {string} appChannel
 * @property {object} [mqttClient]  - use existed mqtt client or create new one using ws
 * @property {string} [ws]
 * @property {string} clientId
 */


/**
 *
 * @param {GMQTTConfig} config
 * @constructor
 */
function GMQTTNetwork(config) {
    EventEmitter.call(this);
    this.clientId = config.clientId;
    this.userId = config.userId;
    this.appChannel = config.appChannel;
    this.classChannel = this.appChannel + '/gphone';
    this.userChannel = this.classChannel + '/' + this.userId;
    this.clientChannel = this.classChannel + '/' + this.clientId;
    if (config.mqttClient) {
        this.mqttClient = config.mqttClient;
    }
    else if (typeof config.ws === "string") {
        this.mqttClient = mqtt.connect(config.ws, {
            clientId: this.clientId,
            connectTimeout: 100000
        });
    }
    else {
        throw new Error("Invalid config: can not config mqtt");
    }

    this.mqttClient.subscribe([this.classChannel, this.userChannel, this.clientChannel]);
    this.mqttClient.on('connect', this.ev_connect.bind(this));
    this.mqttClient.on('error', this.ev_error.bind(this));
    this.mqttClient.on('message', this.ev_message.bind(this));
}

mixClass(GMQTTNetwork, EventEmitter);

GMQTTNetwork.prototype.ready = function () {
    if (this.mqttClient) return Promise.resolve();
    return new Promise((resolve, reject) => {
        this.mqttClient.once('connect', function () {
            resolve();
        });
        this.mqttClient.once('error', function (error) {
            reject(error);
        });
    });
};

GMQTTNetwork.prototype.ev_connect = function () {

};

GMQTTNetwork.prototype.ev_error = function (error) {

};

GMQTTNetwork.prototype.packagePrefix = 'gadevoir_';

GMQTTNetwork.prototype.packageSubfix = '_gadevoir';

GMQTTNetwork.prototype.ev_message = function (channel, payload) {
    payload = payload + '';
    var json;
    if (payload.startsWith(this.packagePrefix) && payload.endsWith(this.packageSubfix)) {
        payload = payload.substring(this.packagePrefix.length, payload.length - this.packageSubfix.length);
        console.log(payload)

        try {
            json = JSON.parse(payload);
            this.emit('receive_package', json);
        } catch (error) {
            console.error(error);
        }
    }
};


GMQTTNetwork.prototype.sendToUser = function (userId, type, data) {
    var json = JSON.stringify({
        type: type,
        data: data,
        sourceClient: this.clientId,
        sourceUser: this.userId,
        from: this.userId,
        destType: 'user',
        dest: userId
    });
    this.mqttClient.publish(this.classChannel + '/' + userId, this.packagePrefix + json + this.packageSubfix);
};

GMQTTNetwork.prototype.sendToClient = function (client, type, data) {
    var json = JSON.stringify({
        type: type,
        data: data,
        sourceClient: this.clientId,
        sourceUser: this.userId,
        from: this.clientId,
        destType: 'client',
        dest: client
    });
    this.mqttClient.publish(this.classChannel + '/' + client, this.packagePrefix + json + this.packageSubfix);
};

GMQTTNetwork.prototype.sendToClass = function (type, data) {
    var json = JSON.stringify({
        type: type,
        data: data,
        sourceClient: this.clientId,
        sourceUser: this.userId,
        from: this.userId,
        destType: 'class'
    });
    this.mqttClient.publish(this.classChannel, this.packagePrefix + json + this.packageSubfix);
}


function GPhone(config) {
    this.state = "NOT_READY";
    EventEmitter.call(this);
    this.config = mergeObject(defaultConfig, config || {});
    this.config.userId = this.config.userId || localStorage.getItem('gphone_id') || this._randomPhoneNumber();
    this.config.clientId = this.config.clientId || (this.config.userId + '-' + randomIdent(24));
    localStorage.setItem('gphone_id', this.config.userId);

    this.userId = this.config.userId;
    this.clientId = this.config.clientId;

    this.network = new GMQTTNetwork(Object.assign({ userId: this.userId, clientId: this.clientId }, this.config));
    // setup receive package from network
    this.pkgRecv = new EventEmitter();
    this.network.on('receive_package', this.ev_package.bind(this));
    this.pkgRecv.on('dial', this.recv_dial.bind(this));
    this.pkgRecv.on('who_online', this.recv_whoOnline.bind(this));
    this.pkgRecv.on('icecandidate', this.recv_icecandidate.bind(this));
    this.pkgRecv.on('answer', this.recv_answer.bind(this));
    this.pkgRecv.on('hangup', this.recv_hangup.bind(this));


    Object.defineProperty(this, 'number', {
        get: function () {
            return this.userId;
        }
    });

    /***
     *
     * @type {null|GConversation}
     */
    this.conversation = null;
    this.state = "READY";

    this.sync = this.network.ready();

}

mixClass(GPhone, EventEmitter);

GPhone.isSupported = function () {
    return !!window.RTCPeerConnection
        && !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)
        && !!RTCPeerConnection.prototype.createDataChannel
};

GPhone.prototype._randomPhoneNumber = function () {
    return '0908' + Array(6).fill(0).map(function () {
        return Math.floor(Math.random() * 10);
    }).join('');
};


GPhone.prototype.ev_package = function (pkg) {
    this.pkgRecv.emit(pkg.type, pkg);
};


GPhone.prototype.sendToPhone = function (number, type, data) {
    return this.network.sendToUser(number, type, data);

};

GPhone.prototype.sendToClient = function (client, type, data) {
    return this.network.sendToClient(client, type, data);

}


GPhone.prototype.sendToClass = function (type, data, cb) {
    return this.network.sendToClass(type, data, cb);

};

/***
 *
 * @param {string} phoneNumber
 * @param {number} timeout
 * @param {function} onStateChangeCb
 */
GPhone.prototype.dial = function (phoneNumber, timeout, onStateChangeCb) {
    if (this.state !== 'READY') {
        return false;
        // throw  new Error("GPhone is not ready");
    }
    this.state = "OUTGOING_DIAL";
    var responseIdx = -1;
    var answerCount = 0;
    var thisPhone = this;
    var conversation = new GConversation(this, { clientId: "undefined", number: phoneNumber });
    conversation.on('icecandidate', this.ev_icecandidate.bind(this, conversation));
    var id = conversation.id;
    this.conversation = conversation;
    var answerVal = 'NOT_RESPONSE';

    function onDialResponse(res, pkg) {
        if (id !== res.id) return;
        if (responseIdx >= 0) {
            clearTimeout(responseIdx);
        }
        switch (res.answer) {
            case "ACCEPT":
                answerVal = "ACCEPT";
                finish();
                thisPhone.state = "OUTGOING_TRANSMIT";
                conversation.other.clientId = pkg.sourceClient;
                conversation.startP2P();
                conversation.startAudio();
                conversation.setRemoteDescription(res.offer);
                conversation.answer().then(function (answer) {
                    thisPhone.sendToPhone(conversation.other.userId, 'answer', {
                        id: conversation.id,
                        answer: answer,
                        targetClient: pkg.sourceClient
                    });
                });
                onStateChangeCb({ state: "ACCEPT" });
                break;
            case "BUSY":
                if (answerVal === 'NOT_RESPONSE') {
                    answerVal = 'BUSY';
                    responseIdx = setTimeout(finish, 4000);
                }
                break;
            case "DENY":
                answerVal = "DENY";
                onStateChangeCb({ state: "DENY" });
                finish();
                break;
            case "WAITING":
                if (answerVal !== "WAITING") {
                    answerVal = "WAITING";
                    onStateChangeCb({ state: "WAITING" });
                }
                responseIdx = setTimeout(finish, timeout);
                break;
        }
    }


    function finish() {
        responseIdx = -1;
        thisPhone.off('dial_response', onDialResponse);
        switch (answerVal) {
            case "NOT_RESPONSE":
                onStateChangeCb({ state: "NOT_RESPONSE" });
                break;
            case "BUSY":
                onStateChangeCb({ state: "BUSY" });
                break;
            case "WAITING":
                onStateChangeCb({ state: "NOT_ANSWER" });
                break;
        }
        if (answerVal !== 'ACCEPT') {
            thisPhone.state = "READY";
            thisPhone.conversation = null;
        }
    }


    responseIdx = setTimeout(finish, 2000)
    this.sendToPhone(phoneNumber, 'dial', { id: id });
    this.pkgRecv.on('dial_response', onDialResponse);
    return true;
};

GPhone.prototype.recv_dial = function (pkg) {
    var data = pkg.data;
    var clientId = pkg.sourceClient;
    if (this.state === 'READY') {
        this.sendToPhone(clientId, 'dial_response', { answer: "WAITING", id: data.id });
        this.state = "INCOME_DIAL";
        var conversation = new GConversation(this, {
            clientId: pkg.sourceClient,
            number: pkg.sourceUser
        }, data.id, 'income');
        conversation.on('icecandidate', this.ev_icecandidate.bind(this, conversation));
        this.conversation = conversation;
        this.emit('income', { from: pkg.sourceUser, clientId: clientId, conversationId: conversation.id });
    }
    else {
        this.sendToClient(clientId, 'dial_response', { answer: "BUSY" });
    }
};


GPhone.prototype.hangup = function () {
    var conversation = this.conversation;
    if (conversation) {
        switch (this.conversation.type) {
            case "income":
                if (conversation.pc) {
                    this.sendToPhone(this.conversation.other.clientId, 'hangup', {
                        id: conversation.id
                    });
                    conversation.close();
                }
                else {
                    this.sendToClient(this.conversation.other.clientId, 'dial_response', {
                        id: conversation.id,
                        answer: "DENY"
                    });
                }
                break;
            case "outgoing":
                if (conversation.pc) {
                    this.sendToPhone(this.conversation.other.clientId, 'hangup', {
                        id: conversation.id
                    });
                    conversation.close();
                }
                else {
                    this.sendToClient(this.conversation.other.clientId, 'dial_cancel', {
                        id: conversation.id,
                        answer: "DENY"
                    });
                }
                break;
        }

        //do something before remove
        this.conversation = null;
    }
    this.state = "READY";
};

GPhone.prototype.pickup = function () {
    var conversation = this.conversation;
    if (!conversation) return;
    var thisPhone = this;
    if (!conversation.pc) {
        this.state = "INCOME_TRANSMIT";
        conversation.startP2P();
        conversation.startAudio();
        conversation.offer().then(function (offer) {
            return thisPhone.sendToClient(conversation.other.clientId, 'dial_response', {
                answer: "ACCEPT",
                id: conversation.id,
                offer: offer
            });
        });
        //todo: handle dial cancel after start P2P
    }
};

GPhone.prototype.getOnlineList = function () {
    var thisPhone = this;
    this.sendToClass('who_online', {});
    return new Promise(function (resolve) {
        var onlineList = {};
        var timeout;

        function onFinish() {
            thisPhone.pkgRecv.off('who_online_response', onResponse);
            resolve(onlineList);
        }

        function onResponse(pkg) {

            var from = pkg.sourceUser;
            if (from === thisPhone.userId) return;
            onlineList[from] = true;
            clearTimeout(timeout);
            timeout = setTimeout(onFinish, 500);
        }

        timeout = setTimeout(onFinish, 1500);
        thisPhone.pkgRecv.on('who_online_response', onResponse);
    });
};


GPhone.prototype.recv_whoOnline = function (pkg) {
    this.sendToClient(pkg.sourceClient, 'who_online_response', {});
};

GPhone.prototype.ev_icecandidate = function (conversation, event) {
    if (conversation !== this.conversation) return;
    if (conversation) {
        this.sendToPhone(conversation.other.clientId, 'icecandidate', {
            id: conversation.id,
            candidate: event.candidate
        })
    }
};

GPhone.prototype.recv_icecandidate = function (pkg) {
    var data = pkg.data;
    var conversation = this.conversation;
    if (!conversation || conversation.id !== data.id) return;
    conversation.addIceCandidate(data.candidate);
};

GPhone.prototype.recv_answer = function (pkg) {
    var data = pkg.data;
    var conversation = this.conversation;
    if (!conversation || conversation.id !== data.id) return;
    if (this.clientId === data.targetClient) {
        conversation.setRemoteDescription(data.answer);
    }
    else {
        //todo: cancel current conversation
        conversation.close();
        this.emit('cancel_income', { type: 'cancel_income', target: this });
        console.info("Pickup by other client, cancel!");
        this.state = "READY";
    }
};

GPhone.prototype.recv_hangup = function (pkg) {
    var data = pkg.data;

    var conversation = this.conversation;
    if (!conversation || conversation.id !== data.id) return;
    if (conversation.pc) {
        conversation.close();
        this.emit('hangup', { from: pkg.sourceUser, conversationId: data.id }, this);
    }
    else {
        this.emit('cancel_income', { type: 'cancel_income', target: this });
    }
    this.emit('hangup', { from: pkg.sourceUser, conversationId: data.id }, this);
    this.state = "READY";
};

GPhone.prototype.copyRTCIceCandidate = function (obj) {
    return ['address', 'candidate', "component", "foundation", "port", "priority", "protocol", "relatedAddress", "relatedPort",
        "sdpMLineIndex", "sdpMid", "tcpType", "usernameFragment"
    ].reduce(function (ac, key) {
        ac[key] = obj[key];
        return ac;
    }, {});
};


export default GPhone;

VaKeR 2022