<commit_before>import variantConfirm from './variant';
import * as hookRepo from './hookRepo';
import * as seekRepo from './seekRepo';
import { make as makeStores, Stores } from './store';
import * as xhr from './xhr';
import * as poolRangeStorage from './poolRangeStorage';
import { LobbyOpts, LobbyData, Tab, Mode, Sort, Filter, Hook, Seek } from './interfaces';
import LobbySocket from './socket';

export default class LobbyController {

  opts: LobbyOpts;
  data: LobbyData;
  playban: any;
  currentGame: any;
  socket: LobbySocket;
  stores: Stores;
  tab: Tab;
  mode: Mode;
  sort: Sort;
  filterOpen: boolean = false;
  stepHooks: Hook[] = [];
  stepping: boolean = false;
  redirecting: boolean = false;
  poolMember?: any;
  trans: Trans;
  redraw: () => void;

  private poolInStorage: LichessStorage;
  private flushHooksTimeout?: number;
  private alreadyWatching: string[] = [];

  constructor(opts: LobbyOpts, redraw: () => void) {
    this.opts = opts;
    this.data = opts.data;
    this.data.hooks = [];
    this.playban = opts.playban;
    this.currentGame = opts.currentGame;
    this.redraw = redraw;

    hookRepo.initAll(this);
    seekRepo.initAll(this);
    this.socket = new LobbySocket(opts.socketSend, this);

    this.stores = makeStores(this.data.me ? this.data.me.username.toLowerCase() : null);
    this.tab = this.stores.tab.get(),
    this.mode = this.stores.mode.get(),
    this.sort = this.stores.sort.get(),
    this.trans = window.lichess.trans(opts.i18n);

    this.poolInStorage = window.lichess.storage.make('lobby.pool-in');
    this.poolInStorage.listen(() => { // when another tab joins a pool
      this.leavePool();
      redraw();
    });
    this.flushHooksSchedule();

    this.startWatching();

    if (this.playban) setTimeout(window.lichess.reload, this.playban.remainingSeconds * 1000);
    else {
      setInterval(() => {
        if (this.poolMember) this.poolIn();
        else if (this.tab === 'real_time' && !this.data.hooks.length) this.socket.realTimeIn();
      }, 10 * 1000);
      this.onNewOpponent();
    }

    window.lichess.pubsub.on('socket.open', () => {
      if (this.tab === 'real_time') {
        this.data.hooks = [];
        this.socket.realTimeIn();
      } else if (this.tab === 'pools' && this.poolMember) this.poolIn();
    });

    window.addEventListener('beforeunload', () => {
      if (this.poolMember) this.socket.poolOut(this.poolMember);
    });
  }

  private doFlushHooks() {
    this.stepHooks = this.data.hooks.slice(0);
    if (this.tab === 'real_time') this.redraw();
  };

  flushHooks = (now: boolean) => {
    if (this.flushHooksTimeout) clearTimeout(this.flushHooksTimeout);
    if (now) this.doFlushHooks();
    else {
      this.stepping = true;
      if (this.tab === 'real_time') this.redraw();
      setTimeout(() => {
        this.stepping = false;
        this.doFlushHooks();
      }, 500);
    }
    this.flushHooksTimeout = this.flushHooksSchedule();
  };

  private flushHooksSchedule(): number {
    return setTimeout(this.flushHooks, 8000);
  }

  setTab = (tab: Tab) => {
    if (tab !== this.tab) {
      if (tab === 'seeks') xhr.seeks().then(this.setSeeks);
      else if (tab === 'real_time') this.socket.realTimeIn();
      else if (this.tab === 'real_time') {
        this.socket.realTimeOut();
        this.data.hooks = [];
      }
      this.tab = this.stores.tab.set(tab);
    }
    this.filterOpen = false;
  };

  setMode = (mode: Mode) => {
    this.mode = this.stores.mode.set(mode);
    this.filterOpen = false;
  };

  setSort = (sort: Sort) => {
    this.sort = this.stores.sort.set(sort);
  };

  toggleFilter = () => {
    this.filterOpen = !this.filterOpen;
  };

  setFilter = (filter: Filter) => {
    this.data.filter = filter;
    this.flushHooks(true);
    if (this.tab !== 'real_time') this.redraw();
  };

  clickHook = (id: string) => {
    const hook = hookRepo.find(this, id);
    if (!hook || hook.disabled || this.stepping || this.redirecting) return;
    if (hook.action === 'cancel' || variantConfirm(hook.variant)) this.socket.send(hook.action, hook.id);
  };

  clickSeek = (id: string) => {
    const seek = seekRepo.find(this, id);
    if (!seek || this.redirecting) return;
    if (seek.action === 'cancelSeek' || variantConfirm(seek.variant)) this.socket.send(seek.action, seek.id);
  };

  setSeeks = (seeks: Seek[]) => {
    this.data.seeks = seeks;
    seekRepo.initAll(this);
    this.redraw();
  };

  clickPool = (id: string) => {
    if (!this.data.me) {
      xhr.anonPoolSeek(this.data.pools.find(function(p) {
        return p.id === id;
      }));
      this.setTab('real_time');
    } else if (this.poolMember && this.poolMember.id === id) this.leavePool();
    else {
      this.enterPool({ id });
      this.redraw();
    }
  };

  enterPool = (member) => {
    poolRangeStorage.set(member.id, member.range);
    this.setTab('pools');
    this.poolMember = member;
    this.poolIn();
  };

  leavePool = () => {
    if (!this.poolMember) return;
    this.socket.poolOut(this.poolMember);
    this.poolMember = null;
    this.redraw();
  };

  poolIn = () => {
    this.poolInStorage.set('1');
    this.socket.poolIn(this.poolMember);
  };

  gameActivity = (gameId) => {
    if (this.data.nowPlaying.find(function(p) {
      return p.gameId === gameId;
    })) xhr.nowPlaying().then(povs => {
      this.data.nowPlaying = povs;
      this.startWatching();
      this.redraw();
    });
  };

  private startWatching() {
    const newIds = this.data.nowPlaying.map(function(p) {
      return p.gameId;
    }).filter((id: string) => {
      return this.alreadyWatching.indexOf(id) === -1;
    });
    if (newIds.length) {
      setTimeout(() => {
        this.socket.send("startWatching", newIds.join(' '));
      }, 2000);
      newIds.forEach(id => this.alreadyWatching.push(id));
    }
  };

  setRedirecting = () => {
    this.redirecting = true;
    setTimeout(() => this.redirecting = false, 2000);
  };

  awake = () => {
    switch (this.tab) {
      case 'real_time':
        this.data.hooks = [];
        this.socket.realTimeIn();
        break;
      case 'seeks':
        xhr.seeks().then(this.setSeeks);
        break;
    }
  };

  // after click on round "new opponent" button
  private onNewOpponent() {
    if (location.hash.indexOf('#pool/') === 0) {
      const regex = /^#pool\/(\d+\+\d+)$/,
      match = regex.exec(location.hash),
      member: any = { id: match![1] },
      range = poolRangeStorage.get(member.id);
      if (range) member.range = range;
      if (match) {
        this.setTab('pools');
        this.enterPool(member);
        history.replaceState(null, '', '/');
      }
    }
  }
}
<commit_msg>fix IE 11 lobby enter bug - closes #3280<commit_after>import variantConfirm from './variant';
import * as hookRepo from './hookRepo';
import * as seekRepo from './seekRepo';
import { make as makeStores, Stores } from './store';
import * as xhr from './xhr';
import * as poolRangeStorage from './poolRangeStorage';
import { LobbyOpts, LobbyData, Tab, Mode, Sort, Filter, Hook, Seek } from './interfaces';
import LobbySocket from './socket';

const li = window.lichess;

export default class LobbyController {

  opts: LobbyOpts;
  data: LobbyData;
  playban: any;
  currentGame: any;
  socket: LobbySocket;
  stores: Stores;
  tab: Tab;
  mode: Mode;
  sort: Sort;
  filterOpen: boolean = false;
  stepHooks: Hook[] = [];
  stepping: boolean = false;
  redirecting: boolean = false;
  poolMember?: any;
  trans: Trans;
  redraw: () => void;

  private poolInStorage: LichessStorage;
  private flushHooksTimeout?: number;
  private alreadyWatching: string[] = [];

  constructor(opts: LobbyOpts, redraw: () => void) {
    this.opts = opts;
    this.data = opts.data;
    this.data.hooks = [];
    this.playban = opts.playban;
    this.currentGame = opts.currentGame;
    this.redraw = redraw;

    hookRepo.initAll(this);
    seekRepo.initAll(this);
    this.socket = new LobbySocket(opts.socketSend, this);

    this.stores = makeStores(this.data.me ? this.data.me.username.toLowerCase() : null);
    this.tab = this.stores.tab.get(),
    this.mode = this.stores.mode.get(),
    this.sort = this.stores.sort.get(),
    this.trans = li.trans(opts.i18n);

    this.poolInStorage = li.storage.make('lobby.pool-in');
    this.poolInStorage.listen(e => { // when another tab joins a pool
      if (!e.newValue || e.newValue === li.StrongSocket.sri) return; // same tab, doh, IE 11
      this.leavePool();
      redraw();
    });
    this.flushHooksSchedule();

    this.startWatching();

    if (this.playban) setTimeout(li.reload, this.playban.remainingSeconds * 1000);
    else {
      setInterval(() => {
        if (this.poolMember) this.poolIn();
        else if (this.tab === 'real_time' && !this.data.hooks.length) this.socket.realTimeIn();
      }, 10 * 1000);
      this.onNewOpponent();
    }

    li.pubsub.on('socket.open', () => {
      if (this.tab === 'real_time') {
        this.data.hooks = [];
        this.socket.realTimeIn();
      } else if (this.tab === 'pools' && this.poolMember) this.poolIn();
    });

    window.addEventListener('beforeunload', () => {
      if (this.poolMember) this.socket.poolOut(this.poolMember);
    });
  }

  private doFlushHooks() {
    this.stepHooks = this.data.hooks.slice(0);
    if (this.tab === 'real_time') this.redraw();
  };

  flushHooks = (now: boolean) => {
    if (this.flushHooksTimeout) clearTimeout(this.flushHooksTimeout);
    if (now) this.doFlushHooks();
    else {
      this.stepping = true;
      if (this.tab === 'real_time') this.redraw();
      setTimeout(() => {
        this.stepping = false;
        this.doFlushHooks();
      }, 500);
    }
    this.flushHooksTimeout = this.flushHooksSchedule();
  };

  private flushHooksSchedule(): number {
    return setTimeout(this.flushHooks, 8000);
  }

  setTab = (tab: Tab) => {
    if (tab !== this.tab) {
      if (tab === 'seeks') xhr.seeks().then(this.setSeeks);
      else if (tab === 'real_time') this.socket.realTimeIn();
      else if (this.tab === 'real_time') {
        this.socket.realTimeOut();
        this.data.hooks = [];
      }
      this.tab = this.stores.tab.set(tab);
    }
    this.filterOpen = false;
  };

  setMode = (mode: Mode) => {
    this.mode = this.stores.mode.set(mode);
    this.filterOpen = false;
  };

  setSort = (sort: Sort) => {
    this.sort = this.stores.sort.set(sort);
  };

  toggleFilter = () => {
    this.filterOpen = !this.filterOpen;
  };

  setFilter = (filter: Filter) => {
    this.data.filter = filter;
    this.flushHooks(true);
    if (this.tab !== 'real_time') this.redraw();
  };

  clickHook = (id: string) => {
    const hook = hookRepo.find(this, id);
    if (!hook || hook.disabled || this.stepping || this.redirecting) return;
    if (hook.action === 'cancel' || variantConfirm(hook.variant)) this.socket.send(hook.action, hook.id);
  };

  clickSeek = (id: string) => {
    const seek = seekRepo.find(this, id);
    if (!seek || this.redirecting) return;
    if (seek.action === 'cancelSeek' || variantConfirm(seek.variant)) this.socket.send(seek.action, seek.id);
  };

  setSeeks = (seeks: Seek[]) => {
    this.data.seeks = seeks;
    seekRepo.initAll(this);
    this.redraw();
  };

  clickPool = (id: string) => {
    if (!this.data.me) {
      xhr.anonPoolSeek(this.data.pools.find(function(p) {
        return p.id === id;
      }));
      this.setTab('real_time');
    } else if (this.poolMember && this.poolMember.id === id) this.leavePool();
    else {
      this.enterPool({ id });
      this.redraw();
    }
  };

  enterPool = (member) => {
    poolRangeStorage.set(member.id, member.range);
    this.setTab('pools');
    this.poolMember = member;
    this.poolIn();
  };

  leavePool = () => {
    if (!this.poolMember) return;
    this.socket.poolOut(this.poolMember);
    this.poolMember = null;
    this.redraw();
  };

  poolIn = () => {
    this.poolInStorage.set(li.StrongSocket.sri);
    this.socket.poolIn(this.poolMember);
  };

  gameActivity = (gameId) => {
    if (this.data.nowPlaying.find(function(p) {
      return p.gameId === gameId;
    })) xhr.nowPlaying().then(povs => {
      this.data.nowPlaying = povs;
      this.startWatching();
      this.redraw();
    });
  };

  private startWatching() {
    const newIds = this.data.nowPlaying.map(function(p) {
      return p.gameId;
    }).filter((id: string) => {
      return this.alreadyWatching.indexOf(id) === -1;
    });
    if (newIds.length) {
      setTimeout(() => {
        this.socket.send("startWatching", newIds.join(' '));
      }, 2000);
      newIds.forEach(id => this.alreadyWatching.push(id));
    }
  };

  setRedirecting = () => {
    this.redirecting = true;
    setTimeout(() => this.redirecting = false, 2000);
  };

  awake = () => {
    switch (this.tab) {
      case 'real_time':
        this.data.hooks = [];
        this.socket.realTimeIn();
        break;
      case 'seeks':
        xhr.seeks().then(this.setSeeks);
        break;
    }
  };

  // after click on round "new opponent" button
  private onNewOpponent() {
    if (location.hash.indexOf('#pool/') === 0) {
      const regex = /^#pool\/(\d+\+\d+)$/,
      match = regex.exec(location.hash),
      member: any = { id: match![1] },
      range = poolRangeStorage.get(member.id);
      if (range) member.range = range;
      if (match) {
        this.setTab('pools');
        this.enterPool(member);
        history.replaceState(null, '', '/');
      }
    }
  }
}

{'fraction_non_alphanumeric': 0.17375860282690742, 'fraction_numerical': 0.005254199659587064, 'mean_word_length': 2.7486823855755893, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '728967', 'n_tokens_mistral': 4696, 'n_tokens_neox': 4600, 'n_words': 874}