hexlet-codebattle/codebattle

View on GitHub
services/app/apps/codebattle/assets/js/widgets/middlewares/Main.js

Summary

Maintainability
A
55 mins
Test Coverage
import Gon from 'gon';
import { camelizeKeys } from 'humps';
import { Presence } from 'phoenix';

import { makeGameUrl } from '@/utils/urlBuilders';

import socket from '../../socket';
import { actions } from '../slices';

const players = Gon.getAsset('players') || [];
const currentUser = Gon.getAsset('current_user') || {};

let channel;

const mapViewerStateToWeight = {
  online: 0,
  lobby: 1,
  task: 2,
  tournament: 3,
  watching: 4,
  playing: 5,
};

const getMajorState = metas => metas.reduce(
    (state, item) => (mapViewerStateToWeight[state] > mapViewerStateToWeight[item.state]
        ? state
        : item.state),
    'online',
  );

const getUserStateByPath = () => {
  const { pathname } = document.location;

  if (pathname.startsWith('/tournament')) {
    return { state: 'tournament' };
  }

  if (pathname.startsWith('/games')) {
    const state = players.some(player => player.id === currentUser.id)
      ? 'playing'
      : 'watching';

    return {
      state,
    };
  }

  if (pathname === '/') {
    return {
      state: 'lobby',
    };
  }

  if (pathname.startsWith('/tasks')) {
    return {
      state: 'task',
    };
  }

  return { state: 'online' };
};

const listBy = (id, { metas: [first, ...rest] }) => {
  const userInfo = {
    ...first,
    id: Number(id),
    count: rest.length + 1,
    currentState: getMajorState([first, ...rest]),
  };

  return userInfo;
};

const camelizeKeysAndDispatch = (dispatch, actionCreator) => data => dispatch(actionCreator(camelizeKeys(data)));

const redirectToNewGame = data => (_dispatch, getState) => {
  const { followPaused } = getState().gameUI;

  if (!followPaused) {
    window.location.replace(makeGameUrl(data.activeGameId));
  }
};

const initPresence = followId => dispatch => {
  channel = socket.channel('main', {
    ...getUserStateByPath(),
    follow_id: followId,
  });
  const presence = new Presence(channel);

  presence.onSync(() => {
    const list = presence.list(listBy);
    camelizeKeysAndDispatch(dispatch, actions.syncPresenceList)(list);
  });

  channel.join().receive('ok', () => {
    camelizeKeysAndDispatch(dispatch, actions.syncPresenceList);
  });

  channel.onError(() => dispatch(actions.updateMainChannelState(false)));

  const ref = channel.on('user:game_created', data => {
    camelizeKeysAndDispatch(dispatch, actions.setActiveGameId)(data);
    dispatch(redirectToNewGame(camelizeKeys(data)));
  });

  return () => {
    channel.off('user:game_created', ref);
  };
};

export const changePresenceState = state => () => {
  channel.push('change_presence_state', { state });
};

export const changePresenceUser = user => () => {
  channel.push('change_presence_user', { user });
};

export const followUser = userId => (dispatch, getState) => {
  channel.push('user:follow', { user_id: userId }).receive('ok', payload => {
    const data = camelizeKeys(payload);

    camelizeKeysAndDispatch(dispatch, actions.followUser)(data);

    if (!data.activeGameId) return;

    camelizeKeysAndDispatch(dispatch, actions.setActiveGameId)(data);

    if (data.activeGameId !== getState().game?.gameStatus?.gameId) {
      setTimeout(() => {
        window.location.replace(makeGameUrl(data.activeGameId));
      }, 1000);
    }
  });
};

export const unfollowUser = userId => dispatch => {
  channel.push('user:unfollow', { user_id: userId });
  camelizeKeysAndDispatch(dispatch, actions.unfollowUser)();
};

export default initPresence;