<commit_before>/* global RTCPeerConnection */

//
// Camera and Microphone Authorization
//

/*
cordova.plugins.diagnostic.requestMicrophoneAuthorization(function (status) {
    if(status === cordova.plugins.diagnostic.permissionStatus.GRANTED) {
        console.log('GRANTED');
    } else {
        console.log(new Error('AuthorizationDenied'));
    }
}, function (err) {
    console.log(new Error('AuthorizationFailed: ' + err));
});

cordova.plugins.diagnostic.requestCameraAuthorization(function (status) {
    if(status === cordova.plugins.diagnostic.permissionStatus.GRANTED) {
        console.log('GRANTED');
    } else {
        console.log(new Error('AuthorizationDenied'));
    }
}, function (err) {
    console.log(new Error('AuthorizationFailed: ' + err));
});
*/

var cordova = window.cordova;

// Expose WebRTC Globals
if (cordova && cordova.plugins && cordova.plugins.iosrtc) {
  cordova.plugins.iosrtc.registerGlobals();
  cordova.plugins.iosrtc.debug.enabled = true;
}

//
// Container
//

document.body.innerHTML = "";
var appContainer = document.body;

//
// getUserMedia
//

var localStream;
function TestGetUserMedia() {

  var localVideoEl = document.createElement('video');
  localVideoEl.setAttribute('autoplay', 'autoplay');
  localVideoEl.setAttribute('playsinline', 'playsinline');
  // Cause zIndex - 1 failure
  //localVideoEl.style.backgroundColor = 'purple';
  localVideoEl.style.position = 'absolute';
  localVideoEl.style.top = 0;
  localVideoEl.style.left = 0;
  localVideoEl.style.width = "100px";
  localVideoEl.style.height = "100px";
  localVideoEl.style.transform = "scaleX(-1)";
  appContainer.appendChild(localVideoEl);

  navigator.mediaDevices.enumerateDevices().then(function (devices) {
      console.log('getMediaDevices.ok', devices);
      devices.forEach(function (device, idx) {
        console.log('getMediaDevices.devices', idx, device.label, device.kind, device.deviceId);
      });
  }, function (err) {
      console.log('getMediaDevices.err', err);
  });

  navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true
    /*
    video: {
      // Test Back Camera
      //deviceId: 'com.apple.avfoundation.avcapturedevice.built-in_video:0'
      //sourceId: 'com.apple.avfoundation.avcapturedevice.built-in_video:0'
      deviceId: {
        exact: 'com.apple.avfoundation.avcapturedevice.built-in_video:0'
      }
    },
    audio: {
      exact: 'Built-In Microphone'
    }*/
  }).then(function (stream) {

    console.log('getUserMedia.stream', stream);
    console.log('getUserMedia.stream.getTracks', stream.getTracks());

    var srcObjectStream;
    try {

      localStream = stream;
      srcObjectStream = localVideoEl.srcObject = localStream;

    } catch (err) {
      console.log('srcObject.err', err);
    }

    if (cordova && cordova.plugins && cordova.plugins.iosrtc) {
      cordova.plugins.iosrtc.observeVideo(localVideoEl);
    }

    if (srcObjectStream) {

      TestPluginMediaStreamRenderer(localVideoEl);
      TestRTCPeerConnection(localStream);
    }

  }).catch(function (err) {
    console.log('getUserMediaError', err, err.stack);
  });
}

var useAnimateVideo = false;
function TestPluginMediaStreamRenderer(localVideoEl) {

  // Animate video position
  var currentPosition = {
    x: 0,
    y: 0
  };

  var animateTimer;
  function animateVideo() {

    currentPosition.x = currentPosition.x < (window.innerWidth - parseInt(localVideoEl.style.width, 10)) ? currentPosition.x + 1 : 0;
    currentPosition.y = currentPosition.y < (window.innerHeight - parseInt(localVideoEl.style.height, 10)) ? currentPosition.y + 1 : 0;

    localVideoEl.style.top = currentPosition.y + 'px';
    localVideoEl.style.left = currentPosition.x + 'px';

    if (cordova && cordova.plugins && cordova.plugins.iosrtc) {
      cordova.plugins.iosrtc.refreshVideos();
    }

    return (animateTimer = requestAnimationFrame(animateVideo));
  }

  if (useAnimateVideo) {
    animateTimer = animateVideo();
  }

  //
  // Test Video behind Element
  //

  document.body.style.background = "transparent";
  document.documentElement.style.background = "transparent";
  localVideoEl.style.zIndex = -1;

  var overEl = document.createElement('button');
  overEl.style.backgroundColor = 'red';
  overEl.style.position = 'absolute';
  overEl.style.left = 0;
  overEl.style.top = 0;
  overEl.style.width = "100px";
  overEl.style.height = "100px";

  overEl.addEventListener('click', function () {
    overEl.style.backgroundColor = overEl.style.backgroundColor === 'red' ? 'green' : 'red';

    if (overEl.style.backgroundColor === 'red') {
      animateTimer = animateVideo();
    } else {
      cancelAnimationFrame(animateTimer);
    }
  });

  appContainer.appendChild(overEl);

  overEl.style.left = ((window.innerWidth / 2)  - parseInt(overEl.style.width, 10)) + 'px';
  overEl.style.top  = ((window.innerHeight / 2) - parseInt(overEl.style.height, 10)) + 'px';

}

//
// Test RTCPeerConnection
//

var pc1 = new RTCPeerConnection(),
    pc2 = new RTCPeerConnection();

function TestRTCPeerConnection(localStream) {

  // TODO Deprecated
  //pc1.addStream(localStream);

  // To Test removeStream
  // pc1.removeStream(pc1.getLocalStreams()[0])

  // TODO
  // NotSupportedError: The adapter.js addTrack polyfill only supports a single stream which is associated with the specified track.
  localStream.getTracks().forEach(function (track) {
    console.log('addTrack', track);
    pc1.addTrack(track);
  });

  function onAddIceCandidate(pc, can) {
    console.log('onAddIceCandidate', pc, can);
    return can && pc.addIceCandidate(can).catch(function (err) {
      console.log('addIceCandidateError', err);
    });
  }

  pc1.onicecandidate = function (e) {
    onAddIceCandidate(pc2, e.candidate);
  };
  pc2.onicecandidate = function (e) {
    onAddIceCandidate(pc1, e.candidate);
  };

  pc2.onaddstream = function (e) {
    console.log('pc2.onAddStream', e);

    var peerVideoEl = document.createElement('video');
    peerVideoEl.setAttribute('autoplay', 'autoplay');
    peerVideoEl.setAttribute('playsinline', 'playsinline');
    peerVideoEl.style.backgroundColor = 'blue';
    peerVideoEl.style.position = 'fixed';
    peerVideoEl.style.width = "100px";
    peerVideoEl.style.height = "100px";
    peerVideoEl.style.top = 0;
    peerVideoEl.style.left = (window.innerWidth - parseInt(peerVideoEl.style.width, 10)) + 'px';
    appContainer.appendChild(peerVideoEl);

    peerVideoEl.srcObject = e.stream;

    if (cordova && cordova.plugins && cordova.plugins.iosrtc) {
      cordova.plugins.iosrtc.observeVideo(peerVideoEl);
    }
  };

  pc1.oniceconnectionstatechange = function (e) {
    console.log('pc1.iceConnectionState', e, pc1.iceConnectionState);

    if (pc1.iceConnectionState === 'completed') {
      console.log('pc1.getSenders', pc1.getSenders());
      console.log('pc2.getReceivers', pc2.getReceivers());
    }
  };

  pc1.onnegotiationneeded = function (e) {
    console.log('pc1.negotiatioNeeded', e);

    return pc1.createOffer().then(function (d) {
      return pc1.setLocalDescription(d);
    }).then(function () {
      return pc2.setRemoteDescription({
        type: pc1.localDescription.type,
        sdp: pc1.localDescription.sdp
      });
    }).then(function () {
      return pc2.createAnswer();
    }).then(function (d) {
      return pc2.setLocalDescription(d);
    }).then(function () {
      return pc1.setRemoteDescription({
        type: pc2.localDescription.type,
        sdp: pc2.localDescription.sdp
      });
    }).catch(function (err) {
      console.log('pc1.createOfferError', err);
    });
  };
}

var useWebRTCAdapter = false;

// Expose webrtc-adapter
if (useWebRTCAdapter && typeof window.adapter === 'undefined') {

    // load adapter.js
    var version = 'latest';
    var script = document.createElement("script");
    script.type = "text/javascript";
    //script.src = "adapter-latest.js";
    script.src = "https://webrtc.github.io/adapter/adapter-" + version + ".js";
    script.async = false;
    document.getElementsByTagName("head")[0].appendChild(script);
    script.onload = function () {
      console.log('useWebRTCAdapter.loaded', script.src);
      TestGetUserMedia();
    };
} else {
  TestGetUserMedia();
}

<commit_msg>remove cordova.plugins.iosrtc.observeVideo from test render scripts and update TestRTCPeerConnection<commit_after>/* global RTCPeerConnection */

//
// Camera and Microphone Authorization
//

/*
cordova.plugins.diagnostic.requestMicrophoneAuthorization(function (status) {
    if(status === cordova.plugins.diagnostic.permissionStatus.GRANTED) {
        console.log('GRANTED');
    } else {
        console.log(new Error('AuthorizationDenied'));
    }
}, function (err) {
    console.log(new Error('AuthorizationFailed: ' + err));
});

cordova.plugins.diagnostic.requestCameraAuthorization(function (status) {
    if(status === cordova.plugins.diagnostic.permissionStatus.GRANTED) {
        console.log('GRANTED');
    } else {
        console.log(new Error('AuthorizationDenied'));
    }
}, function (err) {
    console.log(new Error('AuthorizationFailed: ' + err));
});
*/

var cordova = window.cordova;

// Expose WebRTC Globals
if (cordova && cordova.plugins && cordova.plugins.iosrtc) {
  cordova.plugins.iosrtc.registerGlobals();
  cordova.plugins.iosrtc.debug.enable('*', true);
}

//
// Container
//

document.body.innerHTML = "";
var appContainer = document.body;

//
// getUserMedia
//

var localStream;
var localVideoEl;
function TestGetUserMedia() {
  localVideoEl = document.createElement('video');
  localVideoEl.setAttribute('autoplay', 'autoplay');
  localVideoEl.setAttribute('playsinline', 'playsinline');
  // Cause zIndex - 1 failure
  //localVideoEl.style.backgroundColor = 'purple';
  localVideoEl.style.position = 'absolute';
  localVideoEl.style.top = 0;
  localVideoEl.style.left = 0;
  localVideoEl.style.width = "100px";
  localVideoEl.style.height = "100px";
  localVideoEl.style.transform = "scaleX(-1)";
  appContainer.appendChild(localVideoEl);

  navigator.mediaDevices.enumerateDevices().then(function (devices) {
      console.log('getMediaDevices.ok', devices);
      devices.forEach(function (device, idx) {
        console.log('getMediaDevices.devices', idx, device.label, device.kind, device.deviceId);
      });
  }, function (err) {
      console.log('getMediaDevices.err', err);
  });

  navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true
    /*
    video: {
      // Test Back Camera
      //deviceId: 'com.apple.avfoundation.avcapturedevice.built-in_video:0'
      //sourceId: 'com.apple.avfoundation.avcapturedevice.built-in_video:0'
      deviceId: {
        exact: 'com.apple.avfoundation.avcapturedevice.built-in_video:0'
      }
    },
    audio: {
      exact: 'Built-In Microphone'
    }*/
  }).then(function (stream) {

    console.log('getUserMedia.stream', stream);
    console.log('getUserMedia.stream.getTracks', stream.getTracks());

    localStream = stream;
    localVideoEl.srcObject = localStream;

    TestPluginMediaStreamRenderer(localVideoEl);
    TestRTCPeerConnection(localStream);

  }).catch(function (err) {
    console.log('getUserMediaError', err, err.stack);
  });
}

var useAnimateVideo = false;
function TestPluginMediaStreamRenderer(localVideoEl) {

  // Animate video position
  var currentPosition = {
    x: 0,
    y: 0
  };

  var animateTimer;
  function animateVideo() {

    currentPosition.x = currentPosition.x < (window.innerWidth - parseInt(localVideoEl.style.width, 10)) ? currentPosition.x + 1 : 0;
    currentPosition.y = currentPosition.y < (window.innerHeight - parseInt(localVideoEl.style.height, 10)) ? currentPosition.y + 1 : 0;

    localVideoEl.style.top = currentPosition.y + 'px';
    localVideoEl.style.left = currentPosition.x + 'px';

    if (cordova && cordova.plugins && cordova.plugins.iosrtc) {
      cordova.plugins.iosrtc.refreshVideos();
    }

    return (animateTimer = requestAnimationFrame(animateVideo));
  }

  if (useAnimateVideo) {
    animateTimer = animateVideo();
  }

  //
  // Test Video behind Element
  //

  document.body.style.background = "transparent";
  document.documentElement.style.background = "transparent";
  localVideoEl.style.zIndex = -1;

  var overEl = document.createElement('button');
  overEl.style.backgroundColor = 'red';
  overEl.style.position = 'absolute';
  overEl.style.left = 0;
  overEl.style.top = 0;
  overEl.style.width = "100px";
  overEl.style.height = "100px";

  overEl.addEventListener('click', function () {
    overEl.style.backgroundColor = overEl.style.backgroundColor === 'red' ? 'green' : 'red';

    if (overEl.style.backgroundColor === 'red') {
      animateTimer = animateVideo();
    } else {
      cancelAnimationFrame(animateTimer);
    }
  });

  appContainer.appendChild(overEl);

  overEl.style.left = ((window.innerWidth / 2)  - parseInt(overEl.style.width, 10)) + 'px';
  overEl.style.top  = ((window.innerHeight / 2) - parseInt(overEl.style.height, 10)) + 'px';

}

//
// Test RTCPeerConnection
//

var pc1 = new RTCPeerConnection(),
    pc2 = new RTCPeerConnection();

var peerVideoEl;
var peerStream;
function TestRTCPeerConnection(localStream) {

  // Note: Deprecated TestaddStream
  //pc1.addStream(localStream);

  // Note: Deprecated Test removeStream
  // pc1.removeStream(pc1.getLocalStreams()[0])

  localStream.getTracks().forEach(function (track) {
    console.log('addTrack', track);
    pc1.addTrack(track);
  });

  function onAddIceCandidate(pc, can) {
    console.log('addIceCandidate', pc, can);
    return can && pc.addIceCandidate(can).catch(function (err) {
      console.log('addIceCandidateError', err);
    });
  }

  pc1.onicecandidate = function (e) {
    onAddIceCandidate(pc2, e.candidate);
  };

  pc2.onicecandidate = function (e) {
    onAddIceCandidate(pc1, e.candidate);
  };

  pc2.onaddstream = function (e) {
    console.log('pc2.addStream', e);
    peerVideoEl = document.createElement('video');
    peerVideoEl.setAttribute('autoplay', 'autoplay');
    peerVideoEl.setAttribute('playsinline', 'playsinline');
    peerVideoEl.style.backgroundColor = 'blue';
    peerVideoEl.style.position = 'fixed';
    peerVideoEl.style.width = "100px";
    peerVideoEl.style.height = "100px";
    peerVideoEl.style.top = 0;
    peerVideoEl.style.left = (window.innerWidth - parseInt(peerVideoEl.style.width, 10)) + 'px';
    appContainer.appendChild(peerVideoEl);

    peerStream = e.stream;
    peerVideoEl.srcObject = peerStream;
  };

  pc1.oniceconnectionstatechange = function (e) {
    console.log('pc1.iceConnectionState', e, pc1.iceConnectionState);

    if (pc1.iceConnectionState === 'completed') {
      console.log('pc1.getSenders', pc1.getSenders());
      console.log('pc2.getReceivers', pc2.getReceivers());
    }
  };

  pc1.onicegatheringstatechange = function (e) {
    console.log('pc1.iceGatheringStateChange', e);
  };

  pc1.onnegotiationneeded = function (e) {
    console.log('pc1.negotiatioNeeded', e);

    return pc1.createOffer().then(function (d) {
      var desc = {
        type: d.type,
        sdp: d.sdp
      };
      console.log('pc1.setLocalDescription', desc);
      return pc1.setLocalDescription(desc);
    }).then(function () {
      var desc = {
        type: pc1.localDescription.type,
        sdp: pc1.localDescription.sdp
      };
      console.log('pc2.setLocalDescription', desc);
      return pc2.setRemoteDescription(desc);
    }).then(function () {
      console.log('pc2.createAnswer');
      return pc2.createAnswer();
    }).then(function (d) {
      var desc = {
        type: d.type,
        sdp: d.sdp
      };
      console.log('pc2.setLocalDescription', desc);
      return pc2.setLocalDescription(d);
    }).then(function () {
      var desc = {
        type: pc2.localDescription.type,
        sdp: pc2.localDescription.sdp
      };
      console.log('pc1.setRemoteDescription', desc);
      return pc1.setRemoteDescription(desc);
    }).catch(function (err) {
      console.log('pc1.createOfferError', err);
    });
  };
}

var useWebRTCAdapter = false;

// Expose webrtc-adapter
if (useWebRTCAdapter && typeof window.adapter === 'undefined') {

    // load adapter.js
    var version = 'latest';
    var script = document.createElement("script");
    script.type = "text/javascript";
    //script.src = "adapter-latest.js";
    script.src = "https://webrtc.github.io/adapter/adapter-" + version + ".js";
    script.async = false;
    document.getElementsByTagName("head")[0].appendChild(script);
    script.onload = function () {
      console.log('useWebRTCAdapter.loaded', script.src);
      TestGetUserMedia();
    };
} else {
  TestGetUserMedia();
}

<|endoftext|>


<commit_before>require('dotenv').config()

import RiotRateLimiter from 'riot-ratelimiter-temp'
const RRLStrategies = require('riot-ratelimiter-temp/dist/RateLimiter').STRATEGY

import merge from 'lodash.merge'

import Logger from './Logger'

import ParameterHelper from './Utils/ParameterHelper'

import ChallengerEndpoint from './Endpoints/LeagueEndpoint/ChallengerEndpoint'
import ChampionMasteryEndpoint from './Endpoints/ChampionMasteryEndpoint'
import ChampionRotationEndpoint from './Endpoints/ChampionRotationEndpoint'
import CurrentGameEndpoint from './Endpoints/SpectatorEndpoint/CurrentGameEndpoint'
import DDragonChampionEndpoint from './Endpoints/DDragonEndpoints/DDragonChampionEndpoint'
import DDragonItemEndpoint from './Endpoints/DDragonEndpoints/DDragonItemEndpoint'
import DDragonLanguageEndpoint from './Endpoints/DDragonEndpoints/DDragonLanguageEndpoint'
import DDragonLanguageStringEndpoint from './Endpoints/DDragonEndpoints/DDragonLanguageStringEndpoint'
import DDragonMapEndpoint from './Endpoints/DDragonEndpoints/DDragonMapEndpoint'
import DDragonProfileIconEndpoint from './Endpoints/DDragonEndpoints/DDragonProfileIconEndpoint'
import DDragonRealmEndpoint from './Endpoints/DDragonEndpoints/DDragonRealmEndpoint'
import DDragonRunesReforgedEndpoint from './Endpoints/DDragonEndpoints/DDragonRunesReforgedEndpoint'
import DDragonSummonerSpellEndpoint from './Endpoints/DDragonEndpoints/DDragonSummonerSpellEndpoint'
import DDragonVersionEndpoint from './Endpoints/DDragonEndpoints/DDragonVersionEndpoint'
import FeaturedGamesEndpoint from './Endpoints/SpectatorEndpoint/FeaturedGamesEndpoint'
import LeagueEndpoint from './Endpoints/LeagueEndpoint/LeagueEndpoint'
import LeaguePositionsEndpoint from './Endpoints/LeagueEndpoint/LeaguePositionsEndpoint'
import MasterEndpoint from './Endpoints/LeagueEndpoint/MasterEndpoint'
import MatchEndpoint from './Endpoints/MatchEndpoint/MatchEndpoint'
import MatchlistEndpoint from './Endpoints/MatchEndpoint/MatchlistEndpoint'
import StatusEndpoint from './Endpoints/StatusEndpoint'
import SummonerEndpoint from './Endpoints/SummonerEndpoint'
import ThirdPartyCodeEndpoint from './Endpoints/ThirdPartyCodeEndpoint'
import TournamentStubEndpoint from './Endpoints/TournamentStubEndpoint'
import TournamentEndpoint from './Endpoints/TournamentEndpoint'

import ChallengerEndpointV4 from './Endpoints/LeagueEndpoint/ChallengerEndpointV4'
import ChampionMasteryEndpointV4 from './Endpoints/ChampionMasteryEndpointV4'
import CurrentGameEndpointV4 from './Endpoints/SpectatorEndpoint/CurrentGameEndpointV4'
import FeaturedGamesEndpointV4 from './Endpoints/SpectatorEndpoint/FeaturedGamesEndpointV4'
import GrandmasterEndpointV4 from './Endpoints/LeagueEndpoint/GrandmasterEndpointV4'
import LeagueEndpointV4 from './Endpoints/LeagueEndpoint/LeagueEndpointV4'
import LeaguePositionsEndpointV4 from './Endpoints/LeagueEndpoint/LeaguePositionsEndpointV4'
import MasterEndpointV4 from './Endpoints/LeagueEndpoint/MasterEndpointV4'
import MatchEndpointV4 from './Endpoints/MatchEndpoint/MatchEndpointV4'
import MatchlistEndpointV4 from './Endpoints/MatchEndpoint/MatchlistEndpointV4'
import SummonerEndpointV4 from './Endpoints/SummonerEndpointV4'
import ThirdPartyCodeEndpointV4 from './Endpoints/ThirdPartyCodeEndpointV4'
import TournamentEndpointV4 from './Endpoints/TournamentEndpointV4'
import TournamentStubEndpointV4 from './Endpoints/TournamentStubEndpointV4'

import DEFAULT_TTLS, { makeTTLsFromGroupedTTLs } from './Enums/default-ttls'

import { DEFAULT_KAYN_CONFIG, KAYN_CONFIG_STRUCT } from './KaynConfig'

class Kayn {
    constructor(
        key = process.env.RIOT_LOL_API_KEY,
        config = DEFAULT_KAYN_CONFIG,
    ) {
        if (!ParameterHelper.isKeyValid(key)) {
            throw new Error(
                'Failed to initialize Kayn! API key is not a non-empty string.',
            )
        }

        // Make sure that the rest of the sane, nested defaults are set.
        // The source object here is the user config,
        // while the destination is the default config.
        // Extra merge is used to prevent mutation of original default config.
        // Merge is needed for deep-merging.
        this.config = KAYN_CONFIG_STRUCT(
            merge(merge({}, DEFAULT_KAYN_CONFIG), { key, ...config }),
        )

        const strategy = this.config.requestOptions.burst
            ? RRLStrategies.BURST
            : RRLStrategies.SPREAD

        this.limiter = new RiotRateLimiter({
            strategy,
        })

        if (this.config.debugOptions.isEnabled) {
            // Not pure but whatever.
            const configCopy = { ...this.config }
            if (!configCopy.debugOptions.showKey) {
                delete configCopy.key
            }
            Logger(this.config)
            this.config.debugOptions.loggers.initLogger(
                'with config:\n%O',
                configCopy,
            )
        }

        // Handle caching time-to-lives.
        if (this.config.cacheOptions.cache) {
            /*
                Start by checking if default is used. If so, set it.
                Next, merge grouped ttls and then singular ttls.
                Finally, merge final, old `ttls` prop for backwards-compatibility.
                We use this as the source of truth for cache ttls.
            */
            let finalTTLs = {}
            if (this.config.cacheOptions.timeToLives.useDefault) {
                finalTTLs = merge(finalTTLs, DEFAULT_TTLS)
            }
            finalTTLs = merge(
                finalTTLs,
                makeTTLsFromGroupedTTLs(
                    this.config.cacheOptions.timeToLives.byGroup,
                ),
                this.config.cacheOptions.timeToLives.byMethod,
                this.config.cacheOptions.ttls,
            )
            this.config.cacheOptions.ttls = finalTTLs
        }

        // Set up interfaces.
        this.Challenger = new ChallengerEndpoint(this.config, this.limiter)
        this.ChampionMastery = new ChampionMasteryEndpoint(
            this.config,
            this.limiter,
        )
        // Did not mean to have this as ChampionRotation, but I'll keep it just in case people are using it.
        // Use Champion.Rotation.list instead.
        this.ChampionRotation = new ChampionRotationEndpoint(
            this.config,
            this.limiter,
        )
        this.Champion = {}
        this.Champion.Rotation = this.ChampionRotation
        this.CurrentGame = new CurrentGameEndpoint(this.config, this.limiter)
        this.DDragon = {
            Champion: new DDragonChampionEndpoint(this.config),
            Item: new DDragonItemEndpoint(this.config),
            Language: new DDragonLanguageEndpoint(this.config),
            LanguageString: new DDragonLanguageStringEndpoint(this.config),
            Map: new DDragonMapEndpoint(this.config),
            ProfileIcon: new DDragonProfileIconEndpoint(this.config),
            Realm: new DDragonRealmEndpoint(this.config),
            RunesReforged: new DDragonRunesReforgedEndpoint(this.config),
            SummonerSpell: new DDragonSummonerSpellEndpoint(this.config),
            Version: new DDragonVersionEndpoint(this.config),
        }
        this.FeaturedGames = new FeaturedGamesEndpoint(
            this.config,
            this.limiter,
        )
        this.League = new LeagueEndpoint(this.config, this.limiter)
        this.LeaguePositions = new LeaguePositionsEndpoint(
            this.config,
            this.limiter,
        )
        this.Master = new MasterEndpoint(this.config, this.limiter)
        this.Match = new MatchEndpoint(this.config, this.limiter)
        this.Matchlist = new MatchlistEndpoint(this.config, this.limiter)
        this.Summoner = new SummonerEndpoint(this.config, this.limiter)
        this.Status = new StatusEndpoint(this.config, this.limiter)
        this.ThirdPartyCode = new ThirdPartyCodeEndpoint(
            this.config,
            this.limiter,
        )
        this.TournamentStub = new TournamentStubEndpoint(
            this.config,
            this.limiter,
        )
        this.Tournament = new TournamentEndpoint(this.config, this.limiter)

        this.ChallengerV4 = new ChallengerEndpointV4(this.config, this.limiter)
        this.ChampionMasteryV4 = new ChampionMasteryEndpointV4(
            this.config,
            this.limiter,
        )
        this.CurrentGameV4 = new CurrentGameEndpointV4(
            this.config,
            this.limiter,
        )
        this.FeaturedGamesV4 = new FeaturedGamesEndpointV4(
            this.config,
            this.limiter,
        )
        this.GrandmasterV4 = new GrandmasterEndpointV4(
            this.config,
            this.limiter,
        )
        this.LeagueV4 = new LeagueEndpointV4(this.config, this.limiter)
        this.LeaguePositionsV4 = new LeaguePositionsEndpointV4(
            this.config,
            this.limiter,
        )
        this.MasterV4 = new MasterEndpointV4(this.config, this.limiter)
        this.MatchV4 = new MatchEndpointV4(this.config, this.limiter)
        this.MatchlistV4 = new MatchlistEndpointV4(this.config, this.limiter)
        this.SummonerV4 = new SummonerEndpointV4(this.config, this.limiter)
        this.ThirdPartyCodeV4 = new ThirdPartyCodeEndpointV4(
            this.config,
            this.limiter,
        )
        this.TournamentV4 = new TournamentEndpointV4(this.config, this.limiter)
        this.TournamentStubV4 = new TournamentStubEndpointV4(
            this.config,
            this.limiter,
        )

        if (this.config.debugOptions.isEnabled) {
            this.config.debugOptions.loggers.initLogger(
                'Initialized interfaces. Ready!',
            )
        }

        console.warn(`
            WARNING: Please be aware that the V3 endpoints will be deprecated by January 14th, 2019.
            Most (or all) the endpoints will be upgraded to V4. This is problematic because summoner ID's
            will be encrypted uniquely per developer, which obsoletes our existing summoner ID's. We also
            need to adjust our code to use \`puuid\` instead of \`summonerId\` and \'accountId\'.
            This, however, does come with benefits that I won't explain here.

            This deprecation warning is on initialization since it's more significant than the past method deprecations.

            Regardless, \'kayn\' will attempt to be ready a month earlier.

            To read more, see here: https://discussion.developer.riotgames.com/articles/6579/player-universally-unique-identifiers-and-a-new-se.html
            To follow development progress, see here: https://github.com/cnguy/kayn/issues/55
        `)
    }

    flushCache(cb) {
        return new Promise((resolve, reject) => {
            if (!cb) {
                cb = (err, data) => (err ? reject(err) : resolve(data))
            }
            this.config.cacheOptions.cache.flushCache(cb)
        })
    }
}

import REGIONS from 'Enums/regions'
import METHOD_NAMES from 'Enums/method-names'
import BasicJSCache from 'Caches/BasicJSCache'
import LRUCache from 'Caches/LRUCache'
import RedisCache from 'Caches/RedisCache'

const init = key => config => new Kayn(key, config)

module.exports = {
    Kayn: init,
    REGIONS,
    METHOD_NAMES,
    BasicJSCache,
    LRUCache,
    RedisCache,
}
<commit_msg>fix: Update deprecation warning to 1/28/2019 as per docs #55<commit_after>require('dotenv').config()

import RiotRateLimiter from 'riot-ratelimiter-temp'
const RRLStrategies = require('riot-ratelimiter-temp/dist/RateLimiter').STRATEGY

import merge from 'lodash.merge'

import Logger from './Logger'

import ParameterHelper from './Utils/ParameterHelper'

import ChallengerEndpoint from './Endpoints/LeagueEndpoint/ChallengerEndpoint'
import ChampionMasteryEndpoint from './Endpoints/ChampionMasteryEndpoint'
import ChampionRotationEndpoint from './Endpoints/ChampionRotationEndpoint'
import CurrentGameEndpoint from './Endpoints/SpectatorEndpoint/CurrentGameEndpoint'
import DDragonChampionEndpoint from './Endpoints/DDragonEndpoints/DDragonChampionEndpoint'
import DDragonItemEndpoint from './Endpoints/DDragonEndpoints/DDragonItemEndpoint'
import DDragonLanguageEndpoint from './Endpoints/DDragonEndpoints/DDragonLanguageEndpoint'
import DDragonLanguageStringEndpoint from './Endpoints/DDragonEndpoints/DDragonLanguageStringEndpoint'
import DDragonMapEndpoint from './Endpoints/DDragonEndpoints/DDragonMapEndpoint'
import DDragonProfileIconEndpoint from './Endpoints/DDragonEndpoints/DDragonProfileIconEndpoint'
import DDragonRealmEndpoint from './Endpoints/DDragonEndpoints/DDragonRealmEndpoint'
import DDragonRunesReforgedEndpoint from './Endpoints/DDragonEndpoints/DDragonRunesReforgedEndpoint'
import DDragonSummonerSpellEndpoint from './Endpoints/DDragonEndpoints/DDragonSummonerSpellEndpoint'
import DDragonVersionEndpoint from './Endpoints/DDragonEndpoints/DDragonVersionEndpoint'
import FeaturedGamesEndpoint from './Endpoints/SpectatorEndpoint/FeaturedGamesEndpoint'
import LeagueEndpoint from './Endpoints/LeagueEndpoint/LeagueEndpoint'
import LeaguePositionsEndpoint from './Endpoints/LeagueEndpoint/LeaguePositionsEndpoint'
import MasterEndpoint from './Endpoints/LeagueEndpoint/MasterEndpoint'
import MatchEndpoint from './Endpoints/MatchEndpoint/MatchEndpoint'
import MatchlistEndpoint from './Endpoints/MatchEndpoint/MatchlistEndpoint'
import StatusEndpoint from './Endpoints/StatusEndpoint'
import SummonerEndpoint from './Endpoints/SummonerEndpoint'
import ThirdPartyCodeEndpoint from './Endpoints/ThirdPartyCodeEndpoint'
import TournamentStubEndpoint from './Endpoints/TournamentStubEndpoint'
import TournamentEndpoint from './Endpoints/TournamentEndpoint'

import ChallengerEndpointV4 from './Endpoints/LeagueEndpoint/ChallengerEndpointV4'
import ChampionMasteryEndpointV4 from './Endpoints/ChampionMasteryEndpointV4'
import CurrentGameEndpointV4 from './Endpoints/SpectatorEndpoint/CurrentGameEndpointV4'
import FeaturedGamesEndpointV4 from './Endpoints/SpectatorEndpoint/FeaturedGamesEndpointV4'
import GrandmasterEndpointV4 from './Endpoints/LeagueEndpoint/GrandmasterEndpointV4'
import LeagueEndpointV4 from './Endpoints/LeagueEndpoint/LeagueEndpointV4'
import LeaguePositionsEndpointV4 from './Endpoints/LeagueEndpoint/LeaguePositionsEndpointV4'
import MasterEndpointV4 from './Endpoints/LeagueEndpoint/MasterEndpointV4'
import MatchEndpointV4 from './Endpoints/MatchEndpoint/MatchEndpointV4'
import MatchlistEndpointV4 from './Endpoints/MatchEndpoint/MatchlistEndpointV4'
import SummonerEndpointV4 from './Endpoints/SummonerEndpointV4'
import ThirdPartyCodeEndpointV4 from './Endpoints/ThirdPartyCodeEndpointV4'
import TournamentEndpointV4 from './Endpoints/TournamentEndpointV4'
import TournamentStubEndpointV4 from './Endpoints/TournamentStubEndpointV4'

import DEFAULT_TTLS, { makeTTLsFromGroupedTTLs } from './Enums/default-ttls'

import { DEFAULT_KAYN_CONFIG, KAYN_CONFIG_STRUCT } from './KaynConfig'

class Kayn {
    constructor(
        key = process.env.RIOT_LOL_API_KEY,
        config = DEFAULT_KAYN_CONFIG,
    ) {
        if (!ParameterHelper.isKeyValid(key)) {
            throw new Error(
                'Failed to initialize Kayn! API key is not a non-empty string.',
            )
        }

        // Make sure that the rest of the sane, nested defaults are set.
        // The source object here is the user config,
        // while the destination is the default config.
        // Extra merge is used to prevent mutation of original default config.
        // Merge is needed for deep-merging.
        this.config = KAYN_CONFIG_STRUCT(
            merge(merge({}, DEFAULT_KAYN_CONFIG), { key, ...config }),
        )

        const strategy = this.config.requestOptions.burst
            ? RRLStrategies.BURST
            : RRLStrategies.SPREAD

        this.limiter = new RiotRateLimiter({
            strategy,
        })

        if (this.config.debugOptions.isEnabled) {
            // Not pure but whatever.
            const configCopy = { ...this.config }
            if (!configCopy.debugOptions.showKey) {
                delete configCopy.key
            }
            Logger(this.config)
            this.config.debugOptions.loggers.initLogger(
                'with config:\n%O',
                configCopy,
            )
        }

        // Handle caching time-to-lives.
        if (this.config.cacheOptions.cache) {
            /*
                Start by checking if default is used. If so, set it.
                Next, merge grouped ttls and then singular ttls.
                Finally, merge final, old `ttls` prop for backwards-compatibility.
                We use this as the source of truth for cache ttls.
            */
            let finalTTLs = {}
            if (this.config.cacheOptions.timeToLives.useDefault) {
                finalTTLs = merge(finalTTLs, DEFAULT_TTLS)
            }
            finalTTLs = merge(
                finalTTLs,
                makeTTLsFromGroupedTTLs(
                    this.config.cacheOptions.timeToLives.byGroup,
                ),
                this.config.cacheOptions.timeToLives.byMethod,
                this.config.cacheOptions.ttls,
            )
            this.config.cacheOptions.ttls = finalTTLs
        }

        // Set up interfaces.
        this.Challenger = new ChallengerEndpoint(this.config, this.limiter)
        this.ChampionMastery = new ChampionMasteryEndpoint(
            this.config,
            this.limiter,
        )
        // Did not mean to have this as ChampionRotation, but I'll keep it just in case people are using it.
        // Use Champion.Rotation.list instead.
        this.ChampionRotation = new ChampionRotationEndpoint(
            this.config,
            this.limiter,
        )
        this.Champion = {}
        this.Champion.Rotation = this.ChampionRotation
        this.CurrentGame = new CurrentGameEndpoint(this.config, this.limiter)
        this.DDragon = {
            Champion: new DDragonChampionEndpoint(this.config),
            Item: new DDragonItemEndpoint(this.config),
            Language: new DDragonLanguageEndpoint(this.config),
            LanguageString: new DDragonLanguageStringEndpoint(this.config),
            Map: new DDragonMapEndpoint(this.config),
            ProfileIcon: new DDragonProfileIconEndpoint(this.config),
            Realm: new DDragonRealmEndpoint(this.config),
            RunesReforged: new DDragonRunesReforgedEndpoint(this.config),
            SummonerSpell: new DDragonSummonerSpellEndpoint(this.config),
            Version: new DDragonVersionEndpoint(this.config),
        }
        this.FeaturedGames = new FeaturedGamesEndpoint(
            this.config,
            this.limiter,
        )
        this.League = new LeagueEndpoint(this.config, this.limiter)
        this.LeaguePositions = new LeaguePositionsEndpoint(
            this.config,
            this.limiter,
        )
        this.Master = new MasterEndpoint(this.config, this.limiter)
        this.Match = new MatchEndpoint(this.config, this.limiter)
        this.Matchlist = new MatchlistEndpoint(this.config, this.limiter)
        this.Summoner = new SummonerEndpoint(this.config, this.limiter)
        this.Status = new StatusEndpoint(this.config, this.limiter)
        this.ThirdPartyCode = new ThirdPartyCodeEndpoint(
            this.config,
            this.limiter,
        )
        this.TournamentStub = new TournamentStubEndpoint(
            this.config,
            this.limiter,
        )
        this.Tournament = new TournamentEndpoint(this.config, this.limiter)

        this.ChallengerV4 = new ChallengerEndpointV4(this.config, this.limiter)
        this.ChampionMasteryV4 = new ChampionMasteryEndpointV4(
            this.config,
            this.limiter,
        )
        this.CurrentGameV4 = new CurrentGameEndpointV4(
            this.config,
            this.limiter,
        )
        this.FeaturedGamesV4 = new FeaturedGamesEndpointV4(
            this.config,
            this.limiter,
        )
        this.GrandmasterV4 = new GrandmasterEndpointV4(
            this.config,
            this.limiter,
        )
        this.LeagueV4 = new LeagueEndpointV4(this.config, this.limiter)
        this.LeaguePositionsV4 = new LeaguePositionsEndpointV4(
            this.config,
            this.limiter,
        )
        this.MasterV4 = new MasterEndpointV4(this.config, this.limiter)
        this.MatchV4 = new MatchEndpointV4(this.config, this.limiter)
        this.MatchlistV4 = new MatchlistEndpointV4(this.config, this.limiter)
        this.SummonerV4 = new SummonerEndpointV4(this.config, this.limiter)
        this.ThirdPartyCodeV4 = new ThirdPartyCodeEndpointV4(
            this.config,
            this.limiter,
        )
        this.TournamentV4 = new TournamentEndpointV4(this.config, this.limiter)
        this.TournamentStubV4 = new TournamentStubEndpointV4(
            this.config,
            this.limiter,
        )

        if (this.config.debugOptions.isEnabled) {
            this.config.debugOptions.loggers.initLogger(
                'Initialized interfaces. Ready!',
            )
        }

        console.warn(`
            UPDATED WARNING: Please be aware that the V3 endpoints will be deprecated by January 28th, 2019 (extended by 2 weeks).
            Most (or all) the endpoints will be upgraded to V4. This is problematic because summoner ID's
            will be encrypted uniquely per developer, which obsoletes our existing summoner ID's. We also
            need to adjust our code to use \`puuid\` instead of \`summonerId\` and \'accountId\'.
            This, however, does come with benefits that I won't explain here.

            This deprecation warning is on initialization since it's more significant than the past method deprecations.

            Regardless, \'kayn\' will attempt to be ready a month earlier.

            To read more, see here: https://discussion.developer.riotgames.com/articles/6579/player-universally-unique-identifiers-and-a-new-se.html
            To follow development progress, see here: https://github.com/cnguy/kayn/issues/55
        `)
    }

    flushCache(cb) {
        return new Promise((resolve, reject) => {
            if (!cb) {
                cb = (err, data) => (err ? reject(err) : resolve(data))
            }
            this.config.cacheOptions.cache.flushCache(cb)
        })
    }
}

import REGIONS from 'Enums/regions'
import METHOD_NAMES from 'Enums/method-names'
import BasicJSCache from 'Caches/BasicJSCache'
import LRUCache from 'Caches/LRUCache'
import RedisCache from 'Caches/RedisCache'

const init = key => config => new Kayn(key, config)

module.exports = {
    Kayn: init,
    REGIONS,
    METHOD_NAMES,
    BasicJSCache,
    LRUCache,
    RedisCache,
}
<|endoftext|>


<commit_before>'use strict';

angular.module('app.services', [])
  // more flexible log service
  // https://groups.google.com/d/msg/angular/vgMF3i3Uq2Y/q1fY_iIvkhUJ
  .value('logWhiteList', /.*Ctrl|.*Srvc/)
  .factory('logFactory', function($log, logWhiteList, state) {
    return function(prefix) {
      var match = prefix ? prefix.match(logWhiteList) : true;
      function extracted(prop) {
        if (!match) return angular.noop;
        return function() {
          var args = [].slice.call(arguments);
          if (prefix) args.unshift('[' + prefix + ']');
          $log[prop].apply($log, args);
        };
      }
      var logLogger = extracted('log');
      return {
        log:   logLogger,
        warn:  extracted('warn'),
        error: extracted('error'),
        // XXX angular now has support for console.debug?
        debug: function() { if (state.dev) logLogger.apply(logLogger, arguments); }
      };
    };
  })
  .service('cometdSrvc', function(COMETD_URL, logFactory, apiSrvc, $rootScope, $window) {
    var log = logFactory('cometdSrvc');
    // boilerplate cometd setup
    // http://cometd.org/documentation/cometd-javascript/subscription
    var cometd = $.cometd,
        connected = false,
        clientId,
        subscriptions = [];
    cometd.configure({
      url: COMETD_URL,
      //logLevel: 'debug',
      backoffIncrement: 100,
      maxBackoff: 500,
      // necessary to work with Faye backend when browser lacks websockets:
      // https://groups.google.com/d/msg/faye-users/8cr_4QZ-7cU/sKVLbCFDkEUJ
      appendMessageTypeToURL: false
    });
    //cometd.websocketsEnabled = false; // XXX can we re-enable in Lantern?

    function disconnect() {
      cometd.disconnect(true);
    }
    $($window).unload(disconnect);

    // http://cometd.org/documentation/cometd-javascript/subscription
    cometd.onListenerException = function(exception, subscriptionHandle, isListener, message) {
      log.error('Uncaught exception for subscription', subscriptionHandle, ':', exception, 'message:', message);
      apiSrvc.exception({error: 'uncaughtSubscriptionException', subscriptionHandle: subscriptionHandle, exception: exception, message: message});
      if (isListener) {
        cometd.removeListener(subscriptionHandle);
        log.error('removed listener');
      } else {
        cometd.unsubscribe(subscriptionHandle);
        log.error('unsubscribed');
      }
      disconnect();
    };

    cometd.addListener('/meta/connect', function(msg) {
      if (cometd.isDisconnected()) {
        connected = false;
        log.debug('connection closed');
        return;
      }
      var wasConnected = connected;
      connected = msg.successful;
      if (!wasConnected && connected) { // reconnected
        log.debug('connection established');
        $rootScope.$broadcast('cometdConnected');
        // XXX why do docs put this in successful handshake callback?
        cometd.batch(function(){ refresh(); });
      } else if (wasConnected && !connected) {
        log.warn('connection broken');
        $rootScope.$broadcast('cometdDisconnected');
      }
    });

    // backend doesn't send disconnects, but just in case
    cometd.addListener('/meta/disconnect', function(msg) {
      log.debug('got disconnect');
      if (msg.successful) {
        connected = false;
        log.debug('connection closed');
        $rootScope.$broadcast('cometdDisconnected');
        // XXX handle disconnect
      }
    });

    function subscribe(key) {
      if (connected) {
        key.sub = cometd.subscribe(key.chan, key.cb);
        log.debug('subscribed', key);
      } else {
        log.debug('queuing subscription request', key);
      }
      subscriptions.push(key);
      if (angular.isUndefined(key.renewOnReconnect))
        key.renewOnReconnect = true;
    }

    function unsubscribe(key) {
      cometd.unsubscribe(key.sub);
      log.debug('unsubscribed', key);
      key.renewOnReconnect = false;
    }

    function refresh() {
      log.debug('refreshing subscriptions');
      var renew = [];
      angular.forEach(subscriptions, function(key) {
        if (key.sub)
          cometd.unsubscribe(key.sub);
        if (key.renewOnReconnect)
          renew.push(key);
      });
      subscriptions = [];
      _.each(renew, function(key) {
        subscribe(key);
      });
    }

    cometd.addListener('/meta/handshake', function(handshake) {
      if (handshake.successful) {
        log.debug('successful handshake', handshake);
        clientId = handshake.clientId;
        //cometd.batch(function(){ refresh(); }); // XXX moved to connect callback
      }
      else {
        log.warn('unsuccessful handshake');
        clientId = null;
      }
    });

    cometd.handshake();

    return {
      subscribe: subscribe,
      unsubscribe: unsubscribe,
      disconnect: disconnect
    };
  })
  .service('modelSrvc', function($rootScope, apiSrvc, MODEL_SYNC_CHANNEL, cometdSrvc, logFactory) {
    var log = logFactory('modelSrvc'),
        model = {},
        syncSubscriptionKey;

    // XXX use modelValidatorSrvc to validate update before accepting
    function handleSync(msg) {
      var patch = msg.data;
      // backend can send updates before model has been populated
      // https://github.com/getlantern/lantern/issues/587
      if (patch[0].path !== '' && _.isEmpty(model)) {
        log.debug('ignoring', msg, 'while model has not yet been populated');
        return;
      }
      $rootScope.$apply(function() {
        if (patch[0].path === '') {
          // XXX jsonpatch can't mutate root object https://github.com/dharmafly/jsonpatch.js/issues/10
          angular.copy(patch[0].value, model);
        } else {
          try {
            applyPatch(model, patch);
          } catch (e) {
            if (!(e instanceof PatchApplyError || e instanceof InvalidPatch)) throw e;
            log.error('Error applying patch', patch);
            apiSrvc.exception({exception: e, patch: patch});
          }
        }
      });
    }

    syncSubscriptionKey = {chan: MODEL_SYNC_CHANNEL, cb: handleSync};
    cometdSrvc.subscribe(syncSubscriptionKey);

    return {
      model: model,
      sane: true
    };
  })
  // XXX shared global state object
  .service('state', function() {
    return {};
  })
  .service('apiSrvc', function($http, API_URL_PREFIX) {
    return {
      exception: function(data) {
        return $http.post(API_URL_PREFIX+'/exception', data);
      },
      interaction: function(interactionid, data) {
        var url = API_URL_PREFIX+'/interaction/'+interactionid;
        return $http.post(url, data);
      }
    };
  });
<commit_msg>work around angular-ui/bootstrap#407<commit_after>'use strict';

angular.module('app.services', [])
  // more flexible log service
  // https://groups.google.com/d/msg/angular/vgMF3i3Uq2Y/q1fY_iIvkhUJ
  .value('logWhiteList', /.*Ctrl|.*Srvc/)
  .factory('logFactory', function($log, logWhiteList, state) {
    return function(prefix) {
      var match = prefix ? prefix.match(logWhiteList) : true;
      function extracted(prop) {
        if (!match) return angular.noop;
        return function() {
          var args = [].slice.call(arguments);
          if (prefix) args.unshift('[' + prefix + ']');
          $log[prop].apply($log, args);
        };
      }
      var logLogger = extracted('log');
      return {
        log:   logLogger,
        warn:  extracted('warn'),
        error: extracted('error'),
        // XXX angular now has support for console.debug?
        debug: function() { if (state.dev) logLogger.apply(logLogger, arguments); }
      };
    };
  })
  .service('cometdSrvc', function(COMETD_URL, logFactory, apiSrvc, $rootScope, $window) {
    var log = logFactory('cometdSrvc');
    // boilerplate cometd setup
    // http://cometd.org/documentation/cometd-javascript/subscription
    var cometd = $.cometd,
        connected = false,
        clientId,
        subscriptions = [];
    cometd.configure({
      url: COMETD_URL,
      //logLevel: 'debug',
      backoffIncrement: 100,
      maxBackoff: 500,
      // necessary to work with Faye backend when browser lacks websockets:
      // https://groups.google.com/d/msg/faye-users/8cr_4QZ-7cU/sKVLbCFDkEUJ
      appendMessageTypeToURL: false
    });
    //cometd.websocketsEnabled = false; // XXX can we re-enable in Lantern?

    function disconnect() {
      cometd.disconnect(true);
    }
    $($window).unload(disconnect);

    // http://cometd.org/documentation/cometd-javascript/subscription
    cometd.onListenerException = function(exception, subscriptionHandle, isListener, message) {
      log.error('Uncaught exception for subscription', subscriptionHandle, ':', exception, 'message:', message);
      apiSrvc.exception({error: 'uncaughtSubscriptionException', subscriptionHandle: subscriptionHandle, exception: exception, message: message});
      if (isListener) {
        cometd.removeListener(subscriptionHandle);
        log.error('removed listener');
      } else {
        cometd.unsubscribe(subscriptionHandle);
        log.error('unsubscribed');
      }
      disconnect();
    };

    cometd.addListener('/meta/connect', function(msg) {
      if (cometd.isDisconnected()) {
        connected = false;
        log.debug('connection closed');
        return;
      }
      var wasConnected = connected;
      connected = msg.successful;
      if (!wasConnected && connected) { // reconnected
        log.debug('connection established');
        $rootScope.$broadcast('cometdConnected');
        // XXX why do docs put this in successful handshake callback?
        cometd.batch(function(){ refresh(); });
      } else if (wasConnected && !connected) {
        log.warn('connection broken');
        $rootScope.$broadcast('cometdDisconnected');
      }
    });

    // backend doesn't send disconnects, but just in case
    cometd.addListener('/meta/disconnect', function(msg) {
      log.debug('got disconnect');
      if (msg.successful) {
        connected = false;
        log.debug('connection closed');
        $rootScope.$broadcast('cometdDisconnected');
        // XXX handle disconnect
      }
    });

    function subscribe(key) {
      if (connected) {
        key.sub = cometd.subscribe(key.chan, key.cb);
        log.debug('subscribed', key);
      } else {
        log.debug('queuing subscription request', key);
      }
      subscriptions.push(key);
      if (angular.isUndefined(key.renewOnReconnect))
        key.renewOnReconnect = true;
    }

    function unsubscribe(key) {
      cometd.unsubscribe(key.sub);
      log.debug('unsubscribed', key);
      key.renewOnReconnect = false;
    }

    function refresh() {
      log.debug('refreshing subscriptions');
      var renew = [];
      angular.forEach(subscriptions, function(key) {
        if (key.sub)
          cometd.unsubscribe(key.sub);
        if (key.renewOnReconnect)
          renew.push(key);
      });
      subscriptions = [];
      _.each(renew, function(key) {
        subscribe(key);
      });
    }

    cometd.addListener('/meta/handshake', function(handshake) {
      if (handshake.successful) {
        log.debug('successful handshake', handshake);
        clientId = handshake.clientId;
        //cometd.batch(function(){ refresh(); }); // XXX moved to connect callback
      }
      else {
        log.warn('unsuccessful handshake');
        clientId = null;
      }
    });

    cometd.handshake();

    return {
      subscribe: subscribe,
      unsubscribe: unsubscribe,
      disconnect: disconnect
    };
  })
  .service('modelSrvc', function($rootScope, apiSrvc, MODEL_SYNC_CHANNEL, cometdSrvc, logFactory) {
    var log = logFactory('modelSrvc'),
        model = {},
        syncSubscriptionKey;

    // XXX use modelValidatorSrvc to validate update before accepting
    function handleSync(msg) {
      var patch = msg.data;
      // backend can send updates before model has been populated
      // https://github.com/getlantern/lantern/issues/587
      if (patch[0].path !== '' && _.isEmpty(model)) {
        log.debug('ignoring', msg, 'while model has not yet been populated');
        return;
      }
      try {
        $rootScope.$apply(function() {
          if (patch[0].path === '') {
            // XXX jsonpatch can't mutate root object https://github.com/dharmafly/jsonpatch.js/issues/10
            angular.copy(patch[0].value, model);
          } else {
            try {
              applyPatch(model, patch);
            } catch (e) {
              if (!(e instanceof PatchApplyError || e instanceof InvalidPatch)) throw e;
              log.error('Error applying patch', patch);
              apiSrvc.exception({exception: e, patch: patch});
            }
          }
        });
      } catch (e) {
        // XXX https://github.com/angular/angular.js/issues/2602
        // XXX https://github.com/angular-ui/bootstrap/issues/407
        if (/scrollHeight/.test(e.message)) {
          log.debug('Swallowing "<TTL> $digest() iterations reached" error caused by https://github.com/angular-ui/bootstrap/issues/407');
        } else {
          throw e;
        }
      }
    }

    syncSubscriptionKey = {chan: MODEL_SYNC_CHANNEL, cb: handleSync};
    cometdSrvc.subscribe(syncSubscriptionKey);

    return {
      model: model,
      sane: true
    };
  })
  // XXX shared global state object
  .service('state', function() {
    return {};
  })
  .service('apiSrvc', function($http, API_URL_PREFIX) {
    return {
      exception: function(data) {
        return $http.post(API_URL_PREFIX+'/exception', data);
      },
      interaction: function(interactionid, data) {
        var url = API_URL_PREFIX+'/interaction/'+interactionid;
        return $http.post(url, data);
      }
    };
  });
<|endoftext|>


<commit_before>/*global require, module */

var request = require('request'),
    log4js = require('log4js');

var log = log4js.getLogger('default');

/**
 * Default Poll Interval used by the backend.
 * We set the default poll interval to high, so
 * if any of the frontend needs faster than this
 * time, then would have to pass in the 'interval'
 * in their request.
 */
var POLL_INTERVAL = 10*1000;

/**
 * Aggregator
 * receives resourceObj, aggregate them,
 * and send poll responses back through socket
 *
 * @param {Object} SockJS connection
 */
function Aggregator (conn) {
  // make "new" optional
  if ( !(this instanceof Aggregator) ) {
    return new Aggregator(conn);
  }

  conn.on('data', onSocketData.bind(this));
  conn.on('close', onSocketClose.bind(this));

  this.connection = conn;

  // WebSocket local resource pool. Key here is the resource id
  // as send from the backend. The FE has to guarantee that the
  // the resource id is unique within a websocket connection.
  this.polledResources = {};
}

/**
 * Checks if the 'id' received from the client is already registered -- This
 * check was added because for whatever reason 'Safari' was sending multiple
 * requests to backend with same ids. As this is happens only once during
 * the start of poll, it's safe to make this check. The assumption here is
 * that the frontend is sending in unique ids within the websocket session.
 *
 * Upon check if it's not duplicate, we invoke doPoll that would make the
 * first call and set the interval for the timeout.
 */
Aggregator.prototype.startPolling = function (resource) {
  // WARN: This assumes that the browser side ids are unique for a websocket session.
  // This check is needed for Safari.
  if(this.polledResources[resource.id]) {
    return;
  }

  resource.interval = resource.interval || POLL_INTERVAL;
  log.debug('Scheduling (' + resource.id + ',' + resource.url + ',' + resource.interval + ')');
  this.polledResources[resource.id] = resource;
  doPoll.bind(this, resource)();
}

/**
 * This method is called regularly by 'doPoll' to register the next interval
 * for timeout. Every resource handle has a flag is used to indicate if the
 * the resource has been requested to be stopped, if it's already stopped, then
 * there is nothing for us to do. If it's not then we go ahead and register
 * the interval timeout.
 */
Aggregator.prototype.scheduleAnotherIteration = function (resource) {
  if (resource.stop) {
    // Don't reschedule another iteration if the resource has been stopped
    return;
  }
  log.debug('Rescheduling (' + resource.id + ',' + resource.url + ',' + resource.interval + ')');
  resource.timerId = setTimeout(doPoll.bind(this, resource), resource.interval);
};

/**
 * Stops the polling of a resource. The resources that has been requested to be stopped
 * is removed from the websocket local poll and the timeouts are cleared and stop flag
 * is set to true.
 */
Aggregator.prototype.stopPolling = function (resource) {
  log.debug('Stopping (' + resource.id + ',' + resource.url + ')');
  var thisResource = removeFromObj(this.polledResources, resource.id);
  if (thisResource === undefined) {
    return;
  }
  clearTimeout(thisResource.timerId);
  thisResource.stop = true;
};

/**
 * Iterates through the websocket local resources and clears the timers and sets
 * the flag. This is called when the websocket is closing the connection.
 */
Aggregator.prototype.stopPollingAll = function() {
  for (var id in this.polledResources) {
    var resource = this.polledResources[id];
    clearTimeout(resource.timerId);
    resource.stop = true;
  }
}

/**
 * Pushes the adapter configuration for templates and plugins to the
 * FE. These configurations are UI specific and hences need to be supported
 * here.
 */
Aggregator.prototype.pushConfiguration = function(resource) {
  var templateid = resource.templateid;
  var pluginid = resource.pluginid;
  var config = {};
  var statusCode = 404;
  try {
    // Check if the configuration is present within the plugin for a template
    config = require('../templates/' + templateid + '/' + pluginid + '.json');
    statusCode = 200;
  } catch(e1) {
   try {
     // Some times there might a plugin that is common across multiple templates
     // in which case, this is stored within the common directory. So, if the
     // template specific plugin check fails, then attempt to get it from common.
     config = require('../templates/common/' + pluginid + '.json');
     statusCode = 200;
   } catch (e2) {
     log.debug("Unable to find template %s, plugin %s", templateid, pluginid);
   }
  }
  this.connection.write(JSON.stringify({
    resource: resource,
    statusCode: statusCode,
    response: config
  }));
}

/**
 * Removes the resource id from the websocket connection local resource pool.
 */
function removeFromObj(obj, key) {
  var el = obj[key];
  delete obj[key];
  return el;
}

/**
 * 'doPoll' is the doer - it makes the resource request call to backend and
 * sends the response back once it receives it. Upon completion of the request
 * it schedulers the interval for next trigger.
 */
function doPoll (resource) {
    var that = this,
        callBack = this.scheduleAnotherIteration.bind(that, resource);

    resource.startTs = Date.now();
    request(resource, function(error, response, body) {
      if (error) {
        emitResponse.call(that, resource, error);
        return;
      }

      emitResponse.call(that, resource, false, response, body);

    }).on('response', callBack)
    .on('error', callBack);
}

/**
 * @private emitResponse
 *
 * sends data back to the client through socket
 *
 * @param  {object} resource that was requested
 * @param  {error|null} error
 * @param  {object} response
 * @param  {string} body
 */
function emitResponse (resource, error, response, body) {
  var timeDiff = Date.now()  - resource.startTs;

  resource.timerId = undefined;
  resource.stop = undefined;
  resource.startTs = undefined;

  if(error) {
    log.debug('[' + timeDiff + 'ms] Error (' + resource.id + ',' + resource.url + ')');
    log.trace('[' + timeDiff + 'ms] Error (' + resource.id + ','
       + resource.url + ') body : (' + error.toString() + ')');
    this.connection.write(JSON.stringify({
      resource: resource,
      error: error,
      warning: error.toString()
    }));

  } else {
    log.debug('[' + timeDiff + 'ms] Success (' + resource.id + ',' + resource.url + ')');
    log.trace('[' + timeDiff + 'ms] Success (' + resource.id + ','
       + resource.url + ') body : (' + JSON.stringify(body) + ')');
    this.connection.write(JSON.stringify({
      resource: resource,
      statusCode: response.statusCode,
      response: body
    }));
  }
}

/**
 * @private onSocketData
 * @param  {string} message received via socket
 */
function onSocketData (message) {
  try {
    message = JSON.parse(message);
    var r = message.resource;

    switch(message.action) {
      case 'template-config':
        log.debug('Adapter config request (' + r.method + ',' + r.id + ',' + r.templateid + ',' + r.pluginid);
        this.pushConfiguration(r);
        break;
      case 'poll-start':
        log.debug ('Poll start (' + r.method + ',' + r.id + ',' + r.url + ')');
        this.startPolling(r);
        break;
      case 'request':
        log.debug ('Single request (' + r.method + ',' + r.id + ',' + r.url + ')');
        r.startTs = Date.now();
        request(r, emitResponse.bind(this, r));
        break;
      case 'poll-stop':
        log.debug ('Poll stop (' + r.id + ',' + r.url + ')');
        this.stopPolling(r);
        break;
    }
  }
  catch (e) {
    log.warn(e);
  }
}

/**
 * @private onSocketClose
 */
function onSocketClose () {
  this.stopPollingAll();
  this.polledResources = {};
}

module.exports = Aggregator;
<commit_msg>Reading the configuration file for adapter on backend everytime without caching<commit_after>/*global require, module */

var request = require('request'),
    fs = require('fs'),
    log4js = require('log4js');

var log = log4js.getLogger('default');

/**
 * Default Poll Interval used by the backend.
 * We set the default poll interval to high, so
 * if any of the frontend needs faster than this
 * time, then would have to pass in the 'interval'
 * in their request.
 */
var POLL_INTERVAL = 10*1000;

/**
 * Aggregator
 * receives resourceObj, aggregate them,
 * and send poll responses back through socket
 *
 * @param {Object} SockJS connection
 */
function Aggregator (conn) {
  // make "new" optional
  if ( !(this instanceof Aggregator) ) {
    return new Aggregator(conn);
  }

  conn.on('data', onSocketData.bind(this));
  conn.on('close', onSocketClose.bind(this));

  this.connection = conn;

  // WebSocket local resource pool. Key here is the resource id
  // as send from the backend. The FE has to guarantee that the
  // the resource id is unique within a websocket connection.
  this.polledResources = {};
}

/**
 * Checks if the 'id' received from the client is already registered -- This
 * check was added because for whatever reason 'Safari' was sending multiple
 * requests to backend with same ids. As this is happens only once during
 * the start of poll, it's safe to make this check. The assumption here is
 * that the frontend is sending in unique ids within the websocket session.
 *
 * Upon check if it's not duplicate, we invoke doPoll that would make the
 * first call and set the interval for the timeout.
 */
Aggregator.prototype.startPolling = function (resource) {
  // WARN: This assumes that the browser side ids are unique for a websocket session.
  // This check is needed for Safari.
  if(this.polledResources[resource.id]) {
    return;
  }

  resource.interval = resource.interval || POLL_INTERVAL;
  log.debug('Scheduling (' + resource.id + ',' + resource.url + ',' + resource.interval + ')');
  this.polledResources[resource.id] = resource;
  doPoll.bind(this, resource)();
}

/**
 * This method is called regularly by 'doPoll' to register the next interval
 * for timeout. Every resource handle has a flag is used to indicate if the
 * the resource has been requested to be stopped, if it's already stopped, then
 * there is nothing for us to do. If it's not then we go ahead and register
 * the interval timeout.
 */
Aggregator.prototype.scheduleAnotherIteration = function (resource) {
  if (resource.stop) {
    // Don't reschedule another iteration if the resource has been stopped
    return;
  }
  log.debug('Rescheduling (' + resource.id + ',' + resource.url + ',' + resource.interval + ')');
  resource.timerId = setTimeout(doPoll.bind(this, resource), resource.interval);
};

/**
 * Stops the polling of a resource. The resources that has been requested to be stopped
 * is removed from the websocket local poll and the timeouts are cleared and stop flag
 * is set to true.
 */
Aggregator.prototype.stopPolling = function (resource) {
  log.debug('Stopping (' + resource.id + ',' + resource.url + ')');
  var thisResource = removeFromObj(this.polledResources, resource.id);
  if (thisResource === undefined) {
    return;
  }
  clearTimeout(thisResource.timerId);
  thisResource.stop = true;
};

/**
 * Iterates through the websocket local resources and clears the timers and sets
 * the flag. This is called when the websocket is closing the connection.
 */
Aggregator.prototype.stopPollingAll = function() {
  for (var id in this.polledResources) {
    var resource = this.polledResources[id];
    clearTimeout(resource.timerId);
    resource.stop = true;
  }
}

/**
 * Pushes the adapter configuration for templates and plugins to the
 * FE. These configurations are UI specific and hences need to be supported
 * here.
 */
Aggregator.prototype.pushConfiguration = function(resource) {
  var templateid = resource.templateid;
  var pluginid = resource.pluginid;
  var config = {};
  var statusCode = 404;
  try {
    // Check if the configuration is present within the plugin for a template
    var file = __dirname + '/../templates/' + templateid + '/' + pluginid + '.json';
    config = JSON.parse(fs.readFileSync(file, 'utf8'));
    statusCode = 200;
  } catch(e1) {
   try {
     // Some times there might a plugin that is common across multiple templates
     // in which case, this is stored within the common directory. So, if the
     // template specific plugin check fails, then attempt to get it from common.
     var file = __dirname + '/../templates/common/' + pluginid + '.json';
     config = JSON.parse(fs.readFileSync(file, 'utf8'));
     statusCode = 200;
   } catch (e2) {
     log.debug("Unable to find template %s, plugin %s", templateid, pluginid);
   }
  }
  this.connection.write(JSON.stringify({
    resource: resource,
    statusCode: statusCode,
    response: config
  }));
}

/**
 * Removes the resource id from the websocket connection local resource pool.
 */
function removeFromObj(obj, key) {
  var el = obj[key];
  delete obj[key];
  return el;
}

/**
 * 'doPoll' is the doer - it makes the resource request call to backend and
 * sends the response back once it receives it. Upon completion of the request
 * it schedulers the interval for next trigger.
 */
function doPoll (resource) {
    var that = this,
        callBack = this.scheduleAnotherIteration.bind(that, resource);

    resource.startTs = Date.now();
    request(resource, function(error, response, body) {
      if (error) {
        emitResponse.call(that, resource, error);
        return;
      }

      emitResponse.call(that, resource, false, response, body);

    }).on('response', callBack)
    .on('error', callBack);
}

/**
 * @private emitResponse
 *
 * sends data back to the client through socket
 *
 * @param  {object} resource that was requested
 * @param  {error|null} error
 * @param  {object} response
 * @param  {string} body
 */
function emitResponse (resource, error, response, body) {
  var timeDiff = Date.now()  - resource.startTs;

  resource.timerId = undefined;
  resource.stop = undefined;
  resource.startTs = undefined;

  if(error) {
    log.debug('[' + timeDiff + 'ms] Error (' + resource.id + ',' + resource.url + ')');
    log.trace('[' + timeDiff + 'ms] Error (' + resource.id + ','
       + resource.url + ') body : (' + error.toString() + ')');
    this.connection.write(JSON.stringify({
      resource: resource,
      error: error,
      warning: error.toString()
    }));

  } else {
    log.debug('[' + timeDiff + 'ms] Success (' + resource.id + ',' + resource.url + ')');
    log.trace('[' + timeDiff + 'ms] Success (' + resource.id + ','
       + resource.url + ') body : (' + JSON.stringify(body) + ')');
    this.connection.write(JSON.stringify({
      resource: resource,
      statusCode: response.statusCode,
      response: body
    }));
  }
}

/**
 * @private onSocketData
 * @param  {string} message received via socket
 */
function onSocketData (message) {
  try {
    message = JSON.parse(message);
    var r = message.resource;

    switch(message.action) {
      case 'template-config':
        log.debug('Adapter config request (' + r.method + ',' + r.id + ',' + r.templateid + ',' + r.pluginid);
        this.pushConfiguration(r);
        break;
      case 'poll-start':
        log.debug ('Poll start (' + r.method + ',' + r.id + ',' + r.url + ')');
        this.startPolling(r);
        break;
      case 'request':
        log.debug ('Single request (' + r.method + ',' + r.id + ',' + r.url + ')');
        r.startTs = Date.now();
        request(r, emitResponse.bind(this, r));
        break;
      case 'poll-stop':
        log.debug ('Poll stop (' + r.id + ',' + r.url + ')');
        this.stopPolling(r);
        break;
    }
  }
  catch (e) {
    log.warn(e);
  }
}

/**
 * @private onSocketClose
 */
function onSocketClose () {
  this.stopPollingAll();
  this.polledResources = {};
}

module.exports = Aggregator;
<|endoftext|>


<commit_before>var Controller = function(shape) {
  this.gameBoard = new TetrisBoard();
  this.gameView = new BrowserView({gameBoard: this.gameBoard});
}
Controller.prototype.startGame = function() {
  this.gameView.animate();
  this.gameBoard.blit();
  this.dropTimeout = setTimeout(this.cycleDropBlock.bind(this), DROP_DELAY);
};
Controller.prototype.cycleDropBlock = function () {
  var dropResult = this.gameBoard.dropBlock();
  if(dropResult == 'landed') {
    this.gameBoard.tetrinimo = new Tetrinimo({
      shape: TETRINIMO_SHAPES.line,
      element: 1
    });
  }
  this.dropTimeout = setTimeout(this.cycleDropBlock.bind(this), DROP_DELAY);
};
<commit_msg>Update controller to set interval on cycleDropBlock<commit_after>var Controller = function(shape) {
  this.gameBoard = new TetrisBoard();
  this.gameView = new BrowserView({
    gameBoard: this.gameBoard,
    cycleDropBlock: this.cycleDropBlock
  });
}
Controller.prototype.startGame = function() {
  this.gameView.animate();
  this.gameBoard.blit();

  this.cycleDropBlock();
};
Controller.prototype.cycleDropBlock = function (args = {}) {
  var dropDelay = args.quickly ? FAST_DROP : DROP_DELAY;
  this.gameBoard.blit();
  if(this.gameBoard.dropInterval) {
    clearInterval(this.gameBoard.dropInterval);
  }
  this.gameBoard.dropInterval = setInterval(
    this.gameBoard.dropBlock.bind(this.gameBoard),
    dropDelay
  );
};
<|endoftext|>


<commit_before>var writeFileSync = require('fs').writeFileSync
var path          = require('path')
var logger        = require('../util/logger')

function hashRev (files, manifest) {
  files = Array.isArray(files) ? files : [files]

  return files.forEach(function (file) {
    var revision
    var matches
    var original

    revision = fs.readFileSync(file, 'utf8')
    original = revision

    matches = revision.match(/url\(\s*['"]?([^'"\)]+)['"]?\s*\)/g)
      || revision.match(/(?:src|href)="(\S+)"/g)

    if (!matches) return

    matches.forEach(function (item) {
      var key
      var results = []

      for (key in manifest) {
        // keyName = newKeys(key)
        // mani = newKeys(manifest[key].path)

        results.push(revision = revision.replace(rkey(key), rkey(manifest[key].path)))
      }
      return results
    })

    if (original !== revision) {
      logger.readyEvent('Hash', 'revised', file, '✔')
      // logger.event('Hash', '✔ ' + file, 'changed')
      return writeFileSync(file, revision)
    }
  })
}

function rkey (fn) {
  return fn.replace('build/', '')
}

module.exports = hashRev
<commit_msg>adjustment to require("fs"}<commit_after>var writeFileSync = require('fs').writeFileSync
var readFileSync  = require('fs').readFileSync
var path          = require('path')
var logger        = require('../util/logger')

function hashRev (files, manifest) {
  files = Array.isArray(files) ? files : [files]

  return files.forEach(function (file) {
    var revision
    var matches
    var original

    revision = readFileSync(file, 'utf8')
    original = revision

    matches = revision.match(/url\(\s*['"]?([^'"\)]+)['"]?\s*\)/g)
      || revision.match(/(?:src|href)="(\S+)"/g)

    if (!matches) return

    matches.forEach(function (item) {
      var key
      var results = []

      for (key in manifest) {
        // keyName = newKeys(key)
        // mani = newKeys(manifest[key].path)

        results.push(revision = revision.replace(rkey(key), rkey(manifest[key].path)))
      }
      return results
    })

    if (original !== revision) {
      logger.readyEvent('Hash', 'revised', file, '✔')
      // logger.event('Hash', '✔ ' + file, 'changed')
      return writeFileSync(file, revision)
    }
  })
}

function rkey (fn) {
  return fn.replace('build/', '')
}

module.exports = hashRev
<|endoftext|>


<commit_before>// Generated by CoffeeScript 1.10.0
define(function() {
  var BibLogParser, LINE_SPLITTER_REGEX, MESSAGE_LEVELS, MULTILINE_ERROR_REGEX, MULTILINE_WARNING_REGEX, SINGLELINE_WARNING_REGEX, consume;
  LINE_SPLITTER_REGEX = /^\[(\d+)].*>\s(INFO|WARN|ERROR)\s-\s(.*)$/;
  MESSAGE_LEVELS = {
    "INFO": "info",
    "WARN": "warning",
    "ERROR": "error"
  };
  BibLogParser = function(text, options) {
    if (typeof text !== 'string') {
      throw new Error("BibLogParser Error: text parameter must be a string");
    }
    this.text = text.replace(/(\r\n)|\r/g, '\n');
    this.options = options || {};
    this.lines = text.split('\n');
  };
  consume = function(logText, regex, process) {
    var iterationCount, match, newEntry, re, result, text;
    text = logText;
    result = [];
    re = regex;
    iterationCount = 0;
    while (match = re.exec(text)) {
      iterationCount += 1;
      if (iterationCount >= 10000) {
        return result;
      }
      newEntry = process(match);
      result.push(newEntry);
      text = (match.input.slice(0, match.index)) + (match.input.slice(match.index + match[0].length + 1, match.input.length));
    }
    return [result, text];
  };
  MULTILINE_WARNING_REGEX = /^Warning--(.+)\n--line (\d+) of file (.+)$/m;
  SINGLELINE_WARNING_REGEX = /^Warning--(.+)$/m;
  MULTILINE_ERROR_REGEX = /^(.*)---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this entry$/m;
  (function() {
    this.parseBibtex = function() {
      var multiLineErrors, multiLineWarnings, ref, ref1, ref2, remainingText, result, singleLineWarnings;
      result = {
        all: [],
        errors: [],
        warnings: [],
        files: [],
        typesetting: []
      };
      ref = consume(this.text, MULTILINE_WARNING_REGEX, function(match) {
        var fileName, fullMatch, lineNumber, message;
        fullMatch = match[0], message = match[1], lineNumber = match[2], fileName = match[3];
        return {
          file: fileName,
          level: "warning",
          message: message,
          line: lineNumber,
          raw: fullMatch
        };
      }), multiLineWarnings = ref[0], remainingText = ref[1];
      result.all = multiLineWarnings;
      result.warnings = multiLineWarnings;
      ref1 = consume(remainingText, SINGLELINE_WARNING_REGEX, function(match) {
        var fullMatch, message;
        fullMatch = match[0], message = match[1];
        return {
          file: null,
          level: "warning",
          message: message,
          line: null,
          raw: fullMatch
        };
      }), singleLineWarnings = ref1[0], remainingText = ref1[1];
      result.all = result.all.concat(singleLineWarnings);
      result.warnings = result.warnings.concat(singleLineWarnings);
      ref2 = consume(remainingText, MULTILINE_ERROR_REGEX, function(match) {
        var fileName, firstMessage, fullMatch, lineNumber, secondMessage;
        fullMatch = match[0], firstMessage = match[1], lineNumber = match[2], fileName = match[3], secondMessage = match[4];
        return {
          file: fileName,
          level: "error",
          message: firstMessage + '\n' + secondMessage,
          line: lineNumber,
          raw: fullMatch
        };
      }), multiLineErrors = ref2[0], remainingText = ref2[1];
      result.all = result.all.concat(multiLineErrors);
      result.errors = multiLineErrors;
      return result;
    };
    this.parseBiber = function() {
      var result;
      result = {
        all: [],
        errors: [],
        warnings: [],
        files: [],
        typesetting: []
      };
      this.lines.forEach(function(line) {
        var _, fileName, fullLine, lineMatch, lineNumber, match, message, messageType, newEntry, realMessage;
        match = line.match(LINE_SPLITTER_REGEX);
        if (match) {
          fullLine = match[0], lineNumber = match[1], messageType = match[2], message = match[3];
          newEntry = {
            file: null,
            level: MESSAGE_LEVELS[messageType] || "INFO",
            message: message,
            line: null,
            raw: fullLine
          };
          lineMatch = newEntry.message.match(/^BibTeX subsystem: \/.+\/(\w+\.\w+)_.+, line (\d+), (.+)$/);
          if (lineMatch && lineMatch.length === 4) {
            _ = lineMatch[0], fileName = lineMatch[1], lineNumber = lineMatch[2], realMessage = lineMatch[3];
            newEntry.file = fileName;
            newEntry.line = lineNumber;
            newEntry.message = realMessage;
          }
          result.all.push(newEntry);
          switch (newEntry.level) {
            case 'error':
              return result.errors.push(newEntry);
            case 'warning':
              return result.warnings.push(newEntry);
          }
        }
      });
      return result;
    };
    return this.parse = function() {
      var firstLine;
      firstLine = this.lines[0];
      if (firstLine.match(/^.*INFO - This is Biber.*$/)) {
        return this.parseBiber();
      } else if (firstLine.match(/^This is BibTeX, Version.+$/)) {
        return this.parseBibtex();
      } else {
        throw new Error("BibLogParser Error: cannot determine whether text is biber or bibtex output");
      }
    };
  }).call(BibLogParser.prototype);
  BibLogParser.parse = function(text, options) {
    return new BibLogParser(text, options).parse();
  };
  return BibLogParser;
});
<commit_msg>Upgrade parser.<commit_after>// Generated by CoffeeScript 1.10.0
define(function() {
  var BAD_CROSS_REFERENCE_REGEX, BibLogParser, LINE_SPLITTER_REGEX, MESSAGE_LEVELS, MULTILINE_ERROR_REGEX, MULTILINE_WARNING_REGEX, SINGLELINE_WARNING_REGEX, consume;
  LINE_SPLITTER_REGEX = /^\[(\d+)].*>\s(INFO|WARN|ERROR)\s-\s(.*)$/;
  MESSAGE_LEVELS = {
    "INFO": "info",
    "WARN": "warning",
    "ERROR": "error"
  };
  BibLogParser = function(text, options) {
    if (typeof text !== 'string') {
      throw new Error("BibLogParser Error: text parameter must be a string");
    }
    this.text = text.replace(/(\r\n)|\r/g, '\n');
    this.options = options || {};
    this.lines = text.split('\n');
  };
  consume = function(logText, regex, process) {
    var iterationCount, match, newEntry, re, result, text;
    text = logText;
    result = [];
    re = regex;
    iterationCount = 0;
    while (match = re.exec(text)) {
      iterationCount += 1;
      if (iterationCount >= 10000) {
        return result;
      }
      newEntry = process(match);
      result.push(newEntry);
      text = (match.input.slice(0, match.index)) + (match.input.slice(match.index + match[0].length + 1, match.input.length));
    }
    return [result, text];
  };
  MULTILINE_WARNING_REGEX = /^Warning--(.+)\n--line (\d+) of file (.+)$/m;
  SINGLELINE_WARNING_REGEX = /^Warning--(.+)$/m;
  MULTILINE_ERROR_REGEX = /^(.*)---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this entry$/m;
  BAD_CROSS_REFERENCE_REGEX = /^(A bad cross reference---entry ".+?"\nrefers to entry.+?, which doesn't exist)$/m;
  (function() {
    this.parseBibtex = function() {
      var crossReferenceErrors, multiLineErrors, multiLineWarnings, ref, ref1, ref2, ref3, remainingText, result, singleLineWarnings;
      result = {
        all: [],
        errors: [],
        warnings: [],
        files: [],
        typesetting: []
      };
      ref = consume(this.text, MULTILINE_WARNING_REGEX, function(match) {
        var fileName, fullMatch, lineNumber, message;
        fullMatch = match[0], message = match[1], lineNumber = match[2], fileName = match[3];
        return {
          file: fileName,
          level: "warning",
          message: message,
          line: lineNumber,
          raw: fullMatch
        };
      }), multiLineWarnings = ref[0], remainingText = ref[1];
      result.all = multiLineWarnings;
      result.warnings = multiLineWarnings;
      ref1 = consume(remainingText, SINGLELINE_WARNING_REGEX, function(match) {
        var fullMatch, message;
        fullMatch = match[0], message = match[1];
        return {
          file: null,
          level: "warning",
          message: message,
          line: null,
          raw: fullMatch
        };
      }), singleLineWarnings = ref1[0], remainingText = ref1[1];
      result.all = result.all.concat(singleLineWarnings);
      result.warnings = result.warnings.concat(singleLineWarnings);
      ref2 = consume(remainingText, MULTILINE_ERROR_REGEX, function(match) {
        var fileName, firstMessage, fullMatch, lineNumber, secondMessage;
        fullMatch = match[0], firstMessage = match[1], lineNumber = match[2], fileName = match[3], secondMessage = match[4];
        return {
          file: fileName,
          level: "error",
          message: firstMessage + '\n' + secondMessage,
          line: lineNumber,
          raw: fullMatch
        };
      }), multiLineErrors = ref2[0], remainingText = ref2[1];
      result.all = result.all.concat(multiLineErrors);
      result.errors = multiLineErrors;
      ref3 = consume(remainingText, BAD_CROSS_REFERENCE_REGEX, function(match) {
        var fullMatch, message;
        fullMatch = match[0], message = match[1];
        return {
          file: null,
          level: "error",
          message: message,
          line: null,
          raw: fullMatch
        };
      }), crossReferenceErrors = ref3[0], remainingText = ref3[1];
      result.all = result.all.concat(crossReferenceErrors);
      result.errors = result.errors.concat(crossReferenceErrors);
      return result;
    };
    this.parseBiber = function() {
      var result;
      result = {
        all: [],
        errors: [],
        warnings: [],
        files: [],
        typesetting: []
      };
      this.lines.forEach(function(line) {
        var _, fileName, fullLine, lineMatch, lineNumber, match, message, messageType, newEntry, realMessage;
        match = line.match(LINE_SPLITTER_REGEX);
        if (match) {
          fullLine = match[0], lineNumber = match[1], messageType = match[2], message = match[3];
          newEntry = {
            file: null,
            level: MESSAGE_LEVELS[messageType] || "INFO",
            message: message,
            line: null,
            raw: fullLine
          };
          lineMatch = newEntry.message.match(/^BibTeX subsystem: \/.+\/(\w+\.\w+)_.+, line (\d+), (.+)$/);
          if (lineMatch && lineMatch.length === 4) {
            _ = lineMatch[0], fileName = lineMatch[1], lineNumber = lineMatch[2], realMessage = lineMatch[3];
            newEntry.file = fileName;
            newEntry.line = lineNumber;
            newEntry.message = realMessage;
          }
          result.all.push(newEntry);
          switch (newEntry.level) {
            case 'error':
              return result.errors.push(newEntry);
            case 'warning':
              return result.warnings.push(newEntry);
          }
        }
      });
      return result;
    };
    return this.parse = function() {
      var firstLine;
      firstLine = this.lines[0];
      if (firstLine.match(/^.*INFO - This is Biber.*$/)) {
        return this.parseBiber();
      } else if (firstLine.match(/^This is BibTeX, Version.+$/)) {
        return this.parseBibtex();
      } else {
        throw new Error("BibLogParser Error: cannot determine whether text is biber or bibtex output");
      }
    };
  }).call(BibLogParser.prototype);
  BibLogParser.parse = function(text, options) {
    return new BibLogParser(text, options).parse();
  };
  return BibLogParser;
});
<|endoftext|>


<commit_before>var XML = require('pixl-xml');
var request = require('request');

module.exports = {getWeatherData, getWeatherDataByCoords};

function getWeatherData(callback) {
  var url = 'http://www.yr.no/stad/Noreg/Sør-Trøndelag/Trondheim/Trondheim/varsel.xml';
  request(url, function(error, response, body) {
    if (error) {
      callback(error);
    } else if (response.statusCode != 200) {
      callback(new Error(response.statusCode));
    } else {
      var xmlDoc = XML.parse(body);
      var currentTimePeriod = xmlDoc.forecast.tabular.time[0];

      var temperature = currentTimePeriod.temperature.value;
      var windSpeed = currentTimePeriod.windSpeed.mps;
      var effectiveTemperature = getEffectiveTemperature(temperature, windSpeed);

      var weatherData = {
        temperature: Math.round(temperature),
        effectiveTemperature: Math.round(effectiveTemperature),
        windSpeed: windSpeed,
        windDirection: currentTimePeriod.windDirection.code,
        symbol: currentTimePeriod.symbol.number,
      };

      isItShortsWeather(weatherData, callback);
    }
  });
}

function getWeatherDataByCoords(lat, long, callback) {
  var url = 'http://api.met.no/weatherapi/locationforecast/1.9/?lat=' + lat + ';lon=' + long;
  request(url, function(error, response, body) {
    if (error) {
      callback(error);
    } else if (response.statusCode != 200) {
      callback(new Error(response.statusCode));
    } else {
      var xmlDoc = XML.parse(body);
      var currentTimePeriod = xmlDoc.product.time[0].location;

      var temperature = currentTimePeriod.temperature.value;
      var windSpeed = currentTimePeriod.windSpeed.mps;
      var effectiveTemperature = getEffectiveTemperature(temperature, windSpeed);

      // Symbols are weird in the met.no API
      // The first time data-point doesn't seem to have symbol data,
      // but the next one usually does.
      var symbol = xmlDoc.product.time.filter(function(timePeriod) {
        return timePeriod.location.hasOwnProperty('symbol');
      })[0].location.symbol.number;

      var weatherData = {
        temperature: Math.round(temperature),
        effectiveTemperature: Math.round(effectiveTemperature),
        windSpeed: windSpeed,
        windDirection: currentTimePeriod.windDirection.name,
        symbol: symbol
      };

      isItShortsWeather(weatherData, callback);
    }
  });
}

function isItShortsWeather(data, callback) {
  // Symbol numbers documented here:
  // http://om.yr.no/forklaring/symbol/
  const rainNumbers = [40, 5, 41, 24, 6, 25, 42, 7, 43, 26, 20, 27, 46, 9, 10, 30, 22, 11, 47, 12, 48, 31, 23, 32];
  const snowNumbers = [44, 8, 45, 28, 21, 29, 49, 13, 50, 33, 14, 34];

  // Effective temperature above 15 and sunny
  if (data.effectiveTemperature >= 15 && data.symbol > 0 && data.symbol <= 3) {
    data.weather = 'shorts';
  }

  // Effective temperature above 18 and cloudy
  if (data.effectiveTemperature >= 18 && data.symbol == 4) {
    data.weather = 'shorts';
  }

  if (data.weather !== 'shorts') {
    if ((data.symbol > 0 && data.symbol <= 4) || data.symbol == 15) {
      if (data.effectiveTemperature <= 0) {
        data.weather = 'freezing';
      } else {
        data.weather = 'pants';
      }
    }
    if (rainNumbers.indexOf(+data.symbol) !== -1) {
      data.weather = 'rain';
    }
    if (snowNumbers.indexOf(+data.symbol) !== -1) {
      data.weather = 'snow';
    }
  }

  callback(null, data);
}

// Adapted from http://om.yr.no/forklaring/symbol/effektiv-temperatur/
function getEffectiveTemperature(temperature, windSpeed) {
  return 13.12 +
         0.6215 * temperature -
         11.37 * Math.pow(windSpeed, 0.16) +
         0.3965 * temperature * Math.pow(windSpeed, 0.16);
}
<commit_msg>Allow all 2xx status codes<commit_after>var XML = require('pixl-xml');
var request = require('request');

module.exports = {getWeatherData, getWeatherDataByCoords};

function getWeatherData(callback) {
  var url = 'http://www.yr.no/stad/Noreg/Sør-Trøndelag/Trondheim/Trondheim/varsel.xml';
  request(url, function(error, response, body) {
    if (error) {
      callback(error);
    } else if (response.statusCode != 200) {
      callback(new Error(response.statusCode));
    } else {
      var xmlDoc = XML.parse(body);
      var currentTimePeriod = xmlDoc.forecast.tabular.time[0];

      var temperature = currentTimePeriod.temperature.value;
      var windSpeed = currentTimePeriod.windSpeed.mps;
      var effectiveTemperature = getEffectiveTemperature(temperature, windSpeed);

      var weatherData = {
        temperature: Math.round(temperature),
        effectiveTemperature: Math.round(effectiveTemperature),
        windSpeed: windSpeed,
        windDirection: currentTimePeriod.windDirection.code,
        symbol: currentTimePeriod.symbol.number,
      };

      isItShortsWeather(weatherData, callback);
    }
  });
}

function getWeatherDataByCoords(lat, long, callback) {
  var url = 'http://api.met.no/weatherapi/locationforecast/1.9/?lat=' + lat + ';lon=' + long;
  request(url, function(error, response, body) {
    if (error) {
      callback(error);
    } else if (response.statusCode < 200 || response.statusCode >= 300) {
      callback(new Error(response.statusCode));
    } else {
      var xmlDoc = XML.parse(body);
      var currentTimePeriod = xmlDoc.product.time[0].location;

      var temperature = currentTimePeriod.temperature.value;
      var windSpeed = currentTimePeriod.windSpeed.mps;
      var effectiveTemperature = getEffectiveTemperature(temperature, windSpeed);

      // Symbols are weird in the met.no API
      // The first time data-point doesn't seem to have symbol data,
      // but the next one usually does.
      var symbol = xmlDoc.product.time.filter(function(timePeriod) {
        return timePeriod.location.hasOwnProperty('symbol');
      })[0].location.symbol.number;

      var weatherData = {
        temperature: Math.round(temperature),
        effectiveTemperature: Math.round(effectiveTemperature),
        windSpeed: windSpeed,
        windDirection: currentTimePeriod.windDirection.name,
        symbol: symbol
      };

      isItShortsWeather(weatherData, callback);
    }
  });
}

function isItShortsWeather(data, callback) {
  // Symbol numbers documented here:
  // http://om.yr.no/forklaring/symbol/
  const rainNumbers = [40, 5, 41, 24, 6, 25, 42, 7, 43, 26, 20, 27, 46, 9, 10, 30, 22, 11, 47, 12, 48, 31, 23, 32];
  const snowNumbers = [44, 8, 45, 28, 21, 29, 49, 13, 50, 33, 14, 34];

  // Effective temperature above 15 and sunny
  if (data.effectiveTemperature >= 15 && data.symbol > 0 && data.symbol <= 3) {
    data.weather = 'shorts';
  }

  // Effective temperature above 18 and cloudy
  if (data.effectiveTemperature >= 18 && data.symbol == 4) {
    data.weather = 'shorts';
  }

  if (data.weather !== 'shorts') {
    if ((data.symbol > 0 && data.symbol <= 4) || data.symbol == 15) {
      if (data.effectiveTemperature <= 0) {
        data.weather = 'freezing';
      } else {
        data.weather = 'pants';
      }
    }
    if (rainNumbers.indexOf(+data.symbol) !== -1) {
      data.weather = 'rain';
    }
    if (snowNumbers.indexOf(+data.symbol) !== -1) {
      data.weather = 'snow';
    }
  }

  callback(null, data);
}

// Adapted from http://om.yr.no/forklaring/symbol/effektiv-temperatur/
function getEffectiveTemperature(temperature, windSpeed) {
  return 13.12 +
         0.6215 * temperature -
         11.37 * Math.pow(windSpeed, 0.16) +
         0.3965 * temperature * Math.pow(windSpeed, 0.16);
}
<|endoftext|>