UNPKG

teoclits

Version:

Typescript angular Teonet Client module

610 lines 23.6 kB
/* * The MIT License * * Copyright 2017 Kirill Scherba <kirill@scherba.ru>. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); /** * Teonet client direct connect with WebRTC * * Version 0.0.2 * */ import { TeonetClientsTranslate } from './teocli.clients'; import Teocli from 'teocli/teocli'; import 'webrtc-adapter'; //type rtcmap = { key: { pc: any, channel: any, data: any } }; var TeocliRTCMap = /** @class */ (function () { function TeocliRTCMap(t) { this.t = t; this.map = {}; this.cli = new TeonetClientsTranslate(this.t); } /** * Create new key in map */ TeocliRTCMap.prototype.create = function (key, pc, channel, data) { var _this = this; if (channel === void 0) { channel = undefined; } if (data === void 0) { data = {}; } if (key) { this.map[key] = { pc: pc, channel: channel, data: data, translate: '', type: '' }; var split_key = this.cli.splitName(key); if (split_key.user) { this.cli.getUserInfo(split_key.user, function (err, user) { if (!err && _this.map[key]) _this.map[key].translate = user.username; }); } if (split_key.client) { this.cli.getClientInfo(split_key.client, function (err, client) { if (!err && _this.map[key]) _this.map[key].type = client.data.type; }); } } }; /** * Add connected channel to map */ TeocliRTCMap.prototype.add = function (key, channel, data) { if (data === void 0) { data = {}; } if (key && this.map[key] && this.map[key].pc && channel) { var pc = this.map[key].pc; console.log('!!! pc:', pc, 'cannel: ', channel); this.map[key].channel = channel; this.map[key].data = data; } }; /** * Check if key exist (and connection with key established) */ TeocliRTCMap.prototype.exist = function (key) { return key in this.map; }; /** * Check if key exist and connection with this peer established */ TeocliRTCMap.prototype.connected = function (key) { return this.getChannel(key) ? true : false; }; /** * Get reference to data channel (connection) */ TeocliRTCMap.prototype.getChannel = function (key) { var data = this.map[key]; return data ? data.channel : undefined; }; /** * Get reference to connection */ TeocliRTCMap.prototype.getConn = function (key) { var data = this.map[key]; return data ? data.pc : undefined; }; /** * Delete keys channel */ TeocliRTCMap.prototype.deleteChannel = function (key) { if (key && this.map[key] && this.map[key].channel) { this.map[key].channel.close(); delete this.map[key].channel; } }; /** * Delete key and all it services (channel) from the map */ TeocliRTCMap.prototype.delete = function (key) { if (key && this.map[key]) { this.deleteChannel(key); // Close and delete data cannel if (this.map[key].pc) { this.map[key].pc.close(); // Closes the peer connection. delete this.map[key].pc; // Delete connection } delete this.map[key]; // Delete key } }; /** * Delete all keys */ TeocliRTCMap.prototype.deleteAll = function () { for (var key in this.map) this.delete(key); }; /** * Get pointer to map */ TeocliRTCMap.prototype.getMap = function () { return this.map; }; return TeocliRTCMap; }()); export { TeocliRTCMap }; var TeocliRTCSignalingChannel = /** @class */ (function (_super) { __extends(TeocliRTCSignalingChannel, _super); function TeocliRTCSignalingChannel() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.map = new TeocliRTCMap(_this); _this.oncandidate = {}; _this.onrtcmessage = {}; return _this; } /** * Start peer connection - Create new key in the RTC map */ TeocliRTCSignalingChannel.prototype.startConnection = function (peer, pc) { console.log('TeocliRTCSignalingChannel:startConnection', peer, pc); this.map.create(peer, pc); }; /** * Get existing connection from RTC map */ TeocliRTCSignalingChannel.prototype.getConnection = function (peer) { var pc = this.map.getConn(peer); return pc; }; /** * Set peer channel connected - Update key in the RTC map */ TeocliRTCSignalingChannel.prototype.setConnected = function (peer, channel) { this.map.add(peer, channel); }; /** * Remove peer from RTC map */ TeocliRTCSignalingChannel.prototype.removeConnection = function (peer) { this.map.delete(peer); }; /** * Send RTC signal command */ TeocliRTCSignalingChannel.prototype.sendRTC = function (peer, msg) { if (peer) _super.prototype.send.call(this, JSON.stringify({ cmd: 255, to: peer, data: msg })); }; /** * Get WebRTC map * @return {any} Pointer to Teonet WebRTC map */ TeocliRTCSignalingChannel.prototype.getWebRTCMap = function () { return this.map.getMap(); }; /** * Is peer connected and is online */ TeocliRTCSignalingChannel.prototype.isWebRTConline = function (key) { return this.map.connected(key); }; /** * Process RTC command (cmd = 255) */ TeocliRTCSignalingChannel.prototype.onrtc = function (err, p) { if (!err) { //console.log('TeocliRTCSignalingChannel::onrtc Got cmd 255'); this.onrtcmessage(p.from, p); } return 1; }; /** * Send teonet message by Teocli or WebRTC */ TeocliRTCSignalingChannel.prototype.send = function (data) { var channel, retval = true; var d = (typeof data == 'string') ? JSON.parse(data) : data; if ((channel = this.map.getChannel(d.to))) { d.from = this.client_name; //console.log('TeocliRTCSignalingChannel::send :', d, 'channel:', channel); channel.send(JSON.stringify(d)); // Send by WebRTC } else { if (!this.map.exist(d.to)) this.oncandidate(d.to); // Create new RTC connection retval = _super.prototype.send.call(this, data); // Send by Teocli } return retval; }; /** * Destroy all connections. * Interception Send event to Event Subscribers * * @param {string} ev Event name * @param {object[]) ...obj Objects send to subscribers */ TeocliRTCSignalingChannel.prototype.sendEvent = function (ev) { var obj = []; for (var _i = 1; _i < arguments.length; _i++) { obj[_i - 1] = arguments[_i]; } // Disconnect all channels when teonet disconnected if (ev == 'teonet-close') { console.log('TeocliRTCSignalingChannel::sendEvent ', 'teonet-close', obj); this.map.deleteAll(); } }; /** * Convert WebRTCNap to Array */ TeocliRTCSignalingChannel.prototype.webrtcToAr = function (webrtc) { if (!webrtc) webrtc = this.getWebRTCMap(); return Object.keys(webrtc).map(function (key) { return { key: key, data: webrtc[key] }; }); }; return TeocliRTCSignalingChannel; }(Teocli)); export { TeocliRTCSignalingChannel }; var TeocliRTC = /** @class */ (function (_super) { __extends(TeocliRTC, _super); /** * TeocliRTC class constructor */ function TeocliRTC(ws) { var _this = _super.call(this, ws) || this; // RTC configurations (ICE Servers) _this.configuration = { iceServers: [ { urls: 'stun:stun.l.google.com:19302' }, { urls: 'turn:turn.bistri.com:80', username: 'homeo', credential: 'homeo' } ] }; // No dissconect flag when iceConnectionState equal to 'disconnected' _this.nodisconnect_flg = false; console.log('TeocliRTC::constructor'); _this.oncandidate = _this.connect; // Start new connection _this.onrtcmessage = _this.processRemoteMessage; // Process remote signal message return _this; } /** * Connect to remote peer */ TeocliRTC.prototype.connect = function (peer) { if (peer) this.createConnection(peer); }; /** * Process remote peer RTC signal messages */ TeocliRTC.prototype.processRemoteMessage = function (peer, evt) { var _this = this; // Get connection or create new var pc; var message = evt.data; // Send reset to remote peer and close current connection var send_reset = function () { _this.sendRTC(peer, { desc: { type: 'teocli-reset' } }); // Send reset commad to receiver var ch = _this.map.getChannel(peer); if (ch) { console.log('send_reset and !!! Disconnected'); ch.close(); } else _this.removeConnection(peer); }; // Reset if teocli-reset send or if teocli-start send and connection already exists if (message.desc && message.desc.type == 'teocli-reset' || message.desc && message.desc.type == 'teocli-start' && this.getConnection(peer)) { console.log('Got RTC \'' + message.desc.type + '\' signal from peer: ' + peer + ', remove existing connection'); //this.removeConnection(peer); //this.map[peer].close(); //delete this.map[peer]; // Delete key (drop connection) //pc = this.getConnection(peer); //this.map.getChannel(peer).close(); //delete this.map.getMap()[peer]; } // Get existing connection or create new one pc = this.getConnection(peer); // || this.createConnection(peer, false); // Process messages if (message.desc) { var desc = message.desc; // teocli message to create connection if (desc.type == 'teocli-start' || desc.type == 'teocli-reset') { // Create connection console.log('Got RTC ' + desc.type + ' signal from:', peer); this.createConnection(peer, false); } else if (!pc) { console.error('pc not created yet', peer); } else if (desc.type == 'teocli-call') { // \TODO Used in WebCamera mode to add local stream to pc connection. // What do with audioSender and videoSender console.log('Got RTC teocli-call signal from peer: ' + peer + ', signal:', message); //if (this.localStream) { // this.addStream(pc, this.localStream); //} } else if (desc.type == 'offer') { console.log('Got offer', message); this.nodisconnect_flg = false; pc.setRemoteDescription(desc) .then(function () { return pc.createAnswer(); }) .then(function (answer) { return pc.setLocalDescription(answer); }) .then(function () { //var str = JSON.stringify({ desc: pc.localDescription }); _this.sendRTC(peer, { desc: pc.localDescription }); }) .catch(function (err) { send_reset(); _this.logError(err); }); } else { console.log('Got ' + message.desc.type + ': ', message); pc.setRemoteDescription(desc).catch(this.logError); } } else if (pc) { console.log('Got ice candidate', message); pc.addIceCandidate(message.candidate).catch(function (err) { send_reset(); _this.logError(err); }); } else { console.error('pc not created yet', peer); } }; /** * Create connection between this peer and remote peer * * @param {string} peer Remote peer name */ TeocliRTC.prototype.createConnection = function (peer, isInitiator) { var _this = this; if (isInitiator === void 0) { isInitiator = true; } var channel; var lastTime = 0; var t = 0; var WAIT_NEXT_NEGOTIATION = 50; var pc = new RTCPeerConnection(this.configuration); console.log('TeocliRTC::createConnection to:', peer, 'initiator:', isInitiator); this.startConnection(peer, pc); // Add RTCPeerConnection to RTC map if (isInitiator) { console.log('Send RTC teocli-start signal to:', peer, 'initiator', isInitiator); this.sendRTC(peer, { desc: { type: 'teocli-start' } }); // Send start commad to receiver } // send any ice candidates to the other peer pc.onicecandidate = function (evt) { //console.log('TeocliRTC::createConnection onicecandidate', evt); _this.sendRTC(peer, { candidate: evt.candidate }); }; // when remote peer disocnnected pc.oniceconnectionstatechange = function (event) { console.log('oniceconnectionstatechange:', pc.iceConnectionState, ' event:', event); if (pc.iceConnectionState == 'disconnected') { //\TODO Close data channel // This event happends when remote data channel is disconnected // (We processed this even and close local channel). // // Or it happends when remote media track added to this RTC connection, // to stop this event we used nodisconnect_flg: boolean which we set // to true when pc.ontrack event hapends. var ch = void 0; if (!_this.nodisconnect_flg && (ch = _this.map.getChannel(peer))) { console.log('oniceconnectionstatechange !!! Disconnected - close channel'); ch.close(); //this.removeConnection(peer); } } if (pc.iceConnectionState == 'failed') { console.log('oniceconnectionstatechange !!! failed'); _this.removeConnection(peer); // Close WebRTC connection } _this.nodisconnect_flg = false; }; // let the "negotiationneeded" event trigger offer generation pc.onnegotiationneeded = function () { console.log('createConnection::onnegotiationneeded', pc); // Generate and Send only last offer during 50 ms // The onnegotiationneeded fire every time when we add or delete streams // tracks. And we will wait all tracks added or deleted and than send one // offer to remote var currentTime = new Date().getTime(); var createOffer = function () { console.log('createConnection::onnegotiationneeded createOffer', pc); _this.nodisconnect_flg = true; // Set no disconnect flag pc.createOffer() .then(function (offer) { return pc.setLocalDescription(offer); }) .then(function () { // Send the offer to the other peer _this.sendRTC(peer, { desc: pc.localDescription }); }) .catch(_this.logError); }; if (t && currentTime - lastTime < WAIT_NEXT_NEGOTIATION) clearTimeout(t); t = setTimeout(createOffer, WAIT_NEXT_NEGOTIATION); lastTime = currentTime; }; // When remote peer connect his media pc.ontrack = function (e) { console.log('pc.ontrack', e); _this.nodisconnect_flg = true; // Set no disconnect flag // Connect remote stream to the remoteVideo control if (_this.callAnswer) _this.callAnswer(peer, e.streams[0]); }; // Process created channel var processChannel = function (channel) { // When channe lopen channel.onopen = function () { console.log('TeocliRTC::createConnection processChannel channel.onopen', 'Connected to:', peer); _this.setConnected(peer, channel); }; // When message received channel.onmessage = function (event) { //console.log('TeocliRTC::createConnection:onmesage', peer, event); _this.process(event.data); }; // When channel close channel.onclose = function () { console.log('TeocliRTC::createConnection:onclose', peer); _this.removeConnection(peer); //this.map.deleteChannel(peer); if (_this.callAnswer) _this.callAnswer(peer); _this.nodisconnect_flg = false; }; // When channel error channel.onerror = function () { console.log('TeocliRTC::createConnection:onerror', peer); }; }; // Create chanel if this peer is initiator if (isInitiator) { channel = pc.createDataChannel('teocli-rtc', { maxRetransmitTime: 3000 // milliseconds }); //console.log('TeocliRTC::createConnection (createDataChannel) to', peer, channel); processChannel(channel); } else { pc.ondatachannel = function (event) { channel = event.channel; //console.log('TeocliRTC::createConnection (ondatachannel) to', peer, channel); processChannel(channel); }; } return pc; }; /** * Add stream to RTCConnection * * @param {any} Reference to RTCPeerConnection * @param {any} Stream to add to connection * @param {any} Options with tracks type (video or audio) to add. If options * is ommited than all strean tracks will be added */ TeocliRTC.prototype.addStream = function (pc, stream, options) { // Add tracks selected in options if (!options) { stream.getTracks().forEach(function (track) { pc.addTrack(track, stream); }); } else { if (options.video) pc.addTrack(stream.getVideoTracks()[0], stream); if (options.audio) pc.addTrack(stream.getAudioTracks()[0], stream); } console.log('Stream added to pc'); }; /** * Show error * * @param {name: string, nessage: string} error Error to show in console */ TeocliRTC.prototype.logError = function (error) { console.error('TeocliRTC::logError', error.name + ": " + error.message + ', (error: ', error, ')'); //this.removeConnection(peer); }; /** * Make Call to remote peer * * @param {string} peer Peer name * @param {CallOptionsType} options Options: * video - reqiest video, * audio - request audio, * chat - request chat channel * * @return {boolean} true if call send to remotepeer; * false if peer name empty, * or peer not connected to this host, * or wrong options send (all: video, audio and * channel is false) */ TeocliRTC.prototype.call = function (peer, localStream, options) { var _this = this; if (options === void 0) { options = { video: true, audio: true, chat: false }; } // Check option and return false if all options lalse if (!(options.video || options.audio || options.chat)) return false; var pc = this.getConnection(peer); if (pc) { // Add local tracks to RTC connection if (localStream) { this.addStream(pc, localStream, options); pc.onremovestream = function () { if (_this.callAnswer) _this.callAnswer(peer); console.log('onremovestream', peer); _this.removeTracks(peer); }; } // \TODO Create Chat data channel if (options.chat) { } return true; } return false; }; /** * Hangup call */ TeocliRTC.prototype.hangup = function (peer) { this.removeTracks(peer); }; /** * Register remote call answer */ TeocliRTC.prototype.registerCallAnswer = function (callAnswer) { this.callAnswer = callAnswer; }; /** * Remove all senders (all senders media tracks) from connection */ TeocliRTC.prototype.removeTracks = function (peer) { var pc = this.getConnection(peer); if (pc) { [/*pc.getReceivers(), */ pc.getSenders()].forEach(function (senderss) { senderss.forEach(function (sender) { pc.removeTrack(sender); }); }); } }; /** * Unregister remote call answer */ TeocliRTC.prototype.unRegisterCallAnswer = function () { this.callAnswer = undefined; }; return TeocliRTC; }(TeocliRTCSignalingChannel)); export { TeocliRTC }; //# sourceMappingURL=teocli.webrtc.js.map