Kopie van https://gitlab.com/studieverenigingvia/ict/centurion met een paar aanpassingen
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
8.8 KiB
335 lines
8.8 KiB
import io from "socket.io-client";
|
|
import {useEffect, useState} from "react";
|
|
import {parse as parseQueryString, stringify as stringifyQueryString} from 'query-string';
|
|
|
|
import {Config, Room, RoomOptions, Timeline} from "../types/types";
|
|
import {Sub, useSub} from "../util/sub";
|
|
|
|
class Connection {
|
|
url = '/';
|
|
|
|
socket: SocketIOClient.Socket;
|
|
|
|
isConnected = new Sub<boolean>();
|
|
|
|
config = new Sub<Config | null>();
|
|
room = new Sub<Room | null>();
|
|
timeline = new Sub<Timeline | null>();
|
|
|
|
timeSyncIntervals = [500, 1000, 3000, 5000, 10000, 30000];
|
|
timeSyncs: { [requestId: number]: TimeSyncRequest } = {};
|
|
timeSyncTimeoutIds: number[] = [];
|
|
timeSyncTooOld = 120000;
|
|
roomTime = new Sub<number>();
|
|
|
|
calls: { [id: number]: Call } = {};
|
|
|
|
constructor() {
|
|
this.isConnected.set(false);
|
|
|
|
this.socket = io(this.url, {
|
|
autoConnect: false,
|
|
transports: ['websocket']
|
|
});
|
|
|
|
this.setupSocketListeners();
|
|
|
|
this.connect();
|
|
|
|
this.roomTime.set(0);
|
|
}
|
|
|
|
connect() {
|
|
this.socket.connect();
|
|
}
|
|
|
|
setupSocketListeners() {
|
|
this.socket.on('connect', () => {
|
|
this.isConnected.set(true);
|
|
this.onConnect();
|
|
})
|
|
|
|
this.socket.on('disconnect', () => {
|
|
this.isConnected.set(false);
|
|
this.onDisconnect();
|
|
})
|
|
|
|
this.socket.on('config', (data: any) => {
|
|
this.config.set(data.config);
|
|
})
|
|
|
|
this.socket.on('time_sync', (data: any) => {
|
|
this.timeSyncResponse(data.requestId, data.clientDiff, data.serverTime);
|
|
})
|
|
|
|
this.socket.on('room', (data: any) => {
|
|
if (data.room) {
|
|
this.setQueryLobbyId(data.room.id);
|
|
}
|
|
|
|
this.room.set(data.room);
|
|
});
|
|
|
|
this.socket.on('timeline', (data: any) => {
|
|
if (data.timeline) {
|
|
this.timeline.set(new Timeline(data.timeline));
|
|
} else {
|
|
this.timeline.set(null);
|
|
}
|
|
});
|
|
|
|
this.socket.on('call_response', (data: any) => {
|
|
let call = this.calls[data.id];
|
|
if (!call) return;
|
|
|
|
if (data.error) {
|
|
call.callback(data.error, null);
|
|
} else {
|
|
call.callback(null, data.response);
|
|
}
|
|
delete this.calls[data.id];
|
|
});
|
|
}
|
|
|
|
onConnect() {
|
|
this.startTimeSync();
|
|
|
|
let lobbyId = this.getQueryLobbyId();
|
|
if (lobbyId) {
|
|
this.requestJoin(lobbyId).then(v => {
|
|
if (!v) {
|
|
this.setQueryLobbyId(null);
|
|
this.requestJoinRandom();
|
|
}
|
|
})
|
|
} else {
|
|
this.requestJoinRandom();
|
|
}
|
|
}
|
|
|
|
onDisconnect() {
|
|
this.stopTimeSync();
|
|
}
|
|
|
|
autoStart() : boolean {
|
|
let query = parseQueryString(window.location.search);
|
|
return !!query.autostart;
|
|
}
|
|
|
|
private getQueryLobbyId(): number | null {
|
|
let query = parseQueryString(window.location.search);
|
|
if (query.lobby) {
|
|
let lobbyId = Number.parseInt(query.lobby.toString());
|
|
if (Number.isSafeInteger(lobbyId) && lobbyId > 0) {
|
|
return lobbyId
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private setQueryLobbyId(lobbyId: number | null) {
|
|
let query = parseQueryString(window.location.search);
|
|
if (lobbyId) {
|
|
query.lobby = lobbyId.toString();
|
|
} else {
|
|
delete query.lobby;
|
|
}
|
|
let newUrl = window.location.protocol + "//" + window.location.host +
|
|
window.location.pathname + (Object.keys(query).length ? ('?' + stringifyQueryString(query)) : '');
|
|
window.history.pushState({}, '', newUrl);
|
|
}
|
|
|
|
async call(name: string, params: any) {
|
|
return new Promise<any>((resolve, reject) => {
|
|
let callback = (err: any, res: any) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
|
|
resolve(res);
|
|
};
|
|
|
|
let call = new Call(name, params, callback);
|
|
this.calls[call.id] = call;
|
|
this.socket.emit('call', call.id, name, params);
|
|
});
|
|
}
|
|
|
|
setRoomOptions(roomOptions: RoomOptions) {
|
|
this.socket.emit('room_options', roomOptions)
|
|
}
|
|
|
|
requestStart(seekTime: number) {
|
|
this.socket.emit('request_start', {
|
|
seekTime: seekTime
|
|
});
|
|
}
|
|
|
|
async requestJoin(roomId: number): Promise<boolean> {
|
|
return this.call('room_exists', {roomId: roomId}).then(v => {
|
|
if (v) {
|
|
this.socket.emit('request_join', roomId, this.autoStart());
|
|
if (this.autoStart()) {
|
|
this.requestReady();
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
})
|
|
}
|
|
|
|
requestReady() {
|
|
this.socket.emit('request_ready');
|
|
}
|
|
|
|
requestJoinRandom() {
|
|
this.socket.emit('request_join_random');
|
|
}
|
|
|
|
startTimeSync() {
|
|
for (let i = 0; i < this.timeSyncIntervals.length; i++) {
|
|
let timeoutId = setTimeout(() => {
|
|
this.sendTimeSync(i === this.timeSyncIntervals.length - 1);
|
|
}, this.timeSyncIntervals[i]);
|
|
// @ts-ignore
|
|
this.timeSyncTimeoutIds.push(timeoutId);
|
|
}
|
|
}
|
|
|
|
stopTimeSync() {
|
|
for (let i = 0; i < this.timeSyncTimeoutIds.length; i++) {
|
|
clearTimeout(this.timeSyncTimeoutIds[i]);
|
|
}
|
|
}
|
|
|
|
sendTimeSync(alsoSchedule: boolean) {
|
|
let sync = new TimeSyncRequest();
|
|
this.socket.emit('time_sync', sync.requestId, Date.now());
|
|
this.timeSyncs[sync.requestId] = sync;
|
|
|
|
if (alsoSchedule) {
|
|
setTimeout(() => {
|
|
this.sendTimeSync(true);
|
|
}, this.timeSyncIntervals[this.timeSyncIntervals.length - 1]);
|
|
}
|
|
}
|
|
|
|
timeSyncResponse(requestId: number, clientDiff: number, serverTime: number) {
|
|
let syncReq = this.timeSyncs[requestId];
|
|
if (!syncReq) return
|
|
delete this.timeSyncs[requestId];
|
|
syncReq.response(clientDiff, serverTime);
|
|
|
|
for (let i in this.timeSyncs) {
|
|
if (this.timeSyncs[i].start < Date.now() - this.timeSyncTooOld) {
|
|
delete this.timeSyncs[i];
|
|
break;
|
|
}
|
|
}
|
|
// console.log(this.timeSyncs);
|
|
// console.log('SERVER TIME', this.serverTimeOffset());
|
|
|
|
this.roomTime.set(roomTime());
|
|
}
|
|
|
|
serverTime(): number {
|
|
return Date.now() + this.serverTimeOffset();
|
|
}
|
|
|
|
serverTimeOffset(): number {
|
|
let num = 0;
|
|
let sum = 0;
|
|
for (let i in this.timeSyncs) {
|
|
let sync = this.timeSyncs[i];
|
|
if (!sync.ready) continue;
|
|
sum += sync.offset;
|
|
num += 1;
|
|
}
|
|
|
|
if (num === 0) {
|
|
return 0;
|
|
}
|
|
|
|
return Math.round(sum / num);
|
|
}
|
|
}
|
|
|
|
let _callId = 0;
|
|
|
|
class Call {
|
|
id: number;
|
|
name: string;
|
|
params: any;
|
|
callback: (err: any, res: any) => any;
|
|
|
|
constructor(name: string, params: any, callback: (err: any, res: any) => void) {
|
|
this.name = name;
|
|
this.params = params;
|
|
this.id = _callId++;
|
|
this.callback = callback;
|
|
}
|
|
}
|
|
|
|
let _timeSyncId = 0;
|
|
|
|
class TimeSyncRequest {
|
|
requestId: number;
|
|
start: number;
|
|
offset: number = 0;
|
|
ready = false;
|
|
|
|
constructor() {
|
|
this.requestId = _timeSyncId++;
|
|
this.start = Date.now();
|
|
}
|
|
|
|
response(clientDiff: number, serverTime: number) {
|
|
this.ready = true;
|
|
let now = Date.now();
|
|
|
|
let lag = now - this.start;
|
|
this.offset = serverTime - now + lag / 2;
|
|
// console.log('TIME SYNC', 'cdiff:', clientDiff, 'lag:',
|
|
// lag, 'diff:', serverTime - now, 'offset:', this.offset);
|
|
}
|
|
}
|
|
|
|
let connection: Connection = new Connection();
|
|
// @ts-ignore
|
|
window['connection'] = connection;
|
|
export default connection;
|
|
|
|
export function useRoom(): Room | null {
|
|
return useSub(connection.room);
|
|
}
|
|
|
|
export function useConfig(): Config | null {
|
|
return useSub(connection.config);
|
|
}
|
|
|
|
export function useRoomRunningAndReadyChanged(): Room | null {
|
|
return useSub(connection.room, (v) => [v && v.running && v.readyToParticipate]);
|
|
}
|
|
|
|
export function useTimeline(): Timeline | null {
|
|
return useSub(connection.timeline);
|
|
}
|
|
|
|
export function useTimelineSongFileChanged(): Timeline | null {
|
|
return useSub(connection.timeline, (v) => [v && v.songFile]);
|
|
}
|
|
|
|
export function useRoomTime(): number {
|
|
return useSub(connection.roomTime);
|
|
}
|
|
|
|
export function roomTime(): number {
|
|
let room = connection.room.get();
|
|
if (!room) return 0;
|
|
return (connection.serverTime() - room.startTime) * room.speedFactor;
|
|
}
|
|
|
|
export function useIsConnected(): boolean {
|
|
return useSub(connection.isConnected);
|
|
}
|
|
|