UNPKG

matrix-engine

Version:

basic_timeline improved, VT func setup vide html element id with name arg.- DISABLE RAYCAST DEBUG TEST [2.3.3] Fix for GUI win desktop [2.3.0] DestrucMesh solution & loading convex objs for physics BASIC, SpriteAnimation CPU/texture solution added, Improv

1,257 lines (1,053 loc) 226 kB
'use strict'; // Last time updated: 2019-06-15 4:26:11 PM UTC // _________________________ // RTCMultiConnection v3.6.9 // Open-Sourced: https://github.com/muaz-khan/RTCMultiConnection // -------------------------------------------------- // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence // -------------------------------------------------- var RTCMultiConnection3 = function(roomid, forceOptions) { var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; (function(that) { if (!that) { return; } if (typeof window !== 'undefined') { return; } if (typeof global === 'undefined') { return; } global.navigator = { userAgent: browserFakeUserAgent, getUserMedia: function() {} }; if (!global.console) { global.console = {}; } if (typeof global.console.debug === 'undefined') { global.console.debug = global.console.info = global.console.error = global.console.log = global.console.log || function() { console.log(arguments); }; } if (typeof document === 'undefined') { /*global document:true */ that.document = {}; document.createElement = document.captureStream = document.mozCaptureStream = function() { var obj = { getContext: function() { return obj; }, play: function() {}, pause: function() {}, drawImage: function() {}, toDataURL: function() { return ''; } }; return obj; }; document.addEventListener = document.removeEventListener = that.addEventListener = that.removeEventListener = function() {}; that.HTMLVideoElement = that.HTMLMediaElement = function() {}; } if (typeof io === 'undefined') { that.io = function() { return { on: function(eventName, callback) { callback = callback || function() {}; if (eventName === 'connect') { callback(); } }, emit: function(eventName, data, callback) { callback = callback || function() {}; if (eventName === 'open-room' || eventName === 'join-room') { callback(true, data.sessionid, null); } } }; }; } if (typeof location === 'undefined') { /*global location:true */ that.location = { protocol: 'file:', href: '', hash: '', origin: 'self' }; } if (typeof screen === 'undefined') { /*global screen:true */ that.screen = { width: 0, height: 0 }; } if (typeof URL === 'undefined') { /*global screen:true */ that.URL = { createObjectURL: function() { return ''; }, revokeObjectURL: function() { return ''; } }; } /*global window:true */ that.window = global; })(typeof global !== 'undefined' ? global : null); function SocketConnection(connection, connectCallback) { function isData(session) { return !session.audio && !session.video && !session.screen && session.data; } var parameters = ''; parameters += '?userid=' + connection.userid; parameters += '&sessionid=' + connection.sessionid; parameters += '&msgEvent=' + connection.socketMessageEvent; parameters += '&socketCustomEvent=' + connection.socketCustomEvent; parameters += '&autoCloseEntireSession=' + !!connection.autoCloseEntireSession; if (connection.session.broadcast === true) { parameters += '&oneToMany=true'; } parameters += '&maxParticipantsAllowed=' + connection.maxParticipantsAllowed; if (connection.enableScalableBroadcast) { parameters += '&enableScalableBroadcast=true'; parameters += '&maxRelayLimitPerUser=' + (connection.maxRelayLimitPerUser || 2); } parameters += '&extra=' + JSON.stringify(connection.extra || {}); if (connection.socketCustomParameters) { parameters += connection.socketCustomParameters; } try { io.sockets = {}; } catch (e) {}; if (!connection.socketURL) { connection.socketURL = '/'; } if (connection.socketURL.substr(connection.socketURL.length - 1, 1) != '/') { // connection.socketURL = 'https://domain.com:999/'; throw '"socketURL" MUST end with a slash.'; } if (connection.enableLogs) { if (connection.socketURL == '/') { connection.socketURL = "https://localhost:999/"; } } console.info('socket.io url is: ', connection.socketURL); try { connection.socket = io(connection.socketURL + parameters, { withCredentials: true }); } catch (e) { connection.socket = io.connect(connection.socketURL + parameters, connection.socketOptions, { withCredentials: true }); } var mPeer = connection.multiPeersHandler; connection.socket.on('extra-data-updated', function(remoteUserId, extra) { if (!connection.peers[remoteUserId]) return; connection.peers[remoteUserId].extra = extra; connection.onExtraDataUpdated({ userid: remoteUserId, extra: extra }); updateExtraBackup(remoteUserId, extra); }); function updateExtraBackup(remoteUserId, extra) { if (!connection.peersBackup[remoteUserId]) { connection.peersBackup[remoteUserId] = { userid: remoteUserId, extra: {} }; } connection.peersBackup[remoteUserId].extra = extra; } function onMessageEvent(message) { if (message.remoteUserId != connection.userid) return; if (connection.peers[message.sender] && connection.peers[message.sender].extra != message.message.extra) { connection.peers[message.sender].extra = message.extra; connection.onExtraDataUpdated({ userid: message.sender, extra: message.extra }); updateExtraBackup(message.sender, message.extra); } if (message.message.streamSyncNeeded && connection.peers[message.sender]) { var stream = connection.streamEvents[message.message.streamid]; if (!stream || !stream.stream) { return; } var action = message.message.action; if (action === 'ended' || action === 'inactive' || action === 'stream-removed') { if (connection.peersBackup[stream.userid]) { stream.extra = connection.peersBackup[stream.userid].extra; } connection.onstreamended(stream); return; } var type = message.message.type != 'both' ? message.message.type : null; if (typeof stream.stream[action] == 'function') { try { stream.stream[action](type); } catch(err) {} } return; } if (message.message === 'dropPeerConnection') { connection.deletePeer(message.sender); return; } if (message.message.allParticipants) { if (message.message.allParticipants.indexOf(message.sender) === -1) { message.message.allParticipants.push(message.sender); } message.message.allParticipants.forEach(function(participant) { mPeer[!connection.peers[participant] ? 'createNewPeer' : 'renegotiatePeer'](participant, { localPeerSdpConstraints: { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo }, remotePeerSdpConstraints: { OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo }, isOneWay: !!connection.session.oneway || connection.direction === 'one-way', isDataOnly: isData(connection.session) }); }); return; } if (message.message.newParticipant) { if (message.message.newParticipant == connection.userid) return; if (!!connection.peers[message.message.newParticipant]) return; mPeer.createNewPeer(message.message.newParticipant, message.message.userPreferences || { localPeerSdpConstraints: { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo }, remotePeerSdpConstraints: { OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo }, isOneWay: !!connection.session.oneway || connection.direction === 'one-way', isDataOnly: isData(connection.session) }); return; } if (message.message.readyForOffer) { if (connection.attachStreams.length) { connection.waitingForLocalMedia = false; } if (connection.waitingForLocalMedia) { // if someone is waiting to join you // make sure that we've local media before making a handshake setTimeout(function() { onMessageEvent(message); }, 1); return; } } if (message.message.newParticipationRequest && message.sender !== connection.userid) { if (connection.peers[message.sender]) { connection.deletePeer(message.sender); } var userPreferences = { extra: message.extra || {}, localPeerSdpConstraints: message.message.remotePeerSdpConstraints || { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo }, remotePeerSdpConstraints: message.message.localPeerSdpConstraints || { OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo }, isOneWay: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way', isDataOnly: typeof message.message.isDataOnly !== 'undefined' ? message.message.isDataOnly : isData(connection.session), dontGetRemoteStream: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way', dontAttachLocalStream: !!message.message.dontGetRemoteStream, connectionDescription: message, successCallback: function() {} }; connection.onNewParticipant(message.sender, userPreferences); return; } if (message.message.changedUUID) { if (connection.peers[message.message.oldUUID]) { connection.peers[message.message.newUUID] = connection.peers[message.message.oldUUID]; delete connection.peers[message.message.oldUUID]; } } if (message.message.userLeft) { mPeer.onUserLeft(message.sender); if (!!message.message.autoCloseEntireSession) { connection.leave(); } return; } mPeer.addNegotiatedMessage(message.message, message.sender); } connection.socket.on(connection.socketMessageEvent, onMessageEvent); var alreadyConnected = false; connection.socket.resetProps = function() { alreadyConnected = false; }; connection.socket.on('connect', function() { if (alreadyConnected) { return; } alreadyConnected = true; if (connection.enableLogs) { console.info('socket.io connection is opened.'); } setTimeout(function() { connection.socket.emit('extra-data-updated', connection.extra); }, 1000); if (connectCallback) { connectCallback(connection.socket); } }); connection.socket.on('disconnect', function(event) { connection.onSocketDisconnect(event); }); connection.socket.on('error', function(event) { connection.onSocketError(event); }); connection.socket.on('user-disconnected', function(remoteUserId) { if (remoteUserId === connection.userid) { return; } connection.onUserStatusChanged({ userid: remoteUserId, status: 'offline', extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra || {} : {} }); connection.deletePeer(remoteUserId); }); connection.socket.on('user-connected', function(userid) { if (userid === connection.userid) { return; } connection.onUserStatusChanged({ userid: userid, status: 'online', extra: connection.peers[userid] ? connection.peers[userid].extra || {} : {} }); }); connection.socket.on('closed-entire-session', function(sessionid, extra) { connection.leave(); connection.onEntireSessionClosed({ sessionid: sessionid, userid: sessionid, extra: extra }); }); connection.socket.on('userid-already-taken', function(useridAlreadyTaken, yourNewUserId) { connection.onUserIdAlreadyTaken(useridAlreadyTaken, yourNewUserId); }); connection.socket.on('logs', function(log) { if (!connection.enableLogs) return; console.debug('server-logs', log); }); connection.socket.on('number-of-broadcast-viewers-updated', function(data) { connection.onNumberOfBroadcastViewersUpdated(data); }); connection.socket.on('set-isInitiator-true', function(sessionid) { if (sessionid != connection.sessionid) return; connection.isInitiator = true; }); } function MultiPeers(connection) { var self = this; var skipPeers = ['getAllParticipants', 'getLength', 'selectFirst', 'streams', 'send', 'forEach']; connection.peers = { getLength: function() { var numberOfPeers = 0; for (var peer in this) { if (skipPeers.indexOf(peer) == -1) { numberOfPeers++; } } return numberOfPeers; }, selectFirst: function() { var firstPeer; for (var peer in this) { if (skipPeers.indexOf(peer) == -1) { firstPeer = this[peer]; } } return firstPeer; }, getAllParticipants: function(sender) { var allPeers = []; for (var peer in this) { if (skipPeers.indexOf(peer) == -1 && peer != sender) { allPeers.push(peer); } } return allPeers; }, forEach: function(callbcak) { this.getAllParticipants().forEach(function(participant) { callbcak(connection.peers[participant]); }); }, send: function(data, remoteUserId) { var that = this; if (!isNull(data.size) && !isNull(data.type)) { if (connection.enableFileSharing) { self.shareFile(data, remoteUserId); return; } if (typeof data !== 'string') { data = JSON.stringify(data); } } if (data.type !== 'text' && !(data instanceof ArrayBuffer) && !(data instanceof DataView)) { TextSender.send({ text: data, channel: this, connection: connection, remoteUserId: remoteUserId }); return; } if (data.type === 'text') { data = JSON.stringify(data); } if (remoteUserId) { var remoteUser = connection.peers[remoteUserId]; if (remoteUser) { if (!remoteUser.channels.length) { connection.peers[remoteUserId].createDataChannel(); connection.renegotiate(remoteUserId); setTimeout(function() { that.send(data, remoteUserId); }, 3000); return; } remoteUser.channels.forEach(function(channel) { channel.send(data); }); return; } } this.getAllParticipants().forEach(function(participant) { if (!that[participant].channels.length) { connection.peers[participant].createDataChannel(); connection.renegotiate(participant); setTimeout(function() { that[participant].channels.forEach(function(channel) { channel.send(data); }); }, 3000); return; } that[participant].channels.forEach(function(channel) { channel.send(data); }); }); } }; this.uuid = connection.userid; this.getLocalConfig = function(remoteSdp, remoteUserId, userPreferences) { if (!userPreferences) { userPreferences = {}; } return { streamsToShare: userPreferences.streamsToShare || {}, rtcMultiConnection: connection, connectionDescription: userPreferences.connectionDescription, userid: remoteUserId, localPeerSdpConstraints: userPreferences.localPeerSdpConstraints, remotePeerSdpConstraints: userPreferences.remotePeerSdpConstraints, dontGetRemoteStream: !!userPreferences.dontGetRemoteStream, dontAttachLocalStream: !!userPreferences.dontAttachLocalStream, renegotiatingPeer: !!userPreferences.renegotiatingPeer, peerRef: userPreferences.peerRef, channels: userPreferences.channels || [], onLocalSdp: function(localSdp) { self.onNegotiationNeeded(localSdp, remoteUserId); }, onLocalCandidate: function(localCandidate) { localCandidate = OnIceCandidateHandler.processCandidates(connection, localCandidate) if (localCandidate) { self.onNegotiationNeeded(localCandidate, remoteUserId); } }, remoteSdp: remoteSdp, onDataChannelMessage: function(message) { if (!connection.fbr && connection.enableFileSharing) initFileBufferReader(); if (typeof message == 'string' || !connection.enableFileSharing) { self.onDataChannelMessage(message, remoteUserId); return; } var that = this; if (message instanceof ArrayBuffer || message instanceof DataView) { connection.fbr.convertToObject(message, function(object) { that.onDataChannelMessage(object); }); return; } if (message.readyForNextChunk) { connection.fbr.getNextChunk(message, function(nextChunk, isLastChunk) { connection.peers[remoteUserId].channels.forEach(function(channel) { channel.send(nextChunk); }); }, remoteUserId); return; } if (message.chunkMissing) { connection.fbr.chunkMissing(message); return; } connection.fbr.addChunk(message, function(promptNextChunk) { connection.peers[remoteUserId].peer.channel.send(promptNextChunk); }); }, onDataChannelError: function(error) { self.onDataChannelError(error, remoteUserId); }, onDataChannelOpened: function(channel) { self.onDataChannelOpened(channel, remoteUserId); }, onDataChannelClosed: function(event) { self.onDataChannelClosed(event, remoteUserId); }, onRemoteStream: function(stream) { if (connection.peers[remoteUserId]) { connection.peers[remoteUserId].streams.push(stream); } self.onGettingRemoteMedia(stream, remoteUserId); }, onRemoteStreamRemoved: function(stream) { self.onRemovingRemoteMedia(stream, remoteUserId); }, onPeerStateChanged: function(states) { self.onPeerStateChanged(states); if (states.iceConnectionState === 'new') { self.onNegotiationStarted(remoteUserId, states); } if (states.iceConnectionState === 'connected') { self.onNegotiationCompleted(remoteUserId, states); } if (states.iceConnectionState.search(/closed|failed/gi) !== -1) { self.onUserLeft(remoteUserId); self.disconnectWith(remoteUserId); } } }; }; this.createNewPeer = function(remoteUserId, userPreferences) { if (connection.maxParticipantsAllowed <= connection.getAllParticipants().length) { return; } userPreferences = userPreferences || {}; if (connection.isInitiator && !!connection.session.audio && connection.session.audio === 'two-way' && !userPreferences.streamsToShare) { userPreferences.isOneWay = false; userPreferences.isDataOnly = false; userPreferences.session = connection.session; } if (!userPreferences.isOneWay && !userPreferences.isDataOnly) { userPreferences.isOneWay = true; this.onNegotiationNeeded({ enableMedia: true, userPreferences: userPreferences }, remoteUserId); return; } userPreferences = connection.setUserPreferences(userPreferences, remoteUserId); var localConfig = this.getLocalConfig(null, remoteUserId, userPreferences); connection.peers[remoteUserId] = new PeerInitiator(localConfig); }; this.createAnsweringPeer = function(remoteSdp, remoteUserId, userPreferences) { userPreferences = connection.setUserPreferences(userPreferences || {}, remoteUserId); var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences); connection.peers[remoteUserId] = new PeerInitiator(localConfig); }; this.renegotiatePeer = function(remoteUserId, userPreferences, remoteSdp) { if (!connection.peers[remoteUserId]) { if (connection.enableLogs) { console.error('Peer (' + remoteUserId + ') does not exist. Renegotiation skipped.'); } return; } if (!userPreferences) { userPreferences = {}; } userPreferences.renegotiatingPeer = true; userPreferences.peerRef = connection.peers[remoteUserId].peer; userPreferences.channels = connection.peers[remoteUserId].channels; var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences); connection.peers[remoteUserId] = new PeerInitiator(localConfig); }; this.replaceTrack = function(track, remoteUserId, isVideoTrack) { if (!connection.peers[remoteUserId]) { throw 'This peer (' + remoteUserId + ') does not exist.'; } var peer = connection.peers[remoteUserId].peer; if (!!peer.getSenders && typeof peer.getSenders === 'function' && peer.getSenders().length) { peer.getSenders().forEach(function(rtpSender) { if (isVideoTrack && rtpSender.track.kind === 'video') { connection.peers[remoteUserId].peer.lastVideoTrack = rtpSender.track; rtpSender.replaceTrack(track); } if (!isVideoTrack && rtpSender.track.kind === 'audio') { connection.peers[remoteUserId].peer.lastAudioTrack = rtpSender.track; rtpSender.replaceTrack(track); } }); return; } console.warn('RTPSender.replaceTrack is NOT supported.'); this.renegotiatePeer(remoteUserId); }; this.onNegotiationNeeded = function(message, remoteUserId) {}; this.addNegotiatedMessage = function(message, remoteUserId) { if (message.type && message.sdp) { if (message.type == 'answer') { if (connection.peers[remoteUserId]) { connection.peers[remoteUserId].addRemoteSdp(message); } } if (message.type == 'offer') { if (message.renegotiatingPeer) { this.renegotiatePeer(remoteUserId, null, message); } else { this.createAnsweringPeer(message, remoteUserId); } } if (connection.enableLogs) { console.log('Remote peer\'s sdp:', message.sdp); } return; } if (message.candidate) { if (connection.peers[remoteUserId]) { connection.peers[remoteUserId].addRemoteCandidate(message); } if (connection.enableLogs) { console.log('Remote peer\'s candidate pairs:', message.candidate); } return; } if (message.enableMedia) { connection.session = message.userPreferences.session || connection.session; if (connection.session.oneway && connection.attachStreams.length) { connection.attachStreams = []; } if (message.userPreferences.isDataOnly && connection.attachStreams.length) { connection.attachStreams.length = []; } var streamsToShare = {}; connection.attachStreams.forEach(function(stream) { streamsToShare[stream.streamid] = { isAudio: !!stream.isAudio, isVideo: !!stream.isVideo, isScreen: !!stream.isScreen }; }); message.userPreferences.streamsToShare = streamsToShare; self.onNegotiationNeeded({ readyForOffer: true, userPreferences: message.userPreferences }, remoteUserId); } if (message.readyForOffer) { connection.onReadyForOffer(remoteUserId, message.userPreferences); } function cb(stream) { gumCallback(stream, message, remoteUserId); } }; function gumCallback(stream, message, remoteUserId) { var streamsToShare = {}; connection.attachStreams.forEach(function(stream) { streamsToShare[stream.streamid] = { isAudio: !!stream.isAudio, isVideo: !!stream.isVideo, isScreen: !!stream.isScreen }; }); message.userPreferences.streamsToShare = streamsToShare; self.onNegotiationNeeded({ readyForOffer: true, userPreferences: message.userPreferences }, remoteUserId); } this.onGettingRemoteMedia = function(stream, remoteUserId) {}; this.onRemovingRemoteMedia = function(stream, remoteUserId) {}; this.onGettingLocalMedia = function(localStream) {}; this.onLocalMediaError = function(error, constraints) { connection.onMediaError(error, constraints); }; function initFileBufferReader() { connection.fbr = new FileBufferReader(); connection.fbr.onProgress = function(chunk) { connection.onFileProgress(chunk); }; connection.fbr.onBegin = function(file) { connection.onFileStart(file); }; connection.fbr.onEnd = function(file) { connection.onFileEnd(file); }; } this.shareFile = function(file, remoteUserId) { initFileBufferReader(); connection.fbr.readAsArrayBuffer(file, function(uuid) { var arrayOfUsers = connection.getAllParticipants(); if (remoteUserId) { arrayOfUsers = [remoteUserId]; } arrayOfUsers.forEach(function(participant) { connection.fbr.getNextChunk(uuid, function(nextChunk) { connection.peers[participant].channels.forEach(function(channel) { channel.send(nextChunk); }); }, participant); }); }, { userid: connection.userid, // extra: connection.extra, chunkSize: DetectRTC.browser.name === 'Firefox' ? 15 * 1000 : connection.chunkSize || 0 }); }; if (typeof 'TextReceiver' !== 'undefined') { var textReceiver = new TextReceiver(connection); } this.onDataChannelMessage = function(message, remoteUserId) { textReceiver.receive(JSON.parse(message), remoteUserId, connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}); }; this.onDataChannelClosed = function(event, remoteUserId) { event.userid = remoteUserId; event.extra = connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}; connection.onclose(event); }; this.onDataChannelError = function(error, remoteUserId) { error.userid = remoteUserId; event.extra = connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}; connection.onerror(error); }; this.onDataChannelOpened = function(channel, remoteUserId) { // keep last channel only; we are not expecting parallel/channels channels if (connection.peers[remoteUserId].channels.length) { connection.peers[remoteUserId].channels = [channel]; return; } connection.peers[remoteUserId].channels.push(channel); connection.onopen({ userid: remoteUserId, extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}, channel: channel }); }; this.onPeerStateChanged = function(state) { connection.onPeerStateChanged(state); }; this.onNegotiationStarted = function(remoteUserId, states) {}; this.onNegotiationCompleted = function(remoteUserId, states) {}; this.getRemoteStreams = function(remoteUserId) { remoteUserId = remoteUserId || connection.peers.getAllParticipants()[0]; return connection.peers[remoteUserId] ? connection.peers[remoteUserId].streams : []; }; } 'use strict'; // Last Updated On: 2019-01-10 5:32:55 AM UTC // ________________ // DetectRTC v1.3.9 // Open-Sourced: https://github.com/muaz-khan/DetectRTC // -------------------------------------------------- // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence // -------------------------------------------------- (function() { var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; var isNodejs = typeof process === 'object' && typeof process.versions === 'object' && process.versions.node && /*node-process*/ !process.browser; if (isNodejs) { var version = process.versions.node.toString().replace('v', ''); browserFakeUserAgent = 'Nodejs/' + version + ' (NodeOS) AppleWebKit/' + version + ' (KHTML, like Gecko) Nodejs/' + version + ' Nodejs/' + version } (function(that) { if (typeof window !== 'undefined') { return; } if (typeof window === 'undefined' && typeof global !== 'undefined') { global.navigator = { userAgent: browserFakeUserAgent, getUserMedia: function() {} }; /*global window:true */ that.window = global; } else if (typeof window === 'undefined') { // window = this; } if (typeof location === 'undefined') { /*global location:true */ that.location = { protocol: 'file:', href: '', hash: '' }; } if (typeof screen === 'undefined') { /*global screen:true */ that.screen = { width: 0, height: 0 }; } })(typeof global !== 'undefined' ? global : window); /*global navigator:true */ var navigator = window.navigator; if (typeof navigator !== 'undefined') { if (typeof navigator.webkitGetUserMedia !== 'undefined') { navigator.getUserMedia = navigator.webkitGetUserMedia; } if (typeof navigator.mozGetUserMedia !== 'undefined') { navigator.getUserMedia = navigator.mozGetUserMedia; } } else { navigator = { getUserMedia: function() {}, userAgent: browserFakeUserAgent }; } var isMobileDevice = !!(/Android|webOS|iPhone|iPad|iPod|BB10|BlackBerry|IEMobile|Opera Mini|Mobile|mobile/i.test(navigator.userAgent || '')); var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveOrOpenBlob || !!navigator.msSaveBlob); var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; var isFirefox = typeof window.InstallTrigger !== 'undefined'; var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); var isChrome = !!window.chrome && !isOpera; var isIE = typeof document !== 'undefined' && !!document.documentMode && !isEdge; // this one can also be used: // https://www.websocket.org/js/stuff.js (DetectBrowser.js) function getBrowserInfo() { var nVer = navigator.appVersion; var nAgt = navigator.userAgent; var browserName = navigator.appName; var fullVersion = '' + parseFloat(navigator.appVersion); var majorVersion = parseInt(navigator.appVersion, 10); var nameOffset, verOffset, ix; // both and safri and chrome has same userAgent if (isSafari && !isChrome && nAgt.indexOf('CriOS') !== -1) { isSafari = false; isChrome = true; } // In Opera, the true version is after 'Opera' or after 'Version' if (isOpera) { browserName = 'Opera'; try { fullVersion = navigator.userAgent.split('OPR/')[1].split(' ')[0]; majorVersion = fullVersion.split('.')[0]; } catch (e) { fullVersion = '0.0.0.0'; majorVersion = 0; } } // In MSIE version <=10, the true version is after 'MSIE' in userAgent // In IE 11, look for the string after 'rv:' else if (isIE) { verOffset = nAgt.indexOf('rv:'); if (verOffset > 0) { //IE 11 fullVersion = nAgt.substring(verOffset + 3); } else { //IE 10 or earlier verOffset = nAgt.indexOf('MSIE'); fullVersion = nAgt.substring(verOffset + 5); } browserName = 'IE'; } // In Chrome, the true version is after 'Chrome' else if (isChrome) { verOffset = nAgt.indexOf('Chrome'); browserName = 'Chrome'; fullVersion = nAgt.substring(verOffset + 7); } // In Safari, the true version is after 'Safari' or after 'Version' else if (isSafari) { verOffset = nAgt.indexOf('Safari'); browserName = 'Safari'; fullVersion = nAgt.substring(verOffset + 7); if ((verOffset = nAgt.indexOf('Version')) !== -1) { fullVersion = nAgt.substring(verOffset + 8); } if (navigator.userAgent.indexOf('Version/') !== -1) { fullVersion = navigator.userAgent.split('Version/')[1].split(' ')[0]; } } // In Firefox, the true version is after 'Firefox' else if (isFirefox) { verOffset = nAgt.indexOf('Firefox'); browserName = 'Firefox'; fullVersion = nAgt.substring(verOffset + 8); } // In most other browsers, 'name/version' is at the end of userAgent else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) { browserName = nAgt.substring(nameOffset, verOffset); fullVersion = nAgt.substring(verOffset + 1); if (browserName.toLowerCase() === browserName.toUpperCase()) { browserName = navigator.appName; } } if (isEdge) { browserName = 'Edge'; fullVersion = navigator.userAgent.split('Edge/')[1]; // fullVersion = parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10).toString(); } // trim the fullVersion string at semicolon/space/bracket if present if ((ix = fullVersion.search(/[; \)]/)) !== -1) { fullVersion = fullVersion.substring(0, ix); } majorVersion = parseInt('' + fullVersion, 10); if (isNaN(majorVersion)) { fullVersion = '' + parseFloat(navigator.appVersion); majorVersion = parseInt(navigator.appVersion, 10); } return { fullVersion: fullVersion, version: majorVersion, name: browserName, isPrivateBrowsing: false }; } // via: https://gist.github.com/cou929/7973956 function retry(isDone, next) { var currentTrial = 0, maxRetry = 50, interval = 10, isTimeout = false; var id = window.setInterval( function() { if (isDone()) { window.clearInterval(id); next(isTimeout); } if (currentTrial++ > maxRetry) { window.clearInterval(id); isTimeout = true; next(isTimeout); } }, 10 ); } function isIE10OrLater(userAgent) { var ua = userAgent.toLowerCase(); if (ua.indexOf('msie') === 0 && ua.indexOf('trident') === 0) { return false; } var match = /(?:msie|rv:)\s?([\d\.]+)/.exec(ua); if (match && parseInt(match[1], 10) >= 10) { return true; } return false; } function detectPrivateMode(callback) { var isPrivate; try { if (window.webkitRequestFileSystem) { window.webkitRequestFileSystem( window.TEMPORARY, 1, function() { isPrivate = false; }, function(e) { isPrivate = true; } ); } else if (window.indexedDB && /Firefox/.test(window.navigator.userAgent)) { var db; try { db = window.indexedDB.open('test'); db.onerror = function() { return true; }; } catch (e) { isPrivate = true; } if (typeof isPrivate === 'undefined') { retry( function isDone() { return db.readyState === 'done' ? true : false; }, function next(isTimeout) { if (!isTimeout) { isPrivate = db.result ? false : true; } } ); } } else if (isIE10OrLater(window.navigator.userAgent)) { isPrivate = false; try { if (!window.indexedDB) { isPrivate = true; } } catch (e) { isPrivate = true; } } else if (window.localStorage && /Safari/.test(window.navigator.userAgent)) { try { window.localStorage.setItem('test', 1); } catch (e) { isPrivate = true; } if (typeof isPrivate === 'undefined') { isPrivate = false; window.localStorage.removeItem('test'); } } } catch (e) { isPrivate = false; } retry( function isDone() { return typeof isPrivate !== 'undefined' ? true : false; }, function next(isTimeout) { callback(isPrivate); } ); } var isMobile = { Android: function() { return navigator.userAgent.match(/Android/i); }, BlackBerry: function() { return navigator.userAgent.match(/BlackBerry|BB10/i); }, iOS: function() { return navigator.userAgent.match(/iPhone|iPad|iPod/i); }, Opera: function() { return navigator.userAgent.match(/Opera Mini/i); }, Windows: function() { return navigator.userAgent.match(/IEMobile/i); }, any: function() { return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows()); }, getOsName: function() { var osName = 'Unknown OS'; if (isMobile.Android()) { osName = 'Android'; } if (isMobile.BlackBerry()) { osName = 'BlackBerry'; } if (isMobile.iOS()) { osName = 'iOS'; } if (isMobile.Opera()) { osName = 'Opera Mini'; } if (isMobile.Windows()) { osName = 'Windows'; } return osName; } }; // via: http://jsfiddle.net/ChristianL/AVyND/ function detectDesktopOS() {