<commit_before>/**
 *    Copyright (c) ppy Pty Ltd <<EMAIL>>.
 *
 *    This file is part of osu!web. osu!web is distributed with the hope of
 *    attracting more community contributions to the core ecosystem of osu!.
 *
 *    osu!web is free software: you can redistribute it and/or modify
 *    it under the terms of the Affero GNU General Public License version 3
 *    as published by the Free Software Foundation.
 *
 *    osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
 *    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *    See the GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 */

import * as _ from 'lodash';
import {computed, observable} from 'mobx';
import NotificationJson from 'interfaces/notification-json';
import XHRCollection from 'interfaces/xhr-collection';
import Notification from 'models/notification';

interface NotificationBundleJson {
  has_more: boolean;
  notifications: NotificationJson[];
  unread_count: number;
}

interface NotificationEventReadJson {
  event: string;
  data: NotificationReadJson;
}

interface NotificationEventNewJson {
  data: NotificationJson;
  event: string;
}

interface NotificationReadJson {
  ids: number[];
}

interface TimeoutCollection {
  [key: string]: number;
}

const isNotificationEventReadJson = (arg: any): arg is NotificationEventReadJson => {
  return arg.event === 'read';
};

const isNotificationEventNewJson = (arg: any): arg is NotificationEventNewJson => {
  return arg.event === 'new';
};

export default class Worker {
  @observable actualUnreadCount: number = -1;
  @observable hasData: boolean = false;
  @observable hasMore: boolean = true;
  @observable items = observable.map<number, Notification>();
  @observable loadingMore: boolean = false;
  @observable pmNotification = new Notification(-1);
  userId: number | null = null;
  private active: boolean = false;
  private timeout: TimeoutCollection = {};
  private ws?: WebSocket;
  private xhr: XHRCollection = {};

  boot = () => {
    this.active = this.userId != null;
    this.updatePmNotification();
    this.loadMore();
    this.connectWebSocket();
    $(document).on('turbolinks:load', this.updatePmNotification);
  }

  connectWebSocket = () => {
    if (!this.active) {
      return;
    }

    if (this.timeout.connectWebSocket != null) {
      clearTimeout(this.timeout.connectWebSocket);
    }

    const tokenEl = document.querySelector('meta[name=csrf-token]');

    if (tokenEl == null) {
      return;
    }

    const token = tokenEl.getAttribute('content');
    let url = process.env.WEBSOCKET_URL;
    if (url == null) {
      const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
      url = `${protocol}//${window.location.host}/home/notifications/live`;
    }
    this.ws = new WebSocket(`${url}?csrf=${token}`);
    this.ws.onclose = this.delayedConnectWebSocket;
    this.ws.onmessage = this.handleNewEvent;
  }

  delayedConnectWebSocket = () => {
    if (!this.active) {
      return;
    }

    this.timeout.connectWebSocket = setTimeout(this.connectWebSocket, 10000);
  }

  delayedRetryInitialLoadMore = () => {
    if (!this.active || this.hasData) {
      return;
    }

    this.timeout.loadMore = setTimeout(this.loadMore, 10000);
  }

  destroy = () => {
    this.active = false;
    _.forEach(this.xhr, (xhr) => xhr.abort());
    _.forEach(this.timeout, (timeout) => clearTimeout(timeout));

    if (this.ws != null) {
      this.ws.close();
    }

    $(document).off('turbolinks:load', this.updatePmNotification);
  }

  handleNewEvent = (event: MessageEvent) => {
    let data: any;
<commit_msg>Only reconnect on error and clear on disconnect<commit_after>/**
 *    Copyright (c) ppy Pty Ltd <<EMAIL>>.
 *
 *    This file is part of osu!web. osu!web is distributed with the hope of
 *    attracting more community contributions to the core ecosystem of osu!.
 *
 *    osu!web is free software: you can redistribute it and/or modify
 *    it under the terms of the Affero GNU General Public License version 3
 *    as published by the Free Software Foundation.
 *
 *    osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
 *    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *    See the GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 */

import * as _ from 'lodash';
import {computed, observable} from 'mobx';
import NotificationJson from 'interfaces/notification-json';
import XHRCollection from 'interfaces/xhr-collection';
import Notification from 'models/notification';

interface NotificationBundleJson {
  has_more: boolean;
  notifications: NotificationJson[];
  unread_count: number;
}

interface NotificationEventReadJson {
  event: string;
  data: NotificationReadJson;
}

interface NotificationEventNewJson {
  data: NotificationJson;
  event: string;
}

interface NotificationReadJson {
  ids: number[];
}

interface TimeoutCollection {
  [key: string]: number;
}

const isNotificationEventReadJson = (arg: any): arg is NotificationEventReadJson => {
  return arg.event === 'read';
};

const isNotificationEventNewJson = (arg: any): arg is NotificationEventNewJson => {
  return arg.event === 'new';
};

export default class Worker {
  @observable actualUnreadCount: number = -1;
  @observable hasData: boolean = false;
  @observable hasMore: boolean = true;
  @observable items = observable.map<number, Notification>();
  @observable loadingMore: boolean = false;
  @observable pmNotification = new Notification(-1);
  userId: number | null = null;
  private active: boolean = false;
  private timeout: TimeoutCollection = {};
  private ws?: WebSocket;
  private xhr: XHRCollection = {};

  boot = () => {
    this.active = this.userId != null;
    this.updatePmNotification();
    this.loadMore();
    this.connectWebSocket();
    $(document).on('turbolinks:load', this.updatePmNotification);
  }

  connectWebSocket = () => {
    if (!this.active) {
      return;
    }

    if (this.timeout.connectWebSocket != null) {
      clearTimeout(this.timeout.connectWebSocket);
    }

    const tokenEl = document.querySelector('meta[name=csrf-token]');

    if (tokenEl == null) {
      return;
    }

    const token = tokenEl.getAttribute('content');
    let url = process.env.WEBSOCKET_URL;
    if (url == null) {
      const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
      url = `${protocol}//${window.location.host}/home/notifications/live`;
    }
    this.ws = new WebSocket(`${url}?csrf=${token}`);
    this.ws.onclose = this.destroy;
    this.ws.onerror = this.delayedConnectWebSocket;
    this.ws.onmessage = this.handleNewEvent;
  }

  delayedConnectWebSocket = () => {
    if (!this.active) {
      return;
    }

    this.timeout.connectWebSocket = setTimeout(this.connectWebSocket, 10000);
  }

  delayedRetryInitialLoadMore = () => {
    if (!this.active || this.hasData) {
      return;
    }

    this.timeout.loadMore = setTimeout(this.loadMore, 10000);
  }

  destroy = () => {
    this.active = false;
    this.items = observable.map();
    _.forEach(this.xhr, (xhr) => xhr.abort());
    _.forEach(this.timeout, (timeout) => clearTimeout(timeout));

    if (this.ws != null) {
      this.ws.close();
    }

    $(document).off('turbolinks:load', this.updatePmNotification);
  }

  handleNewEvent = (event: MessageEvent) => {
    let data: any;

{'fraction_non_alphanumeric': 0.12521128591860617, 'fraction_numerical': 0.003380574697698609, 'mean_word_length': 3.6142771445710857, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '6339596', 'n_tokens_mistral': 2348, 'n_tokens_neox': 2287, 'n_words': 627}