mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-25 22:25:53 +02:00
tslint changes
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+215
-215
@@ -5,247 +5,247 @@ import { Season, Holiday, MapType } from '../../common/interfaces';
|
||||
import { getUrl } from '../../client/rev';
|
||||
|
||||
interface Track {
|
||||
name: string;
|
||||
src: string[];
|
||||
howl?: Howl;
|
||||
name: string;
|
||||
src: string[];
|
||||
howl?: Howl;
|
||||
}
|
||||
|
||||
function getTracks(season: Season, holiday: Holiday, map: MapType) {
|
||||
switch (map) {
|
||||
case MapType.Island:
|
||||
return [
|
||||
'island',
|
||||
'sunny-island',
|
||||
];
|
||||
case MapType.House:
|
||||
return [
|
||||
'happy-house',
|
||||
'sweet-home',
|
||||
];
|
||||
case MapType.Cave:
|
||||
return [
|
||||
'cave-crystals',
|
||||
'cave-secrets',
|
||||
];
|
||||
default:
|
||||
return [
|
||||
//'largo',
|
||||
//'musicbox',
|
||||
//'unrest',
|
||||
'bossanova',
|
||||
'clop',
|
||||
'fivefour',
|
||||
'hypnosis',
|
||||
'scherzo',
|
||||
'trills',
|
||||
'waltzalt',
|
||||
...(season === Season.Winter ? [
|
||||
'trees-winter',
|
||||
'reindeer-winter',
|
||||
] : [
|
||||
'trees',
|
||||
'reindeer',
|
||||
]),
|
||||
'season',
|
||||
'ambient',
|
||||
'building',
|
||||
'school',
|
||||
'falling',
|
||||
'tio',
|
||||
'orchid',
|
||||
...(season === Season.Winter ? [
|
||||
'xmas-air',
|
||||
'xmas-horns',
|
||||
'xmas-presents',
|
||||
] : []),
|
||||
...(holiday === Holiday.Halloween ? [
|
||||
'ghost',
|
||||
'pumpkin',
|
||||
] : []),
|
||||
];
|
||||
}
|
||||
switch (map) {
|
||||
case MapType.Island:
|
||||
return [
|
||||
'island',
|
||||
'sunny-island',
|
||||
];
|
||||
case MapType.House:
|
||||
return [
|
||||
'happy-house',
|
||||
'sweet-home',
|
||||
];
|
||||
case MapType.Cave:
|
||||
return [
|
||||
'cave-crystals',
|
||||
'cave-secrets',
|
||||
];
|
||||
default:
|
||||
return [
|
||||
//'largo',
|
||||
//'musicbox',
|
||||
//'unrest',
|
||||
'bossanova',
|
||||
'clop',
|
||||
'fivefour',
|
||||
'hypnosis',
|
||||
'scherzo',
|
||||
'trills',
|
||||
'waltzalt',
|
||||
...(season === Season.Winter ? [
|
||||
'trees-winter',
|
||||
'reindeer-winter',
|
||||
] : [
|
||||
'trees',
|
||||
'reindeer',
|
||||
]),
|
||||
'season',
|
||||
'ambient',
|
||||
'building',
|
||||
'school',
|
||||
'falling',
|
||||
'tio',
|
||||
'orchid',
|
||||
...(season === Season.Winter ? [
|
||||
'xmas-air',
|
||||
'xmas-horns',
|
||||
'xmas-presents',
|
||||
] : []),
|
||||
...(holiday === Holiday.Halloween ? [
|
||||
'ghost',
|
||||
'pumpkin',
|
||||
] : []),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const FADE_TRACKS = true;
|
||||
|
||||
function fadeOut(track: Track, id: number, volume: number) {
|
||||
const howl = track && track.howl;
|
||||
const howl = track && track.howl;
|
||||
|
||||
if (howl) {
|
||||
if (FADE_TRACKS) {
|
||||
howl
|
||||
.fade(volume, 0, 1000, id)
|
||||
.once('fade', () => howl.pause(id).stop(id), id);
|
||||
} else {
|
||||
howl
|
||||
.volume(0, id)
|
||||
.pause(id)
|
||||
.stop(id);
|
||||
}
|
||||
}
|
||||
if (howl) {
|
||||
if (FADE_TRACKS) {
|
||||
howl
|
||||
.fade(volume, 0, 1000, id)
|
||||
.once('fade', () => howl.pause(id).stop(id), id);
|
||||
} else {
|
||||
howl
|
||||
.volume(0, id)
|
||||
.pause(id)
|
||||
.stop(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fadeIn(track: Track, id: number, volume: number) {
|
||||
if (track && track.howl) {
|
||||
track.howl.fade(0, volume, 1000, id);
|
||||
}
|
||||
if (track && track.howl) {
|
||||
track.howl.fade(0, volume, 1000, id);
|
||||
}
|
||||
}
|
||||
|
||||
interface Instance {
|
||||
id: number;
|
||||
track: Track;
|
||||
id: number;
|
||||
track: Track;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class Audio {
|
||||
private tracks: Track[] = [];
|
||||
private volume = 0;
|
||||
private loops = 0;
|
||||
private playing = false;
|
||||
private stopped: Instance[] = [];
|
||||
private instance?: Instance;
|
||||
get trackName() {
|
||||
return this.instance && this.volume ? this.instance.track.name : '';
|
||||
}
|
||||
initTracks(season: Season, holiday: Holiday, map: MapType) {
|
||||
const tracks = getTracks(season, holiday, map);
|
||||
private tracks: Track[] = [];
|
||||
private volume = 0;
|
||||
private loops = 0;
|
||||
private playing = false;
|
||||
private stopped: Instance[] = [];
|
||||
private instance?: Instance;
|
||||
get trackName() {
|
||||
return this.instance && this.volume ? this.instance.track.name : '';
|
||||
}
|
||||
initTracks(season: Season, holiday: Holiday, map: MapType) {
|
||||
const tracks = getTracks(season, holiday, map);
|
||||
|
||||
// Make new tracks more frequent
|
||||
// const duplicateTracks = tracks.filter(t => t === 'ghost' || t === 'pumpkin');
|
||||
// tracks.push(...duplicateTracks);
|
||||
// tracks.push(...duplicateTracks);
|
||||
// Make new tracks more frequent
|
||||
// const duplicateTracks = tracks.filter(t => t === 'ghost' || t === 'pumpkin');
|
||||
// tracks.push(...duplicateTracks);
|
||||
// tracks.push(...duplicateTracks);
|
||||
|
||||
this.tracks = tracks.map(name => ({ name, src: [getUrl(`music/${name}.webm`), getUrl(`music/${name}.mp3`)] }));
|
||||
this.loops = 0;
|
||||
}
|
||||
setVolume(volume: number) {
|
||||
this.volume = volume / 100;
|
||||
this.tracks = tracks.map(name => ({ name, src: [getUrl(`music/${name}.webm`), getUrl(`music/${name}.mp3`)] }));
|
||||
this.loops = 0;
|
||||
}
|
||||
setVolume(volume: number) {
|
||||
this.volume = volume / 100;
|
||||
|
||||
if (this.playing) {
|
||||
if (this.instance) {
|
||||
this.setInstanceVolume(this.instance, this.volume);
|
||||
} else if (this.volume) {
|
||||
this.playRandomTrack();
|
||||
}
|
||||
}
|
||||
}
|
||||
play() {
|
||||
try {
|
||||
if (!this.playing) {
|
||||
this.playing = true;
|
||||
if (this.playing) {
|
||||
if (this.instance) {
|
||||
this.setInstanceVolume(this.instance, this.volume);
|
||||
} else if (this.volume) {
|
||||
this.playRandomTrack();
|
||||
}
|
||||
}
|
||||
}
|
||||
play() {
|
||||
try {
|
||||
if (!this.playing) {
|
||||
this.playing = true;
|
||||
|
||||
if (this.volume) {
|
||||
if (this.instance) {
|
||||
this.resumeInstance(this.instance);
|
||||
} else {
|
||||
this.playRandomTrack();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
playOrSwitchToRandomTrack() {
|
||||
if (FADE_TRACKS) {
|
||||
if (this.playing && this.volume) {
|
||||
this.playRandomTrack();
|
||||
} else {
|
||||
this.play();
|
||||
}
|
||||
} else {
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
if (this.playing) {
|
||||
this.playing = false;
|
||||
this.stopInstance(this.instance);
|
||||
}
|
||||
}
|
||||
forcePlay() {
|
||||
if (!this.instance || !this.instance.track.howl!.playing(this.instance.id)) {
|
||||
this.playRandomTrack();
|
||||
}
|
||||
}
|
||||
touch() {
|
||||
this.stopInstances();
|
||||
this.setInstanceVolume(this.instance, this.volume);
|
||||
}
|
||||
private switchToTrack(track: Track) {
|
||||
if (this.instance && this.instance.track === track) {
|
||||
return false;
|
||||
} else {
|
||||
this.stopInstance(this.instance);
|
||||
this.instance = this.playTrack(track);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
playRandomTrack() {
|
||||
while (!this.switchToTrack(sample(this.tracks)!))
|
||||
;
|
||||
if (this.volume) {
|
||||
if (this.instance) {
|
||||
this.resumeInstance(this.instance);
|
||||
} else {
|
||||
this.playRandomTrack();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
playOrSwitchToRandomTrack() {
|
||||
if (FADE_TRACKS) {
|
||||
if (this.playing && this.volume) {
|
||||
this.playRandomTrack();
|
||||
} else {
|
||||
this.play();
|
||||
}
|
||||
} else {
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
if (this.playing) {
|
||||
this.playing = false;
|
||||
this.stopInstance(this.instance);
|
||||
}
|
||||
}
|
||||
forcePlay() {
|
||||
if (!this.instance || !this.instance.track.howl!.playing(this.instance.id)) {
|
||||
this.playRandomTrack();
|
||||
}
|
||||
}
|
||||
touch() {
|
||||
this.stopInstances();
|
||||
this.setInstanceVolume(this.instance, this.volume);
|
||||
}
|
||||
private switchToTrack(track: Track) {
|
||||
if (this.instance && this.instance.track === track) {
|
||||
return false;
|
||||
} else {
|
||||
this.stopInstance(this.instance);
|
||||
this.instance = this.playTrack(track);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
playRandomTrack() {
|
||||
while (!this.switchToTrack(sample(this.tracks)!))
|
||||
;
|
||||
|
||||
this.loops = random(4, 7);
|
||||
}
|
||||
private playTrack(track: Track): Instance {
|
||||
this.prepareTrack(track);
|
||||
const id = track.howl!.play();
|
||||
fadeIn(track, id, this.volume);
|
||||
return { id, track };
|
||||
}
|
||||
private resumeInstance({ track, id }: Instance) {
|
||||
track.howl!.play(id);
|
||||
fadeIn(track, id, this.volume);
|
||||
}
|
||||
private stopInstance(instance: Instance | undefined) {
|
||||
if (instance) {
|
||||
this.stopped.push(instance);
|
||||
}
|
||||
this.loops = random(4, 7);
|
||||
}
|
||||
private playTrack(track: Track): Instance {
|
||||
this.prepareTrack(track);
|
||||
const id = track.howl!.play();
|
||||
fadeIn(track, id, this.volume);
|
||||
return { id, track };
|
||||
}
|
||||
private resumeInstance({ track, id }: Instance) {
|
||||
track.howl!.play(id);
|
||||
fadeIn(track, id, this.volume);
|
||||
}
|
||||
private stopInstance(instance: Instance | undefined) {
|
||||
if (instance) {
|
||||
this.stopped.push(instance);
|
||||
}
|
||||
|
||||
this.stopInstances();
|
||||
}
|
||||
private stopInstances() {
|
||||
this.stopped.forEach(({ track, id }) => fadeOut(track, id, this.volume));
|
||||
this.stopped = this.stopped.filter(({ track, id }) => track.howl!.playing(id));
|
||||
}
|
||||
private setInstanceVolume(instance: Instance | undefined, volume: number) {
|
||||
if (instance) {
|
||||
const howl = instance.track.howl!;
|
||||
howl.volume(volume, instance.id);
|
||||
this.stopInstances();
|
||||
}
|
||||
private stopInstances() {
|
||||
this.stopped.forEach(({ track, id }) => fadeOut(track, id, this.volume));
|
||||
this.stopped = this.stopped.filter(({ track, id }) => track.howl!.playing(id));
|
||||
}
|
||||
private setInstanceVolume(instance: Instance | undefined, volume: number) {
|
||||
if (instance) {
|
||||
const howl = instance.track.howl!;
|
||||
howl.volume(volume, instance.id);
|
||||
|
||||
if (volume && !howl.playing(instance.id)) {
|
||||
howl.play(instance.id);
|
||||
} else if (!volume && howl.playing(instance.id)) {
|
||||
howl.pause(instance.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
private prepareTrack(track: Track) {
|
||||
if (!track.howl) {
|
||||
track.howl = new Howl({
|
||||
src: track.src,
|
||||
loop: true,
|
||||
html5: true,
|
||||
});
|
||||
if (volume && !howl.playing(instance.id)) {
|
||||
howl.play(instance.id);
|
||||
} else if (!volume && howl.playing(instance.id)) {
|
||||
howl.pause(instance.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
private prepareTrack(track: Track) {
|
||||
if (!track.howl) {
|
||||
track.howl = new Howl({
|
||||
src: track.src,
|
||||
loop: true,
|
||||
html5: true,
|
||||
});
|
||||
|
||||
track.howl.on('end', id => this.onEnd(id));
|
||||
}
|
||||
}
|
||||
private handlingOnEnd = 0;
|
||||
private handlingOnEndAt = 0;
|
||||
private onEnd(id: number) {
|
||||
if (
|
||||
this.instance && this.instance.id === id && --this.loops < 0 &&
|
||||
(this.handlingOnEnd !== id || this.handlingOnEndAt < performance.now())
|
||||
) {
|
||||
this.handlingOnEnd = id;
|
||||
this.handlingOnEndAt = performance.now() + 500;
|
||||
track.howl.on('end', id => this.onEnd(id));
|
||||
}
|
||||
}
|
||||
private handlingOnEnd = 0;
|
||||
private handlingOnEndAt = 0;
|
||||
private onEnd(id: number) {
|
||||
if (
|
||||
this.instance && this.instance.id === id && --this.loops < 0 &&
|
||||
(this.handlingOnEnd !== id || this.handlingOnEndAt < performance.now())
|
||||
) {
|
||||
this.handlingOnEnd = id;
|
||||
this.handlingOnEndAt = performance.now() + 500;
|
||||
|
||||
if (this.volume && this.playing) {
|
||||
this.playRandomTrack();
|
||||
} else {
|
||||
this.stopInstance(this.instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.volume && this.playing) {
|
||||
this.playRandomTrack();
|
||||
} else {
|
||||
this.stopInstance(this.instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,20 @@ import { Router, CanActivate } from '@angular/router';
|
||||
import { Model } from './model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private router: Router, private model: Model) {
|
||||
}
|
||||
canActivate() {
|
||||
return this.model.accountPromise
|
||||
.then(account => {
|
||||
if (account) {
|
||||
return true;
|
||||
} else {
|
||||
this.router.navigate(['/']);
|
||||
return false;
|
||||
}
|
||||
}) as any;
|
||||
}
|
||||
constructor(private router: Router, private model: Model) {
|
||||
}
|
||||
canActivate() {
|
||||
return this.model.accountPromise
|
||||
.then(account => {
|
||||
if (account) {
|
||||
return true;
|
||||
} else {
|
||||
this.router.navigate(['/']);
|
||||
return false;
|
||||
}
|
||||
}) as any;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,44 +4,44 @@ import { Person } from '../../common/rollbar';
|
||||
|
||||
@Injectable()
|
||||
export class ErrorReporter {
|
||||
disable() {
|
||||
}
|
||||
configureUser(_person: Person) {
|
||||
}
|
||||
configureData(_data: any) {
|
||||
}
|
||||
captureEvent(_data: any) {
|
||||
}
|
||||
reportError(error: any, data?: any) {
|
||||
console.error(error, data);
|
||||
}
|
||||
createClientErrorHandler(socketOptions: ClientOptions): ClientErrorHandler {
|
||||
const handleRecvError = (error: Error, data: string | Uint8Array) => {
|
||||
if (error.message) {
|
||||
let method: string | undefined;
|
||||
disable() {
|
||||
}
|
||||
configureUser(_person: Person) {
|
||||
}
|
||||
configureData(_data: any) {
|
||||
}
|
||||
captureEvent(_data: any) {
|
||||
}
|
||||
reportError(error: any, data?: any) {
|
||||
console.error(error, data);
|
||||
}
|
||||
createClientErrorHandler(socketOptions: ClientOptions): ClientErrorHandler {
|
||||
const handleRecvError = (error: Error, data: string | Uint8Array) => {
|
||||
if (error.message) {
|
||||
let method: string | undefined;
|
||||
|
||||
if (data instanceof Uint8Array) {
|
||||
const bytes: number[] = [];
|
||||
const length = Math.min(data.length, 200);
|
||||
if (data instanceof Uint8Array) {
|
||||
const bytes: number[] = [];
|
||||
const length = Math.min(data.length, 200);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
bytes.push(data[i]);
|
||||
}
|
||||
for (let i = 0; i < length; i++) {
|
||||
bytes.push(data[i]);
|
||||
}
|
||||
|
||||
const trail = length < data.length ? '...' : '';
|
||||
const trail = length < data.length ? '...' : '';
|
||||
|
||||
if (data.length > 0) {
|
||||
const item = socketOptions.client[data[0]] as string | [string, any];
|
||||
method = typeof item === 'string' ? item : item[0];
|
||||
}
|
||||
if (data.length > 0) {
|
||||
const item = socketOptions.client[data[0]] as string | [string, any];
|
||||
method = typeof item === 'string' ? item : item[0];
|
||||
}
|
||||
|
||||
data = `<${bytes.toString()}${trail}>`;
|
||||
}
|
||||
data = `<${bytes.toString()}${trail}>`;
|
||||
}
|
||||
|
||||
this.reportError(error, { data, method });
|
||||
}
|
||||
};
|
||||
this.reportError(error, { data, method });
|
||||
}
|
||||
};
|
||||
|
||||
return { handleRecvError };
|
||||
}
|
||||
return { handleRecvError };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import { Injectable, NgZone } from '@angular/core';
|
||||
|
||||
export interface FrameLoop {
|
||||
init(): void;
|
||||
destroy(): void;
|
||||
init(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class FrameService {
|
||||
constructor(private zone: NgZone) {
|
||||
}
|
||||
create(frame: (delta: number) => void) {
|
||||
const zone = this.zone;
|
||||
let ref = 0;
|
||||
let last = 0;
|
||||
constructor(private zone: NgZone) {
|
||||
}
|
||||
create(frame: (delta: number) => void) {
|
||||
const zone = this.zone;
|
||||
let ref = 0;
|
||||
let last = 0;
|
||||
|
||||
function tick(now: number) {
|
||||
ref = requestAnimationFrame(tick);
|
||||
frame((now - last) / 1000);
|
||||
last = now;
|
||||
}
|
||||
function tick(now: number) {
|
||||
ref = requestAnimationFrame(tick);
|
||||
frame((now - last) / 1000);
|
||||
last = now;
|
||||
}
|
||||
|
||||
return {
|
||||
init() {
|
||||
if (!ref) {
|
||||
last = performance.now();
|
||||
zone.runOutsideAngular(() => ref = requestAnimationFrame(tick));
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
cancelAnimationFrame(ref);
|
||||
ref = 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
init() {
|
||||
if (!ref) {
|
||||
last = performance.now();
|
||||
zone.runOutsideAngular(() => ref = requestAnimationFrame(tick));
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
cancelAnimationFrame(ref);
|
||||
ref = 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,296 +17,296 @@ import { StorageService } from './storageService';
|
||||
export interface ClientSocketService extends SocketService<ClientActions, IServerActions> { }
|
||||
|
||||
function createSocket(
|
||||
gameService: GameService, game: PonyTownGame, model: Model, zone: NgZone, options: ClientOptions,
|
||||
token: string, errorHandler: ClientErrorHandler
|
||||
gameService: GameService, game: PonyTownGame, model: Model, zone: NgZone, options: ClientOptions,
|
||||
token: string, errorHandler: ClientErrorHandler
|
||||
): ClientSocketService {
|
||||
const socket = createClientSocket<ClientActions, IServerActions>(options, token, errorHandler);
|
||||
socket.client = new ClientActions(gameService, game, model, zone);
|
||||
const socket = createClientSocket<ClientActions, IServerActions>(options, token, errorHandler);
|
||||
socket.client = new ClientActions(gameService, game, model, zone);
|
||||
|
||||
if (!socket.supportsBinary) {
|
||||
throw new Error(BROWSER_NOT_SUPPORTED_ERROR);
|
||||
}
|
||||
if (!socket.supportsBinary) {
|
||||
throw new Error(BROWSER_NOT_SUPPORTED_ERROR);
|
||||
}
|
||||
|
||||
return socket;
|
||||
return socket;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class GameService {
|
||||
playing = false;
|
||||
joining = false;
|
||||
offline = false;
|
||||
protectionError = false;
|
||||
rateLimitError = false;
|
||||
versionError = false;
|
||||
version?: string;
|
||||
server?: ServerInfo;
|
||||
servers: ServerInfo[] = [];
|
||||
error?: string;
|
||||
leftMessage?: string;
|
||||
private safelyLeft = false;
|
||||
private gameLoop?: GameLoop;
|
||||
private disconnectedTimeout?: any;
|
||||
private initialized = false;
|
||||
private update?: boolean;
|
||||
private locked = false;
|
||||
constructor(
|
||||
private model: Model,
|
||||
private game: PonyTownGame,
|
||||
private zone: NgZone,
|
||||
private errorHandler: ErrorHandler,
|
||||
private errorReporter: ErrorReporter,
|
||||
private storage: StorageService,
|
||||
) {
|
||||
this.pollStatus();
|
||||
}
|
||||
get selected() {
|
||||
return this.game.selected;
|
||||
}
|
||||
get account() {
|
||||
return this.model.account;
|
||||
}
|
||||
get canPlay(): boolean {
|
||||
return !!this.model.pony &&
|
||||
!!this.model.pony.name &&
|
||||
!this.model.pending &&
|
||||
!this.joining &&
|
||||
!!this.server &&
|
||||
!this.server.offline &&
|
||||
!this.rateLimitError &&
|
||||
!this.versionError &&
|
||||
!this.locked;
|
||||
}
|
||||
get updateWarning(): boolean {
|
||||
return !!this.update;
|
||||
}
|
||||
get filterSwearWords(): boolean {
|
||||
return !!(this.server && this.server.filter)
|
||||
|| !!(this.account && this.account.settings && this.account.settings.filterSwearWords);
|
||||
}
|
||||
get wasPlaying() {
|
||||
return this.storage.getBoolean('playing');
|
||||
}
|
||||
join(ponyId: string) {
|
||||
this.errorReporter.captureEvent({ name: 'Join' });
|
||||
const server = this.server;
|
||||
playing = false;
|
||||
joining = false;
|
||||
offline = false;
|
||||
protectionError = false;
|
||||
rateLimitError = false;
|
||||
versionError = false;
|
||||
version?: string;
|
||||
server?: ServerInfo;
|
||||
servers: ServerInfo[] = [];
|
||||
error?: string;
|
||||
leftMessage?: string;
|
||||
private safelyLeft = false;
|
||||
private gameLoop?: GameLoop;
|
||||
private disconnectedTimeout?: any;
|
||||
private initialized = false;
|
||||
private update?: boolean;
|
||||
private locked = false;
|
||||
constructor(
|
||||
private model: Model,
|
||||
private game: PonyTownGame,
|
||||
private zone: NgZone,
|
||||
private errorHandler: ErrorHandler,
|
||||
private errorReporter: ErrorReporter,
|
||||
private storage: StorageService,
|
||||
) {
|
||||
this.pollStatus();
|
||||
}
|
||||
get selected() {
|
||||
return this.game.selected;
|
||||
}
|
||||
get account() {
|
||||
return this.model.account;
|
||||
}
|
||||
get canPlay(): boolean {
|
||||
return !!this.model.pony &&
|
||||
!!this.model.pony.name &&
|
||||
!this.model.pending &&
|
||||
!this.joining &&
|
||||
!!this.server &&
|
||||
!this.server.offline &&
|
||||
!this.rateLimitError &&
|
||||
!this.versionError &&
|
||||
!this.locked;
|
||||
}
|
||||
get updateWarning(): boolean {
|
||||
return !!this.update;
|
||||
}
|
||||
get filterSwearWords(): boolean {
|
||||
return !!(this.server && this.server.filter)
|
||||
|| !!(this.account && this.account.settings && this.account.settings.filterSwearWords);
|
||||
}
|
||||
get wasPlaying() {
|
||||
return this.storage.getBoolean('playing');
|
||||
}
|
||||
join(ponyId: string) {
|
||||
this.errorReporter.captureEvent({ name: 'Join' });
|
||||
const server = this.server;
|
||||
|
||||
if (this.playing || this.joining || !server) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (this.playing || this.joining || !server) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (typeof WebSocket === 'undefined' || typeof Float32Array === 'undefined') {
|
||||
return Promise.reject(new Error(BROWSER_NOT_SUPPORTED_ERROR));
|
||||
}
|
||||
if (typeof WebSocket === 'undefined' || typeof Float32Array === 'undefined') {
|
||||
return Promise.reject(new Error(BROWSER_NOT_SUPPORTED_ERROR));
|
||||
}
|
||||
|
||||
this.joining = true;
|
||||
this.leftMessage = undefined;
|
||||
this.safelyLeft = false;
|
||||
this.joining = true;
|
||||
this.leftMessage = undefined;
|
||||
this.safelyLeft = false;
|
||||
|
||||
return this.model.join(server.id, ponyId)
|
||||
.then(({ token, alert }) => {
|
||||
if (!this.joining) {
|
||||
return false;
|
||||
}
|
||||
return this.model.join(server.id, ponyId)
|
||||
.then(({ token, alert }) => {
|
||||
if (!this.joining) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
this.model.accountAlert = alert;
|
||||
this.joining = false;
|
||||
return false;
|
||||
}
|
||||
if (!token) {
|
||||
this.model.accountAlert = alert;
|
||||
this.joining = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.zone.runOutsideAngular(() => {
|
||||
const options = { ...socketOptions(), path: server.path, host: server.host };
|
||||
const errorHandler = this.errorReporter.createClientErrorHandler(options);
|
||||
const socket = createSocket(this, this.game, this.model, this.zone, options, token, errorHandler);
|
||||
return this.zone.runOutsideAngular(() => {
|
||||
const options = { ...socketOptions(), path: server.path, host: server.host };
|
||||
const errorHandler = this.errorReporter.createClientErrorHandler(options);
|
||||
const socket = createSocket(this, this.game, this.model, this.zone, options, token, errorHandler);
|
||||
|
||||
if (this.gameLoop) {
|
||||
this.gameLoop.cancel();
|
||||
}
|
||||
if (this.gameLoop) {
|
||||
this.gameLoop.cancel();
|
||||
}
|
||||
|
||||
this.game.startup(socket, this.model.isMod);
|
||||
this.gameLoop = startGameLoop(this.game, e => this.handleGameError(e));
|
||||
this.game.startup(socket, this.model.isMod);
|
||||
this.gameLoop = startGameLoop(this.game, e => this.handleGameError(e));
|
||||
|
||||
return this.gameLoop.started
|
||||
.then(() => {
|
||||
this.errorReporter.captureEvent({ name: 'gameLoop.started' });
|
||||
const socketConnected = this.pollUntilConnected(socket);
|
||||
socket.connect();
|
||||
return socketConnected;
|
||||
})
|
||||
.then(() => {
|
||||
this.errorReporter.captureEvent({ name: 'socketConnected' });
|
||||
return true;
|
||||
})
|
||||
.catch(e => {
|
||||
this.errorReporter.captureEvent({ name: 'socket.disconnect()', error: e.message });
|
||||
socket.disconnect();
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(joined => {
|
||||
this.errorReporter.captureEvent({ name: joined ? 'Joined game' : 'Not joined game' });
|
||||
})
|
||||
.catch((e: RequestError) => {
|
||||
this.errorReporter.captureEvent({ name: 'Failed to join game', error: e.message });
|
||||
return this.gameLoop.started
|
||||
.then(() => {
|
||||
this.errorReporter.captureEvent({ name: 'gameLoop.started' });
|
||||
const socketConnected = this.pollUntilConnected(socket);
|
||||
socket.connect();
|
||||
return socketConnected;
|
||||
})
|
||||
.then(() => {
|
||||
this.errorReporter.captureEvent({ name: 'socketConnected' });
|
||||
return true;
|
||||
})
|
||||
.catch(e => {
|
||||
this.errorReporter.captureEvent({ name: 'socket.disconnect()', error: e.message });
|
||||
socket.disconnect();
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(joined => {
|
||||
this.errorReporter.captureEvent({ name: joined ? 'Joined game' : 'Not joined game' });
|
||||
})
|
||||
.catch((e: RequestError) => {
|
||||
this.errorReporter.captureEvent({ name: 'Failed to join game', error: e.message });
|
||||
|
||||
// if (e.status && e.status > 500 && e.status < 500) {
|
||||
// this.rateLimitError = true;
|
||||
// setTimeout(() => this.rateLimitError = false, 5000);
|
||||
// }
|
||||
// if (e.status && e.status > 500 && e.status < 500) {
|
||||
// this.rateLimitError = true;
|
||||
// setTimeout(() => this.rateLimitError = false, 5000);
|
||||
// }
|
||||
|
||||
this.zone.run(() => this.left('join.catch'));
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
leave(reason: string) {
|
||||
this.errorReporter.captureEvent({ name: 'Leave', reason });
|
||||
this.game.leave();
|
||||
this.left('leave');
|
||||
}
|
||||
joined() {
|
||||
this.errorReporter.captureEvent({ name: 'Joined' });
|
||||
this.storage.setBoolean('playing', true);
|
||||
clearTimeout(this.disconnectedTimeout);
|
||||
setTimeout(() => {
|
||||
this.joining = false;
|
||||
this.playing = true;
|
||||
});
|
||||
}
|
||||
left(from: string, reason = LeaveReason.None) {
|
||||
this.errorReporter.captureEvent({ name: 'Left', from, reason });
|
||||
this.storage.setBoolean('playing', false);
|
||||
this.safelyLeft = true;
|
||||
this.zone.run(() => this.left('join.catch'));
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
leave(reason: string) {
|
||||
this.errorReporter.captureEvent({ name: 'Leave', reason });
|
||||
this.game.leave();
|
||||
this.left('leave');
|
||||
}
|
||||
joined() {
|
||||
this.errorReporter.captureEvent({ name: 'Joined' });
|
||||
this.storage.setBoolean('playing', true);
|
||||
clearTimeout(this.disconnectedTimeout);
|
||||
setTimeout(() => {
|
||||
this.joining = false;
|
||||
this.playing = true;
|
||||
});
|
||||
}
|
||||
left(from: string, reason = LeaveReason.None) {
|
||||
this.errorReporter.captureEvent({ name: 'Left', from, reason });
|
||||
this.storage.setBoolean('playing', false);
|
||||
this.safelyLeft = true;
|
||||
|
||||
if (reason === LeaveReason.Swearing) {
|
||||
this.leftMessage = 'Kicked for swearing or inappropriate language';
|
||||
this.locked = true;
|
||||
} else {
|
||||
this.leftMessage = undefined;
|
||||
}
|
||||
if (reason === LeaveReason.Swearing) {
|
||||
this.leftMessage = 'Kicked for swearing or inappropriate language';
|
||||
this.locked = true;
|
||||
} else {
|
||||
this.leftMessage = undefined;
|
||||
}
|
||||
|
||||
if (this.gameLoop) {
|
||||
this.errorReporter.captureEvent({ name: 'gameLoop.cancel()' });
|
||||
this.gameLoop.cancel();
|
||||
this.gameLoop = undefined;
|
||||
}
|
||||
if (this.gameLoop) {
|
||||
this.errorReporter.captureEvent({ name: 'gameLoop.cancel()' });
|
||||
this.gameLoop.cancel();
|
||||
this.gameLoop = undefined;
|
||||
}
|
||||
|
||||
clearTimeout(this.disconnectedTimeout);
|
||||
clearTimeout(this.disconnectedTimeout);
|
||||
|
||||
setTimeout(() => {
|
||||
this.joining = false;
|
||||
this.playing = false;
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.joining = false;
|
||||
this.playing = false;
|
||||
});
|
||||
|
||||
if (this.locked) {
|
||||
setTimeout(() => {
|
||||
this.locked = false;
|
||||
}, 7000);
|
||||
}
|
||||
if (this.locked) {
|
||||
setTimeout(() => {
|
||||
this.locked = false;
|
||||
}, 7000);
|
||||
}
|
||||
|
||||
if (this.model.friends) {
|
||||
for (const friend of this.model.friends) {
|
||||
friend.online = false;
|
||||
friend.entityId = 0;
|
||||
}
|
||||
}
|
||||
if (this.model.friends) {
|
||||
for (const friend of this.model.friends) {
|
||||
friend.online = false;
|
||||
friend.entityId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.game.release();
|
||||
this.game.onLeft.next();
|
||||
}
|
||||
disconnected() {
|
||||
this.errorReporter.captureEvent({ name: 'Disconnected' });
|
||||
clearTimeout(this.disconnectedTimeout);
|
||||
this.game.release();
|
||||
this.game.onLeft.next();
|
||||
}
|
||||
disconnected() {
|
||||
this.errorReporter.captureEvent({ name: 'Disconnected' });
|
||||
clearTimeout(this.disconnectedTimeout);
|
||||
|
||||
if (!this.safelyLeft) {
|
||||
this.disconnectedTimeout = setTimeout(() => this.left('disconnected.timeout'), 10000);
|
||||
}
|
||||
}
|
||||
private pollStatus() {
|
||||
return this.getAndUpdateStatus(this.account)
|
||||
.finally(() => {
|
||||
setTimeout(() => this.pollStatus(), this.initialized ? 10000 : 500);
|
||||
});
|
||||
}
|
||||
private getAndUpdateStatus(account: AccountData | undefined) {
|
||||
if (this.joining || this.playing || !account || !isFocused()) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.model.status(this.initialized)
|
||||
.then(status => this.updateStatus(account, status))
|
||||
.catch((e: RequestError) => {
|
||||
DEVELOPMENT && console.error(e);
|
||||
this.offline = e.message === OFFLINE_ERROR;
|
||||
this.versionError = e.message === VERSION_ERROR;
|
||||
this.protectionError = e.message === PROTECTION_ERROR;
|
||||
});
|
||||
}
|
||||
}
|
||||
private updateStatus(account: AccountData, status: GameStatus) {
|
||||
this.initialized = true;
|
||||
this.offline = false;
|
||||
this.version = status.version;
|
||||
this.update = status.update;
|
||||
if (!this.safelyLeft) {
|
||||
this.disconnectedTimeout = setTimeout(() => this.left('disconnected.timeout'), 10000);
|
||||
}
|
||||
}
|
||||
private pollStatus() {
|
||||
return this.getAndUpdateStatus(this.account)
|
||||
.finally(() => {
|
||||
setTimeout(() => this.pollStatus(), this.initialized ? 10000 : 500);
|
||||
});
|
||||
}
|
||||
private getAndUpdateStatus(account: AccountData | undefined) {
|
||||
if (this.joining || this.playing || !account || !isFocused()) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.model.status(this.initialized)
|
||||
.then(status => this.updateStatus(account, status))
|
||||
.catch((e: RequestError) => {
|
||||
DEVELOPMENT && console.error(e);
|
||||
this.offline = e.message === OFFLINE_ERROR;
|
||||
this.versionError = e.message === VERSION_ERROR;
|
||||
this.protectionError = e.message === PROTECTION_ERROR;
|
||||
});
|
||||
}
|
||||
}
|
||||
private updateStatus(account: AccountData, status: GameStatus) {
|
||||
this.initialized = true;
|
||||
this.offline = false;
|
||||
this.version = status.version;
|
||||
this.update = status.update;
|
||||
|
||||
for (const server of status.servers) {
|
||||
const existing = findById(this.servers, server.id);
|
||||
for (const server of status.servers) {
|
||||
const existing = findById(this.servers, server.id);
|
||||
|
||||
if (existing) {
|
||||
merge(existing, server);
|
||||
} else if ('name' in server) {
|
||||
const info = server as ServerInfo;
|
||||
info.countryFlags = info.flag && /^[a-z]{2}( [a-z]{2})*$/.test(info.flag) ? info.flag.split(/ /g) : [];
|
||||
if (existing) {
|
||||
merge(existing, server);
|
||||
} else if ('name' in server) {
|
||||
const info = server as ServerInfo;
|
||||
info.countryFlags = info.flag && /^[a-z]{2}( [a-z]{2})*$/.test(info.flag) ? info.flag.split(/ /g) : [];
|
||||
|
||||
if (info.name && account && meetsRequirement(account, info.require)) {
|
||||
this.servers.push(info);
|
||||
}
|
||||
} else {
|
||||
// got new server on the list
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
if (info.name && account && meetsRequirement(account, info.require)) {
|
||||
this.servers.push(info);
|
||||
}
|
||||
} else {
|
||||
// got new server on the list
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = this.servers.length - 1; i >= 0; i--) {
|
||||
if (!findById(status.servers, this.servers[i].id)) {
|
||||
this.servers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
for (let i = this.servers.length - 1; i >= 0; i--) {
|
||||
if (!findById(status.servers, this.servers[i].id)) {
|
||||
this.servers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (isLanguage('ru')) {
|
||||
this.servers.sort(sortServersForRussian);
|
||||
}
|
||||
if (isLanguage('ru')) {
|
||||
this.servers.sort(sortServersForRussian);
|
||||
}
|
||||
|
||||
if (!this.server && account.settings.defaultServer) {
|
||||
this.server = findById(this.servers, account.settings.defaultServer);
|
||||
if (!this.server && account.settings.defaultServer) {
|
||||
this.server = findById(this.servers, account.settings.defaultServer);
|
||||
|
||||
if (DEVELOPMENT && /join/.test(this.model.pony.name)) {
|
||||
setTimeout(() => this.join(this.model.pony.id));
|
||||
}
|
||||
}
|
||||
if (DEVELOPMENT && /join/.test(this.model.pony.name)) {
|
||||
setTimeout(() => this.join(this.model.pony.id));
|
||||
}
|
||||
}
|
||||
|
||||
if (!includes(this.servers, this.server)) {
|
||||
this.server = undefined;
|
||||
}
|
||||
}
|
||||
private handleGameError(error: Error) {
|
||||
this.errorReporter.captureEvent({ name: 'handleGameError', error: error.message });
|
||||
this.error = error.message;
|
||||
this.errorHandler.handleError(error);
|
||||
this.leave('handleGameError');
|
||||
}
|
||||
private pollUntilConnected(socket: ClientSocketService) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const interval = setInterval(() => {
|
||||
if (socket.isConnected) {
|
||||
clearInterval(interval);
|
||||
this.zone.run(resolve);
|
||||
} else if (!this.joining) {
|
||||
clearInterval(interval);
|
||||
this.zone.run(() => reject(new Error('Cancelled (poll)')));
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
if (!includes(this.servers, this.server)) {
|
||||
this.server = undefined;
|
||||
}
|
||||
}
|
||||
private handleGameError(error: Error) {
|
||||
this.errorReporter.captureEvent({ name: 'handleGameError', error: error.message });
|
||||
this.error = error.message;
|
||||
this.errorHandler.handleError(error);
|
||||
this.leave('handleGameError');
|
||||
}
|
||||
private pollUntilConnected(socket: ClientSocketService) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const interval = setInterval(() => {
|
||||
if (socket.isConnected) {
|
||||
clearInterval(interval);
|
||||
this.zone.run(resolve);
|
||||
} else if (!this.joining) {
|
||||
clearInterval(interval);
|
||||
this.zone.run(() => reject(new Error('Cancelled (poll)')));
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,40 +2,40 @@ import { Injectable } from '@angular/core';
|
||||
import { StorageService } from './storageService';
|
||||
|
||||
interface InstallEvent extends Event {
|
||||
prompt(): void;
|
||||
userChoice: Promise<'accepted' | 'dismissed'>;
|
||||
prompt(): void;
|
||||
userChoice: Promise<'accepted' | 'dismissed'>;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class InstallService {
|
||||
private installEvent?: InstallEvent;
|
||||
constructor(private storage: StorageService) {
|
||||
if (!this.storage.getBoolean('install-dismissed')) {
|
||||
window.addEventListener('beforeinstallprompt', event => {
|
||||
event.preventDefault();
|
||||
this.installEvent = event as any;
|
||||
});
|
||||
}
|
||||
}
|
||||
get canInstall() {
|
||||
return !!this.installEvent || (DEVELOPMENT && localStorage.getItem('install'));
|
||||
}
|
||||
install() {
|
||||
if (!this.installEvent) {
|
||||
return Promise.reject(new Error('Cannot install'));
|
||||
}
|
||||
private installEvent?: InstallEvent;
|
||||
constructor(private storage: StorageService) {
|
||||
if (!this.storage.getBoolean('install-dismissed')) {
|
||||
window.addEventListener('beforeinstallprompt', event => {
|
||||
event.preventDefault();
|
||||
this.installEvent = event as any;
|
||||
});
|
||||
}
|
||||
}
|
||||
get canInstall() {
|
||||
return !!this.installEvent || (DEVELOPMENT && localStorage.getItem('install'));
|
||||
}
|
||||
install() {
|
||||
if (!this.installEvent) {
|
||||
return Promise.reject(new Error('Cannot install'));
|
||||
}
|
||||
|
||||
this.installEvent.prompt();
|
||||
this.installEvent.prompt();
|
||||
|
||||
return this.installEvent.userChoice
|
||||
.finally(() => {
|
||||
this.installEvent = undefined;
|
||||
});
|
||||
}
|
||||
dismiss() {
|
||||
this.installEvent = undefined;
|
||||
this.storage.setBoolean('install-dismissed', true);
|
||||
}
|
||||
return this.installEvent.userChoice
|
||||
.finally(() => {
|
||||
this.installEvent = undefined;
|
||||
});
|
||||
}
|
||||
dismiss() {
|
||||
this.installEvent = undefined;
|
||||
this.storage.setBoolean('install-dismissed', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,43 +2,43 @@ import { Injectable, NgZone } from '@angular/core';
|
||||
import { removeItem } from '../../common/utils';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class IntervalUpdateService {
|
||||
private interval: any;
|
||||
private actions: (() => void)[] = [];
|
||||
constructor(private zone: NgZone) {
|
||||
}
|
||||
subscribe(action: () => void) {
|
||||
this.actions.push(action);
|
||||
private interval: any;
|
||||
private actions: (() => void)[] = [];
|
||||
constructor(private zone: NgZone) {
|
||||
}
|
||||
subscribe(action: () => void) {
|
||||
this.actions.push(action);
|
||||
|
||||
if (!this.interval) {
|
||||
this.zone.runOutsideAngular(() => {
|
||||
this.interval = setInterval(() => {
|
||||
this.actions.forEach(a => a());
|
||||
}, 1000 * 10);
|
||||
});
|
||||
}
|
||||
if (!this.interval) {
|
||||
this.zone.runOutsideAngular(() => {
|
||||
this.interval = setInterval(() => {
|
||||
this.actions.forEach(a => a());
|
||||
}, 1000 * 10);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
removeItem(this.actions, action);
|
||||
return () => {
|
||||
removeItem(this.actions, action);
|
||||
|
||||
if (this.actions.length === 0) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
toggle(action: () => void) {
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
if (this.actions.length === 0) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
toggle(action: () => void) {
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
|
||||
return (on: boolean) => {
|
||||
if (on && !unsubscribe) {
|
||||
unsubscribe = this.subscribe(action);
|
||||
} else if (!on && unsubscribe) {
|
||||
unsubscribe();
|
||||
unsubscribe = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
return (on: boolean) => {
|
||||
if (on && !unsubscribe) {
|
||||
unsubscribe = this.subscribe(action);
|
||||
} else if (!on && unsubscribe) {
|
||||
unsubscribe();
|
||||
unsubscribe = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,142 +4,142 @@ import { Document, LiveResponse, IAdminServerActions, BaseValues } from '../../c
|
||||
import { ClientAdminActions } from '../../client/clientAdminActions';
|
||||
|
||||
export interface Options<T> {
|
||||
// collection
|
||||
beforeUpdate?: (updates: T[]) => void;
|
||||
onUpdated?: (added: T[], all: T[]) => void;
|
||||
onFinished?: () => void;
|
||||
// item
|
||||
decode: (fields: any[], base: BaseValues) => T;
|
||||
onUpdate?: (oldItem: T, newItem: T) => void;
|
||||
onDelete?: (item: T) => void;
|
||||
deleteItems?: boolean;
|
||||
ignore?: (item: T) => boolean;
|
||||
// collection
|
||||
beforeUpdate?: (updates: T[]) => void;
|
||||
onUpdated?: (added: T[], all: T[]) => void;
|
||||
onFinished?: () => void;
|
||||
// item
|
||||
decode: (fields: any[], base: BaseValues) => T;
|
||||
onUpdate?: (oldItem: T, newItem: T) => void;
|
||||
onDelete?: (item: T) => void;
|
||||
deleteItems?: boolean;
|
||||
ignore?: (item: T) => boolean;
|
||||
}
|
||||
|
||||
export class LiveCollection<T extends Document> {
|
||||
items: T[] = [];
|
||||
finished = false;
|
||||
private running = true;
|
||||
private itemsMap = new Map<string, T>();
|
||||
private liveTimeout: any;
|
||||
constructor(
|
||||
private name: 'events',
|
||||
private rate: number,
|
||||
private getKey: (item: T) => string,
|
||||
private options: Options<T>,
|
||||
private socket: SocketService<ClientAdminActions, IAdminServerActions>,
|
||||
private timestamp = (new Date(0)).toISOString(),
|
||||
private logError = (e: Error) => console.error(e.stack),
|
||||
) {
|
||||
}
|
||||
get(key: string) {
|
||||
return this.itemsMap.get(key);
|
||||
}
|
||||
push(item: T) {
|
||||
this.items.push(item);
|
||||
this.itemsMap.set(this.getKey(item), item);
|
||||
return item;
|
||||
}
|
||||
remove(key: string) {
|
||||
return this.server.removeItem(this.name, key)
|
||||
.then(() => this.removeItem(key, true, true));
|
||||
}
|
||||
removeItem(key: string, deleted = false, removeFromList = false) {
|
||||
const item = this.itemsMap.get(key);
|
||||
items: T[] = [];
|
||||
finished = false;
|
||||
private running = true;
|
||||
private itemsMap = new Map<string, T>();
|
||||
private liveTimeout: any;
|
||||
constructor(
|
||||
private name: 'events',
|
||||
private rate: number,
|
||||
private getKey: (item: T) => string,
|
||||
private options: Options<T>,
|
||||
private socket: SocketService<ClientAdminActions, IAdminServerActions>,
|
||||
private timestamp = (new Date(0)).toISOString(),
|
||||
private logError = (e: Error) => console.error(e.stack),
|
||||
) {
|
||||
}
|
||||
get(key: string) {
|
||||
return this.itemsMap.get(key);
|
||||
}
|
||||
push(item: T) {
|
||||
this.items.push(item);
|
||||
this.itemsMap.set(this.getKey(item), item);
|
||||
return item;
|
||||
}
|
||||
remove(key: string) {
|
||||
return this.server.removeItem(this.name, key)
|
||||
.then(() => this.removeItem(key, true, true));
|
||||
}
|
||||
removeItem(key: string, deleted = false, removeFromList = false) {
|
||||
const item = this.itemsMap.get(key);
|
||||
|
||||
if (item) {
|
||||
if (removeFromList || this.options.deleteItems) {
|
||||
removeItem(this.items, item);
|
||||
this.itemsMap.delete(key);
|
||||
} else if (deleted) {
|
||||
item.deleted = true;
|
||||
}
|
||||
if (item) {
|
||||
if (removeFromList || this.options.deleteItems) {
|
||||
removeItem(this.items, item);
|
||||
this.itemsMap.delete(key);
|
||||
} else if (deleted) {
|
||||
item.deleted = true;
|
||||
}
|
||||
|
||||
if (deleted && this.options.onDelete) {
|
||||
this.options.onDelete(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
assignAccount(id: string, account: string) {
|
||||
return this.server.assignAccount(this.name, id, account);
|
||||
}
|
||||
live(): Promise<void> {
|
||||
if (!this.running)
|
||||
return Promise.resolve();
|
||||
if (deleted && this.options.onDelete) {
|
||||
this.options.onDelete(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
assignAccount(id: string, account: string) {
|
||||
return this.server.assignAccount(this.name, id, account);
|
||||
}
|
||||
live(): Promise<void> {
|
||||
if (!this.running)
|
||||
return Promise.resolve();
|
||||
|
||||
clearTimeout(this.liveTimeout);
|
||||
clearTimeout(this.liveTimeout);
|
||||
|
||||
return this.update()
|
||||
.catch(this.logError)
|
||||
.then(more => {
|
||||
this.liveTimeout = setTimeout(() => this.live(), more ? 100 : this.rate);
|
||||
});
|
||||
}
|
||||
stop() {
|
||||
this.running = false;
|
||||
}
|
||||
read({ updates, deletes, base, more }: LiveResponse, liveFetch = true) {
|
||||
const items = updates.map(i => this.options.decode(i, base));
|
||||
return this.update()
|
||||
.catch(this.logError)
|
||||
.then(more => {
|
||||
this.liveTimeout = setTimeout(() => this.live(), more ? 100 : this.rate);
|
||||
});
|
||||
}
|
||||
stop() {
|
||||
this.running = false;
|
||||
}
|
||||
read({ updates, deletes, base, more }: LiveResponse, liveFetch = true) {
|
||||
const items = updates.map(i => this.options.decode(i, base));
|
||||
|
||||
if (liveFetch) {
|
||||
const timestamp = items
|
||||
.reduce((max, i) => max.getTime() < i.updatedAt.getTime() ? i.updatedAt : max, new Date(this.timestamp));
|
||||
this.timestamp = timestamp.toISOString();
|
||||
}
|
||||
if (liveFetch) {
|
||||
const timestamp = items
|
||||
.reduce((max, i) => max.getTime() < i.updatedAt.getTime() ? i.updatedAt : max, new Date(this.timestamp));
|
||||
this.timestamp = timestamp.toISOString();
|
||||
}
|
||||
|
||||
if (this.options.beforeUpdate) {
|
||||
this.options.beforeUpdate(items);
|
||||
}
|
||||
if (this.options.beforeUpdate) {
|
||||
this.options.beforeUpdate(items);
|
||||
}
|
||||
|
||||
const { added, all } = this.applyUpdates(items, liveFetch);
|
||||
const { added, all } = this.applyUpdates(items, liveFetch);
|
||||
|
||||
if (this.options.onUpdated && items.length) {
|
||||
this.options.onUpdated(added, all);
|
||||
}
|
||||
if (this.options.onUpdated && items.length) {
|
||||
this.options.onUpdated(added, all);
|
||||
}
|
||||
|
||||
deletes.forEach(key => this.removeItem(key, true));
|
||||
deletes.forEach(key => this.removeItem(key, true));
|
||||
|
||||
if (liveFetch) {
|
||||
const finished = this.finished || !more;
|
||||
if (liveFetch) {
|
||||
const finished = this.finished || !more;
|
||||
|
||||
if (!this.finished && finished) {
|
||||
this.finished = true;
|
||||
if (!this.finished && finished) {
|
||||
this.finished = true;
|
||||
|
||||
if (this.options.onFinished) {
|
||||
this.options.onFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.options.onFinished) {
|
||||
this.options.onFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return more;
|
||||
}
|
||||
private get server() {
|
||||
return this.socket.server;
|
||||
}
|
||||
private update() {
|
||||
return this.socket.isConnected ? this.server.getAll(this.name, this.timestamp).then(r => this.read(r)) : Promise.resolve(false);
|
||||
}
|
||||
private applyUpdates(updates: T[], liveFetch: boolean) {
|
||||
const added: T[] = [];
|
||||
const all: T[] = [];
|
||||
return more;
|
||||
}
|
||||
private get server() {
|
||||
return this.socket.server;
|
||||
}
|
||||
private update() {
|
||||
return this.socket.isConnected ? this.server.getAll(this.name, this.timestamp).then(r => this.read(r)) : Promise.resolve(false);
|
||||
}
|
||||
private applyUpdates(updates: T[], liveFetch: boolean) {
|
||||
const added: T[] = [];
|
||||
const all: T[] = [];
|
||||
|
||||
updates.forEach(update => {
|
||||
const doc = this.get(this.getKey(update));
|
||||
updates.forEach(update => {
|
||||
const doc = this.get(this.getKey(update));
|
||||
|
||||
if (doc) {
|
||||
if (this.options.onUpdate) {
|
||||
this.options.onUpdate(doc, update);
|
||||
} else {
|
||||
Object.assign(doc, update);
|
||||
}
|
||||
all.push(doc);
|
||||
} else if (!liveFetch || !this.options.ignore || !this.options.ignore(update)) {
|
||||
this.push(update);
|
||||
added.push(update);
|
||||
all.push(update);
|
||||
}
|
||||
});
|
||||
if (doc) {
|
||||
if (this.options.onUpdate) {
|
||||
this.options.onUpdate(doc, update);
|
||||
} else {
|
||||
Object.assign(doc, update);
|
||||
}
|
||||
all.push(doc);
|
||||
} else if (!liveFetch || !this.options.ignore || !this.options.ignore(update)) {
|
||||
this.push(update);
|
||||
added.push(update);
|
||||
all.push(update);
|
||||
}
|
||||
});
|
||||
|
||||
return { added, all };
|
||||
}
|
||||
return { added, all };
|
||||
}
|
||||
}
|
||||
|
||||
+385
-385
@@ -5,18 +5,18 @@ import { merge } from 'lodash';
|
||||
import { Subject } from 'rxjs';
|
||||
import { HASH } from '../../generated/hash';
|
||||
import {
|
||||
AccountData, UpdateAccountData, AccountSettings, GameStatus, SocialSiteInfo, PonyObject, JoinResponse,
|
||||
OAuthProvider, EntitiesEditorInfo, FriendData, PalettePonyInfo, HiddenPlayer
|
||||
AccountData, UpdateAccountData, AccountSettings, GameStatus, SocialSiteInfo, PonyObject, JoinResponse,
|
||||
OAuthProvider, EntitiesEditorInfo, FriendData, PalettePonyInfo, HiddenPlayer
|
||||
} from '../../common/interfaces';
|
||||
import { createDefaultPony, syncLockedPonyInfo, mockPaletteManager } from '../../common/ponyInfo';
|
||||
import { removeById, observableToPromise, delay, computeFriendsCRC } from '../../common/utils';
|
||||
import { isMod, getSupporterInviteLimit, getCharacterLimit } from '../../common/accountUtils';
|
||||
import {
|
||||
NAME_ERROR, ACCESS_ERROR, CHARACTER_SAVING_ERROR, NOT_AUTHENTICATED_ERROR, OFFLINE_ERROR, PROTECTION_ERROR
|
||||
NAME_ERROR, ACCESS_ERROR, CHARACTER_SAVING_ERROR, NOT_AUTHENTICATED_ERROR, OFFLINE_ERROR, PROTECTION_ERROR
|
||||
} from '../../common/errors';
|
||||
import { version, host } from '../../client/data';
|
||||
import {
|
||||
toSocialSiteInfo, cleanName, validatePonyName, isStandalone, attachDebugMethod
|
||||
toSocialSiteInfo, cleanName, validatePonyName, isStandalone, attachDebugMethod
|
||||
} from '../../client/clientUtils';
|
||||
import { ErrorReporter } from './errorReporter';
|
||||
import { randomString } from '../../common/stringUtils';
|
||||
@@ -26,463 +26,463 @@ import { SECOND, PLAYER_DESC_MAX_LENGTH } from '../../common/constants';
|
||||
import { canUseTag } from '../../common/tags';
|
||||
|
||||
export interface Friend extends FriendData {
|
||||
entityId: number;
|
||||
crc: number;
|
||||
online: boolean;
|
||||
ponyInfo: PalettePonyInfo | undefined;
|
||||
actualName: string;
|
||||
entityId: number;
|
||||
crc: number;
|
||||
online: boolean;
|
||||
ponyInfo: PalettePonyInfo | undefined;
|
||||
actualName: string;
|
||||
}
|
||||
|
||||
const LIMIT_ERROR = 'Request limit reached, please wait';
|
||||
const noneSite: SocialSiteInfo = { id: '', name: 'none', url: '', icon: '', color: '#222' };
|
||||
const modStatus = {
|
||||
mod: false,
|
||||
check: {} as any,
|
||||
editor: {
|
||||
names: [],
|
||||
typeToName: [],
|
||||
nameToTypes: [],
|
||||
} as EntitiesEditorInfo,
|
||||
mod: false,
|
||||
check: {} as any,
|
||||
editor: {
|
||||
names: [],
|
||||
typeToName: [],
|
||||
nameToTypes: [],
|
||||
} as EntitiesEditorInfo,
|
||||
};
|
||||
|
||||
function compareStrings(a: string | undefined, b: string | undefined) {
|
||||
return (a || '').localeCompare(b || '');
|
||||
return (a || '').localeCompare(b || '');
|
||||
}
|
||||
|
||||
function comparePonies(a: PonyObject, b: PonyObject) {
|
||||
return compareStrings(a.name, b.name) || compareStrings(a.id, b.id);
|
||||
return compareStrings(a.name, b.name) || compareStrings(a.id, b.id);
|
||||
}
|
||||
|
||||
function getDefaultPony(ponies: PonyObject[]) {
|
||||
let result = ponies[0];
|
||||
let result = ponies[0];
|
||||
|
||||
for (let i = 1; i < ponies.length; i++) {
|
||||
if (compareStrings(result.lastUsed, ponies[i].lastUsed) < 0) {
|
||||
result = ponies[i];
|
||||
}
|
||||
}
|
||||
for (let i = 1; i < ponies.length; i++) {
|
||||
if (compareStrings(result.lastUsed, ponies[i].lastUsed) < 0) {
|
||||
result = ponies[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result || createDefaultPonyObject();
|
||||
return result || createDefaultPonyObject();
|
||||
}
|
||||
|
||||
export function createDefaultPonyObject(): PonyObject {
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
info: '',
|
||||
ponyInfo: createDefaultPony(),
|
||||
};
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
info: '',
|
||||
ponyInfo: createDefaultPony(),
|
||||
};
|
||||
}
|
||||
|
||||
export function getPonyTag(pony: PonyObject, account: AccountData | undefined) {
|
||||
if (account) {
|
||||
const tag = canUseTag(account, pony.tag || '') ? pony.tag : undefined;
|
||||
return (!tag && account.supporter && !pony.hideSupport) ? `sup${account.supporter}` : tag;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
if (account) {
|
||||
const tag = canUseTag(account, pony.tag || '') ? pony.tag : undefined;
|
||||
return (!tag && account.supporter && !pony.hideSupport) ? `sup${account.supporter}` : tag;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const entityTypeToName = new Map<number, string>();
|
||||
const entityNameToTypes = new Map<string, number[]>();
|
||||
|
||||
export function getEntityNames() {
|
||||
return modStatus.editor.names;
|
||||
return modStatus.editor.names;
|
||||
}
|
||||
|
||||
export function getEntityTypesFromName(name: string) {
|
||||
return entityNameToTypes.get(name);
|
||||
return entityNameToTypes.get(name);
|
||||
}
|
||||
|
||||
export function getEntityNameFromType(type: number) {
|
||||
return entityTypeToName.get(type);
|
||||
return entityTypeToName.get(type);
|
||||
}
|
||||
|
||||
export function compareFriends(a: Friend, b: Friend) {
|
||||
return a.online !== b.online ? (a.online ? -1 : 1) : a.accountName.localeCompare(b.accountName);
|
||||
return a.online !== b.online ? (a.online ? -1 : 1) : a.accountName.localeCompare(b.accountName);
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class Model {
|
||||
loading = true;
|
||||
loadingError?: string;
|
||||
account?: AccountData;
|
||||
ponies: PonyObject[] = [];
|
||||
pending = false;
|
||||
sites: SocialSiteInfo[] = [noneSite];
|
||||
accountPromise!: Promise<AccountData | undefined>;
|
||||
accountChanged = new Subject<void>();
|
||||
protectionErrors = new Subject<void>();
|
||||
authError?: string;
|
||||
accountAlert?: string;
|
||||
mergedAccount = false;
|
||||
updating = false;
|
||||
updatingTakesLongTime = false;
|
||||
suffix = '';
|
||||
friends: Friend[] | undefined = undefined;
|
||||
private _pony: PonyObject = createDefaultPonyObject();
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private router: Router,
|
||||
private storage: StorageService,
|
||||
private errorReporter: ErrorReporter,
|
||||
) {
|
||||
this.initialize();
|
||||
loading = true;
|
||||
loadingError?: string;
|
||||
account?: AccountData;
|
||||
ponies: PonyObject[] = [];
|
||||
pending = false;
|
||||
sites: SocialSiteInfo[] = [noneSite];
|
||||
accountPromise!: Promise<AccountData | undefined>;
|
||||
accountChanged = new Subject<void>();
|
||||
protectionErrors = new Subject<void>();
|
||||
authError?: string;
|
||||
accountAlert?: string;
|
||||
mergedAccount = false;
|
||||
updating = false;
|
||||
updatingTakesLongTime = false;
|
||||
suffix = '';
|
||||
friends: Friend[] | undefined = undefined;
|
||||
private _pony: PonyObject = createDefaultPonyObject();
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private router: Router,
|
||||
private storage: StorageService,
|
||||
private errorReporter: ErrorReporter,
|
||||
) {
|
||||
this.initialize();
|
||||
|
||||
// handle completed sign-in
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data && event.data.type === 'loaded-page') {
|
||||
const path = event.data.path;
|
||||
// handle completed sign-in
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data && event.data.type === 'loaded-page') {
|
||||
const path = event.data.path;
|
||||
|
||||
if (event.source && 'close' in event.source) {
|
||||
event.source.close();
|
||||
}
|
||||
if (event.source && 'close' in event.source) {
|
||||
event.source.close();
|
||||
}
|
||||
|
||||
this.initialize();
|
||||
this.accountPromise.then(() => router.navigateByUrl(path));
|
||||
}
|
||||
});
|
||||
}
|
||||
this.initialize();
|
||||
this.accountPromise.then(() => router.navigateByUrl(path));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (DEVELOPMENT) {
|
||||
attachDebugMethod('ddos', () => this.protectionErrors.next());
|
||||
attachDebugMethod('userModel', this);
|
||||
}
|
||||
}
|
||||
private initialize() {
|
||||
this.loading = true;
|
||||
this.account = undefined;
|
||||
this.loadingError = undefined;
|
||||
this.accountAlert = undefined;
|
||||
this.ponies = [];
|
||||
this.friends = undefined;
|
||||
this.sites = [noneSite];
|
||||
this._pony = createDefaultPonyObject();
|
||||
this.storage.setItem('bid', this.storage.getItem('bid') || randomString(20));
|
||||
this.accountPromise = this.initializeAccount();
|
||||
}
|
||||
private initializeAccount(): Promise<AccountData | undefined> {
|
||||
return this.getAccount()
|
||||
.then(account => {
|
||||
if (!account) {
|
||||
throw new Error(ACCESS_ERROR);
|
||||
}
|
||||
if (DEVELOPMENT) {
|
||||
attachDebugMethod('ddos', () => this.protectionErrors.next());
|
||||
attachDebugMethod('userModel', this);
|
||||
}
|
||||
}
|
||||
private initialize() {
|
||||
this.loading = true;
|
||||
this.account = undefined;
|
||||
this.loadingError = undefined;
|
||||
this.accountAlert = undefined;
|
||||
this.ponies = [];
|
||||
this.friends = undefined;
|
||||
this.sites = [noneSite];
|
||||
this._pony = createDefaultPonyObject();
|
||||
this.storage.setItem('bid', this.storage.getItem('bid') || randomString(20));
|
||||
this.accountPromise = this.initializeAccount();
|
||||
}
|
||||
private initializeAccount(): Promise<AccountData | undefined> {
|
||||
return this.getAccount()
|
||||
.then(account => {
|
||||
if (!account) {
|
||||
throw new Error(ACCESS_ERROR);
|
||||
}
|
||||
|
||||
if ('limit' in account) {
|
||||
throw new Error(LIMIT_ERROR);
|
||||
}
|
||||
if ('limit' in account) {
|
||||
throw new Error(LIMIT_ERROR);
|
||||
}
|
||||
|
||||
this.errorReporter.configureUser({ id: account.id, username: account.name });
|
||||
this.errorReporter.configureUser({ id: account.id, username: account.name });
|
||||
|
||||
try {
|
||||
modStatus.mod = isMod(account);
|
||||
modStatus.check = account.check;
|
||||
modStatus.editor = account.editor || modStatus.editor;
|
||||
} catch { }
|
||||
try {
|
||||
modStatus.mod = isMod(account);
|
||||
modStatus.check = account.check;
|
||||
modStatus.editor = account.editor || modStatus.editor;
|
||||
} catch { }
|
||||
|
||||
if (modStatus.editor) {
|
||||
modStatus.editor.typeToName.forEach(({ type, name }) => entityTypeToName.set(type, name));
|
||||
modStatus.editor.nameToTypes.forEach(({ types, name }) => entityNameToTypes.set(name, types));
|
||||
}
|
||||
if (modStatus.editor) {
|
||||
modStatus.editor.typeToName.forEach(({ type, name }) => entityTypeToName.set(type, name));
|
||||
modStatus.editor.nameToTypes.forEach(({ types, name }) => entityNameToTypes.set(name, types));
|
||||
}
|
||||
|
||||
this.account = account;
|
||||
this.sites = [noneSite, ...(account.sites || []).map(toSocialSiteInfo)];
|
||||
this.ponies = account.ponies ? account.ponies.sort(comparePonies) : [];
|
||||
this.friends = undefined;
|
||||
this.account = account;
|
||||
this.sites = [noneSite, ...(account.sites || []).map(toSocialSiteInfo)];
|
||||
this.ponies = account.ponies ? account.ponies.sort(comparePonies) : [];
|
||||
this.friends = undefined;
|
||||
|
||||
this.selectPony(getDefaultPony(this.ponies));
|
||||
this.storage.setItem('vid', account.id);
|
||||
this.loading = false;
|
||||
this.accountAlert = account.alert;
|
||||
this.accountChanged.next();
|
||||
this.fetchFriends();
|
||||
this.selectPony(getDefaultPony(this.ponies));
|
||||
this.storage.setItem('vid', account.id);
|
||||
this.loading = false;
|
||||
this.accountAlert = account.alert;
|
||||
this.accountChanged.next();
|
||||
this.fetchFriends();
|
||||
|
||||
return account;
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
if (e.message === ACCESS_ERROR) {
|
||||
this.loading = false;
|
||||
this.storage.setItem('vid', '---');
|
||||
} else if (e.message === LIMIT_ERROR) {
|
||||
this.loadingError = 'request-limit';
|
||||
return delay(5000).then(() => this.initializeAccount());
|
||||
} else if (e.message === OFFLINE_ERROR) {
|
||||
this.loadingError = 'cannot-connect';
|
||||
return delay(5000).then(() => this.initializeAccount());
|
||||
} else if (e.message === PROTECTION_ERROR) {
|
||||
this.loadingError = 'cloudflare-error';
|
||||
this.protectionErrors.next();
|
||||
// } else if (e.message === VERSION_ERROR) {
|
||||
// this.updating = true;
|
||||
} else {
|
||||
setTimeout(() => this.loadingError = 'unexpected-error', 5 * SECOND);
|
||||
console.error(e);
|
||||
}
|
||||
return account;
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
if (e.message === ACCESS_ERROR) {
|
||||
this.loading = false;
|
||||
this.storage.setItem('vid', '---');
|
||||
} else if (e.message === LIMIT_ERROR) {
|
||||
this.loadingError = 'request-limit';
|
||||
return delay(5000).then(() => this.initializeAccount());
|
||||
} else if (e.message === OFFLINE_ERROR) {
|
||||
this.loadingError = 'cannot-connect';
|
||||
return delay(5000).then(() => this.initializeAccount());
|
||||
} else if (e.message === PROTECTION_ERROR) {
|
||||
this.loadingError = 'cloudflare-error';
|
||||
this.protectionErrors.next();
|
||||
// } else if (e.message === VERSION_ERROR) {
|
||||
// this.updating = true;
|
||||
} else {
|
||||
setTimeout(() => this.loadingError = 'unexpected-error', 5 * SECOND);
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
private fetchFriends() {
|
||||
this.getFriends()
|
||||
.then(friends => {
|
||||
this.friends = friends.map(f => ({
|
||||
...f,
|
||||
online: false,
|
||||
entityId: 0,
|
||||
crc: 0,
|
||||
ponyInfo: f.pony && decodePonyInfo(f.pony, mockPaletteManager) || undefined,
|
||||
actualName: '',
|
||||
})).sort(compareFriends);
|
||||
})
|
||||
.catch(e => {
|
||||
DEVELOPMENT && console.error(e);
|
||||
setTimeout(() => this.fetchFriends(), 5000);
|
||||
});
|
||||
}
|
||||
get characterLimit() {
|
||||
return this.account ? getCharacterLimit(this.account) : 0;
|
||||
}
|
||||
get supporterInviteLimit() {
|
||||
return this.account ? getSupporterInviteLimit(this.account) : 0;
|
||||
}
|
||||
get isMod() {
|
||||
return modStatus.mod;
|
||||
}
|
||||
get modCheck() {
|
||||
return modStatus.check;
|
||||
}
|
||||
get editorInfo() {
|
||||
return modStatus.editor;
|
||||
}
|
||||
get pony() {
|
||||
return this._pony;
|
||||
}
|
||||
get supporter() {
|
||||
return this.account && this.account.supporter || 0;
|
||||
}
|
||||
get missingBirthdate() {
|
||||
return !!this.account && !this.account.birthdate;
|
||||
}
|
||||
computeFriendsCRC() {
|
||||
return this.friends ? computeFriendsCRC(this.friends.map(f => f.accountId)) : 0;
|
||||
}
|
||||
parsePonyObject(pony: PonyObject): PonyObject {
|
||||
try {
|
||||
const ponyInfo = decompressPonyString(pony.info, true);
|
||||
return { ponyInfo, ...pony };
|
||||
} catch (e) {
|
||||
this.errorReporter.reportError(e, { ponyInfo: pony.info });
|
||||
this.errorReporter.reportError('Pony info reading error', { originalError: e.message, ponyInfo: pony.info });
|
||||
throw new Error('Error while reading pony info');
|
||||
}
|
||||
}
|
||||
selectPony(pony: PonyObject) {
|
||||
const copy = this.parsePonyObject(pony);
|
||||
copy.ponyInfo && syncLockedPonyInfo(copy.ponyInfo);
|
||||
this._pony = copy;
|
||||
}
|
||||
// account
|
||||
signIn(provider: OAuthProvider) {
|
||||
this.authError = undefined;
|
||||
this.openAuth(provider.url!);
|
||||
}
|
||||
connectSite(provider: OAuthProvider) {
|
||||
this.authError = undefined;
|
||||
this.openAuth(`${provider.url}/merge`);
|
||||
}
|
||||
signOut() {
|
||||
this.authError = undefined;
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
private fetchFriends() {
|
||||
this.getFriends()
|
||||
.then(friends => {
|
||||
this.friends = friends.map(f => ({
|
||||
...f,
|
||||
online: false,
|
||||
entityId: 0,
|
||||
crc: 0,
|
||||
ponyInfo: f.pony && decodePonyInfo(f.pony, mockPaletteManager) || undefined,
|
||||
actualName: '',
|
||||
})).sort(compareFriends);
|
||||
})
|
||||
.catch(e => {
|
||||
DEVELOPMENT && console.error(e);
|
||||
setTimeout(() => this.fetchFriends(), 5000);
|
||||
});
|
||||
}
|
||||
get characterLimit() {
|
||||
return this.account ? getCharacterLimit(this.account) : 0;
|
||||
}
|
||||
get supporterInviteLimit() {
|
||||
return this.account ? getSupporterInviteLimit(this.account) : 0;
|
||||
}
|
||||
get isMod() {
|
||||
return modStatus.mod;
|
||||
}
|
||||
get modCheck() {
|
||||
return modStatus.check;
|
||||
}
|
||||
get editorInfo() {
|
||||
return modStatus.editor;
|
||||
}
|
||||
get pony() {
|
||||
return this._pony;
|
||||
}
|
||||
get supporter() {
|
||||
return this.account && this.account.supporter || 0;
|
||||
}
|
||||
get missingBirthdate() {
|
||||
return !!this.account && !this.account.birthdate;
|
||||
}
|
||||
computeFriendsCRC() {
|
||||
return this.friends ? computeFriendsCRC(this.friends.map(f => f.accountId)) : 0;
|
||||
}
|
||||
parsePonyObject(pony: PonyObject): PonyObject {
|
||||
try {
|
||||
const ponyInfo = decompressPonyString(pony.info, true);
|
||||
return { ponyInfo, ...pony };
|
||||
} catch (e) {
|
||||
this.errorReporter.reportError(e, { ponyInfo: pony.info });
|
||||
this.errorReporter.reportError('Pony info reading error', { originalError: e.message, ponyInfo: pony.info });
|
||||
throw new Error('Error while reading pony info');
|
||||
}
|
||||
}
|
||||
selectPony(pony: PonyObject) {
|
||||
const copy = this.parsePonyObject(pony);
|
||||
copy.ponyInfo && syncLockedPonyInfo(copy.ponyInfo);
|
||||
this._pony = copy;
|
||||
}
|
||||
// account
|
||||
signIn(provider: OAuthProvider) {
|
||||
this.authError = undefined;
|
||||
this.openAuth(provider.url!);
|
||||
}
|
||||
connectSite(provider: OAuthProvider) {
|
||||
this.authError = undefined;
|
||||
this.openAuth(`${provider.url}/merge`);
|
||||
}
|
||||
signOut() {
|
||||
this.authError = undefined;
|
||||
|
||||
return this.post<AccountData | undefined>('/auth/sign-out', {}, false)
|
||||
.catch(e => console.error(e))
|
||||
.then(() => this.initialize())
|
||||
.then(() => this.router.navigate(['/']));
|
||||
}
|
||||
private openAuth(url: string) {
|
||||
url = `${host.replace(/\/$/, '')}${url}`;
|
||||
return this.post<AccountData | undefined>('/auth/sign-out', {}, false)
|
||||
.catch(e => console.error(e))
|
||||
.then(() => this.initialize())
|
||||
.then(() => this.router.navigate(['/']));
|
||||
}
|
||||
private openAuth(url: string) {
|
||||
url = `${host.replace(/\/$/, '')}${url}`;
|
||||
|
||||
if (isStandalone()) {
|
||||
window.open(url);
|
||||
} else {
|
||||
location.href = url;
|
||||
}
|
||||
}
|
||||
getAccount() {
|
||||
return this.post<AccountData | { limit: true; } | undefined>('/api1/account', {}, false);
|
||||
}
|
||||
getAccountCharacters() {
|
||||
return this.post<PonyObject[]>('/api/account-characters', {});
|
||||
}
|
||||
updateAccount(account: Partial<UpdateAccountData>) {
|
||||
return this.post<AccountData>('/api/account-update', { account })
|
||||
.then(a => merge(this.account, a));
|
||||
}
|
||||
saveSettings(settings: AccountSettings) {
|
||||
return this.post<AccountData>('/api/account-settings', { settings })
|
||||
.then(a => merge(this.account, a));
|
||||
}
|
||||
removeSite(siteId: string) {
|
||||
return this.post('/api/remove-site', { siteId })
|
||||
.then(() => {
|
||||
if (this.account && this.account.sites) {
|
||||
removeById(this.account.sites, siteId);
|
||||
}
|
||||
});
|
||||
}
|
||||
unhidePlayer(hideId: string) {
|
||||
return this.post('/api/remove-hide', { hideId });
|
||||
}
|
||||
verifyAccount() {
|
||||
const verificationId = this.storage.getItem('vid');
|
||||
const accountId = this.account && this.account.id || '---';
|
||||
if (isStandalone()) {
|
||||
window.open(url);
|
||||
} else {
|
||||
location.href = url;
|
||||
}
|
||||
}
|
||||
getAccount() {
|
||||
return this.post<AccountData | { limit: true; } | undefined>('/api1/account', {}, false);
|
||||
}
|
||||
getAccountCharacters() {
|
||||
return this.post<PonyObject[]>('/api/account-characters', {});
|
||||
}
|
||||
updateAccount(account: Partial<UpdateAccountData>) {
|
||||
return this.post<AccountData>('/api/account-update', { account })
|
||||
.then(a => merge(this.account, a));
|
||||
}
|
||||
saveSettings(settings: AccountSettings) {
|
||||
return this.post<AccountData>('/api/account-settings', { settings })
|
||||
.then(a => merge(this.account, a));
|
||||
}
|
||||
removeSite(siteId: string) {
|
||||
return this.post('/api/remove-site', { siteId })
|
||||
.then(() => {
|
||||
if (this.account && this.account.sites) {
|
||||
removeById(this.account.sites, siteId);
|
||||
}
|
||||
});
|
||||
}
|
||||
unhidePlayer(hideId: string) {
|
||||
return this.post('/api/remove-hide', { hideId });
|
||||
}
|
||||
verifyAccount() {
|
||||
const verificationId = this.storage.getItem('vid');
|
||||
const accountId = this.account && this.account.id || '---';
|
||||
|
||||
if (!this.loading && verificationId && accountId !== verificationId) {
|
||||
this.initialize();
|
||||
}
|
||||
}
|
||||
getHides(page: number) {
|
||||
return this.post<HiddenPlayer[]>('/api/get-hides', { page });
|
||||
}
|
||||
getFriends() {
|
||||
return this.post<FriendData[]>('/api/get-friends', {});
|
||||
}
|
||||
// ponies
|
||||
savePony(pony: PonyObject, fast = false) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (this.pending) {
|
||||
throw new Error('Saving in progress');
|
||||
}
|
||||
if (!this.loading && verificationId && accountId !== verificationId) {
|
||||
this.initialize();
|
||||
}
|
||||
}
|
||||
getHides(page: number) {
|
||||
return this.post<HiddenPlayer[]>('/api/get-hides', { page });
|
||||
}
|
||||
getFriends() {
|
||||
return this.post<FriendData[]>('/api/get-friends', {});
|
||||
}
|
||||
// ponies
|
||||
savePony(pony: PonyObject, fast = false) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (this.pending) {
|
||||
throw new Error('Saving in progress');
|
||||
}
|
||||
|
||||
pony.name = cleanName(pony.name);
|
||||
pony.desc = pony.desc && pony.desc.substr(0, PLAYER_DESC_MAX_LENGTH) || '';
|
||||
pony.name = cleanName(pony.name);
|
||||
pony.desc = pony.desc && pony.desc.substr(0, PLAYER_DESC_MAX_LENGTH) || '';
|
||||
|
||||
if (!validatePonyName(pony.name)) {
|
||||
throw new Error(NAME_ERROR);
|
||||
}
|
||||
if (!validatePonyName(pony.name)) {
|
||||
throw new Error(NAME_ERROR);
|
||||
}
|
||||
|
||||
if (pony.ponyInfo) {
|
||||
pony.info = compressPonyString(pony.ponyInfo);
|
||||
}
|
||||
if (pony.ponyInfo) {
|
||||
pony.info = compressPonyString(pony.ponyInfo);
|
||||
}
|
||||
|
||||
const { id, name, desc, site, tag, info, hideSupport, respawnAtSpawn } = pony;
|
||||
const { id, name, desc, site, tag, info, hideSupport, respawnAtSpawn } = pony;
|
||||
|
||||
if (!fast) {
|
||||
this.pending = true;
|
||||
}
|
||||
if (!fast) {
|
||||
this.pending = true;
|
||||
}
|
||||
|
||||
return this.post<PonyObject | undefined>('/api/pony/save', {
|
||||
pony: { id, name, desc, site, tag, info, hideSupport, respawnAtSpawn }
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
if (e.message === CHARACTER_SAVING_ERROR) {
|
||||
this.errorReporter.reportError(e, { pony });
|
||||
}
|
||||
return this.post<PonyObject | undefined>('/api/pony/save', {
|
||||
pony: { id, name, desc, site, tag, info, hideSupport, respawnAtSpawn }
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
if (e.message === CHARACTER_SAVING_ERROR) {
|
||||
this.errorReporter.reportError(e, { pony });
|
||||
}
|
||||
|
||||
throw e;
|
||||
})
|
||||
.then(newPony => {
|
||||
if (!newPony) {
|
||||
throw new Error('Failed to save pony');
|
||||
}
|
||||
throw e;
|
||||
})
|
||||
.then(newPony => {
|
||||
if (!newPony) {
|
||||
throw new Error('Failed to save pony');
|
||||
}
|
||||
|
||||
if (pony.id) {
|
||||
removeById(this.ponies, pony.id);
|
||||
} else {
|
||||
this.account!.characterCount++;
|
||||
}
|
||||
if (pony.id) {
|
||||
removeById(this.ponies, pony.id);
|
||||
} else {
|
||||
this.account!.characterCount++;
|
||||
}
|
||||
|
||||
this.ponies.push(newPony);
|
||||
this.ponies.sort(comparePonies);
|
||||
this.ponies.push(newPony);
|
||||
this.ponies.sort(comparePonies);
|
||||
|
||||
if (this.pony === pony) {
|
||||
this.selectPony(newPony);
|
||||
}
|
||||
if (this.pony === pony) {
|
||||
this.selectPony(newPony);
|
||||
}
|
||||
|
||||
return newPony;
|
||||
})
|
||||
.finally(() => this.pending = false);
|
||||
}
|
||||
removePony(pony: PonyObject) {
|
||||
return this.post('/api/pony/remove', { id: pony.id })
|
||||
.then(() => {
|
||||
removeById(this.ponies, pony.id);
|
||||
this.account!.characterCount--;
|
||||
return newPony;
|
||||
})
|
||||
.finally(() => this.pending = false);
|
||||
}
|
||||
removePony(pony: PonyObject) {
|
||||
return this.post('/api/pony/remove', { id: pony.id })
|
||||
.then(() => {
|
||||
removeById(this.ponies, pony.id);
|
||||
this.account!.characterCount--;
|
||||
|
||||
if (this.pony === pony) {
|
||||
this.selectPony(getDefaultPony(this.ponies));
|
||||
}
|
||||
});
|
||||
}
|
||||
loadPonies() {
|
||||
return this.getAccountCharacters()
|
||||
.then(ponies => {
|
||||
if (this.account) {
|
||||
this.account.ponies = ponies || [];
|
||||
this.ponies = this.account.ponies.sort(comparePonies);
|
||||
}
|
||||
});
|
||||
}
|
||||
sortPonies() {
|
||||
this.ponies.sort(comparePonies);
|
||||
}
|
||||
// game
|
||||
status(short: boolean): Promise<GameStatus> {
|
||||
let age = 6;
|
||||
if (this.pony === pony) {
|
||||
this.selectPony(getDefaultPony(this.ponies));
|
||||
}
|
||||
});
|
||||
}
|
||||
loadPonies() {
|
||||
return this.getAccountCharacters()
|
||||
.then(ponies => {
|
||||
if (this.account) {
|
||||
this.account.ponies = ponies || [];
|
||||
this.ponies = this.account.ponies.sort(comparePonies);
|
||||
}
|
||||
});
|
||||
}
|
||||
sortPonies() {
|
||||
this.ponies.sort(comparePonies);
|
||||
}
|
||||
// game
|
||||
status(short: boolean): Promise<GameStatus> {
|
||||
let age = 6;
|
||||
|
||||
if (this.account) {
|
||||
const now = new Date();
|
||||
const currentYear = now.getFullYear();
|
||||
const currentMonth = now.getMonth() + 1;
|
||||
if (this.account) {
|
||||
const now = new Date();
|
||||
const currentYear = now.getFullYear();
|
||||
const currentMonth = now.getMonth() + 1;
|
||||
|
||||
if (this.account.birthyear) {
|
||||
age = currentYear - this.account.birthyear;
|
||||
} else if (this.account.birthdate) {
|
||||
const [year, month] = this.account.birthdate.split('-');
|
||||
const before = parseInt(month, 10) > currentMonth;
|
||||
age = Math.max(0, currentYear - parseInt(year, 10) - (before ? 1 : 0));
|
||||
}
|
||||
}
|
||||
if (this.account.birthyear) {
|
||||
age = currentYear - this.account.birthyear;
|
||||
} else if (this.account.birthdate) {
|
||||
const [year, month] = this.account.birthdate.split('-');
|
||||
const before = parseInt(month, 10) > currentMonth;
|
||||
age = Math.max(0, currentYear - parseInt(year, 10) - (before ? 1 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
const params = new HttpParams()
|
||||
.set('short', short.toString())
|
||||
.set('d', age.toString())
|
||||
.set('t', (Date.now() % 0x10000).toString(16));
|
||||
const params = new HttpParams()
|
||||
.set('short', short.toString())
|
||||
.set('d', age.toString())
|
||||
.set('t', (Date.now() % 0x10000).toString(16));
|
||||
|
||||
return observableToPromise(this.http.get<GameStatus>('/api2/game/status', { params }));
|
||||
}
|
||||
join(serverId: string, ponyId: string): Promise<JoinResponse> {
|
||||
if (this.pending)
|
||||
return Promise.reject(new Error('Joining in progress'));
|
||||
if (!serverId)
|
||||
return Promise.reject(new Error('Invalid server ID'));
|
||||
if (!ponyId)
|
||||
return Promise.reject(new Error('Invalid pony ID'));
|
||||
return observableToPromise(this.http.get<GameStatus>('/api2/game/status', { params }));
|
||||
}
|
||||
join(serverId: string, ponyId: string): Promise<JoinResponse> {
|
||||
if (this.pending)
|
||||
return Promise.reject(new Error('Joining in progress'));
|
||||
if (!serverId)
|
||||
return Promise.reject(new Error('Invalid server ID'));
|
||||
if (!ponyId)
|
||||
return Promise.reject(new Error('Invalid pony ID'));
|
||||
|
||||
this.pending = true;
|
||||
this.pending = true;
|
||||
|
||||
const alert = !!this.accountAlert ? 'y' : '';
|
||||
const alert = !!this.accountAlert ? 'y' : '';
|
||||
|
||||
return this.post<JoinResponse>('/api/game/join', { version, ponyId, serverId, alert, url: location.href })
|
||||
.finally(() => this.pending = false);
|
||||
}
|
||||
private post<T = void>(url: string, data: any, authenticate = true): Promise<T> {
|
||||
if (authenticate) {
|
||||
if (!this.account) {
|
||||
return Promise.reject(new Error(NOT_AUTHENTICATED_ERROR));
|
||||
}
|
||||
return this.post<JoinResponse>('/api/game/join', { version, ponyId, serverId, alert, url: location.href })
|
||||
.finally(() => this.pending = false);
|
||||
}
|
||||
private post<T = void>(url: string, data: any, authenticate = true): Promise<T> {
|
||||
if (authenticate) {
|
||||
if (!this.account) {
|
||||
return Promise.reject(new Error(NOT_AUTHENTICATED_ERROR));
|
||||
}
|
||||
|
||||
const accountId = this.account.id + this.suffix;
|
||||
const accountName = this.account.name + this.suffix;
|
||||
data = { accountId, accountName, ...data };
|
||||
}
|
||||
const accountId = this.account.id + this.suffix;
|
||||
const accountName = this.account.name + this.suffix;
|
||||
data = { accountId, accountName, ...data };
|
||||
}
|
||||
|
||||
const params = new HttpParams()
|
||||
.set('t', (Date.now() % 0x10000).toString(16));
|
||||
const headers = new HttpHeaders({ 'api-version': HASH, 'api-bid': this.storage.getItem('bid') || '-' });
|
||||
const params = new HttpParams()
|
||||
.set('t', (Date.now() % 0x10000).toString(16));
|
||||
const headers = new HttpHeaders({ 'api-version': HASH, 'api-bid': this.storage.getItem('bid') || '-' });
|
||||
|
||||
return observableToPromise(this.http.post<T>(url, data, { params, headers }));
|
||||
}
|
||||
return observableToPromise(this.http.post<T>(url, data, { params, headers }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,99 +9,99 @@ import { MINUTE } from '../../common/constants';
|
||||
type OnModel<T> = (item: T | undefined) => void;
|
||||
|
||||
interface ModelSubscriberConfig<T> {
|
||||
fix?: (item: T) => void;
|
||||
fix?: (item: T) => void;
|
||||
}
|
||||
|
||||
interface ModelSubscription<T> {
|
||||
value: T | undefined;
|
||||
timeout: any;
|
||||
callbacks: OnModel<T>[];
|
||||
value: T | undefined;
|
||||
timeout: any;
|
||||
callbacks: OnModel<T>[];
|
||||
}
|
||||
|
||||
const unsubscribeTimeout = 1 * MINUTE;
|
||||
|
||||
export class ModelSubscriber<T> {
|
||||
private subscriptions = new Map<string, ModelSubscription<T>>();
|
||||
// private observables = new Map<string, ModelSubscription<T>>();
|
||||
constructor(
|
||||
private type: ModelTypes,
|
||||
private socket: SocketService<ClientAdminActions, IAdminServerActions>,
|
||||
private config: ModelSubscriberConfig<T> = {},
|
||||
private defaultValue: T | undefined = undefined,
|
||||
) {
|
||||
}
|
||||
// for(id: string) {
|
||||
// return this.createObservable(id);
|
||||
// }
|
||||
// private createObservable(id: string) {
|
||||
// return new Observable<T | undefined>(observer => {
|
||||
// this.socket.server.subscribe(this.model, id);
|
||||
private subscriptions = new Map<string, ModelSubscription<T>>();
|
||||
// private observables = new Map<string, ModelSubscription<T>>();
|
||||
constructor(
|
||||
private type: ModelTypes,
|
||||
private socket: SocketService<ClientAdminActions, IAdminServerActions>,
|
||||
private config: ModelSubscriberConfig<T> = {},
|
||||
private defaultValue: T | undefined = undefined,
|
||||
) {
|
||||
}
|
||||
// for(id: string) {
|
||||
// return this.createObservable(id);
|
||||
// }
|
||||
// private createObservable(id: string) {
|
||||
// return new Observable<T | undefined>(observer => {
|
||||
// this.socket.server.subscribe(this.model, id);
|
||||
|
||||
// return () => {
|
||||
// this.socket.server.unsubscribe(this.model, id);
|
||||
// };
|
||||
// });
|
||||
// }
|
||||
get(id: string) {
|
||||
const subscription = this.subscriptions.get(id);
|
||||
return subscription && subscription.value;
|
||||
}
|
||||
subscribe(id: string, callback: OnModel<T>): Subscription {
|
||||
const subscription = this.subscriptions.get(id);
|
||||
// return () => {
|
||||
// this.socket.server.unsubscribe(this.model, id);
|
||||
// };
|
||||
// });
|
||||
// }
|
||||
get(id: string) {
|
||||
const subscription = this.subscriptions.get(id);
|
||||
return subscription && subscription.value;
|
||||
}
|
||||
subscribe(id: string, callback: OnModel<T>): Subscription {
|
||||
const subscription = this.subscriptions.get(id);
|
||||
|
||||
if (subscription) {
|
||||
if (subscription.timeout) {
|
||||
clearTimeout(subscription.timeout);
|
||||
subscription.timeout = 0;
|
||||
}
|
||||
if (subscription) {
|
||||
if (subscription.timeout) {
|
||||
clearTimeout(subscription.timeout);
|
||||
subscription.timeout = 0;
|
||||
}
|
||||
|
||||
subscription.callbacks.push(callback);
|
||||
subscription.callbacks.push(callback);
|
||||
|
||||
if (subscription.value !== undefined) {
|
||||
callback(subscription.value);
|
||||
}
|
||||
} else {
|
||||
this.socket.server.subscribe(this.type, id);
|
||||
this.subscriptions.set(id, {
|
||||
value: this.defaultValue,
|
||||
timeout: 0,
|
||||
callbacks: [callback],
|
||||
});
|
||||
}
|
||||
if (subscription.value !== undefined) {
|
||||
callback(subscription.value);
|
||||
}
|
||||
} else {
|
||||
this.socket.server.subscribe(this.type, id);
|
||||
this.subscriptions.set(id, {
|
||||
value: this.defaultValue,
|
||||
timeout: 0,
|
||||
callbacks: [callback],
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
unsubscribe: () => this.unsubscribe(id, callback),
|
||||
};
|
||||
}
|
||||
unsubscribe(id: string, callback: OnModel<T>) {
|
||||
const subscription = this.subscriptions.get(id);
|
||||
return {
|
||||
unsubscribe: () => this.unsubscribe(id, callback),
|
||||
};
|
||||
}
|
||||
unsubscribe(id: string, callback: OnModel<T>) {
|
||||
const subscription = this.subscriptions.get(id);
|
||||
|
||||
if (subscription) {
|
||||
removeItem(subscription.callbacks, callback);
|
||||
if (subscription) {
|
||||
removeItem(subscription.callbacks, callback);
|
||||
|
||||
if (subscription.callbacks.length === 0) {
|
||||
subscription.timeout = setTimeout(() => {
|
||||
this.socket.server.unsubscribe(this.type, id);
|
||||
this.subscriptions.delete(id);
|
||||
}, unsubscribeTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
update(id: string, update: T) {
|
||||
const subscription = this.subscriptions.get(id);
|
||||
if (subscription.callbacks.length === 0) {
|
||||
subscription.timeout = setTimeout(() => {
|
||||
this.socket.server.unsubscribe(this.type, id);
|
||||
this.subscriptions.delete(id);
|
||||
}, unsubscribeTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
update(id: string, update: T) {
|
||||
const subscription = this.subscriptions.get(id);
|
||||
|
||||
if (update !== undefined && this.config.fix) {
|
||||
this.config.fix(update);
|
||||
}
|
||||
if (update !== undefined && this.config.fix) {
|
||||
this.config.fix(update);
|
||||
}
|
||||
|
||||
if (subscription) {
|
||||
subscription.value = update;
|
||||
subscription.callbacks.forEach(c => c(update));
|
||||
}
|
||||
}
|
||||
connected() {
|
||||
this.subscriptions.forEach((_, id) => {
|
||||
this.socket.server.subscribe(this.type, id);
|
||||
});
|
||||
}
|
||||
if (subscription) {
|
||||
subscription.value = update;
|
||||
subscription.callbacks.forEach(c => c(update));
|
||||
}
|
||||
}
|
||||
connected() {
|
||||
this.subscriptions.forEach((_, id) => {
|
||||
this.socket.server.subscribe(this.type, id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,54 +8,54 @@ import { rollbarCheckIgnore, isIgnoredError } from '../../common/rollbar';
|
||||
const host = typeof location === 'undefined' ? '' : location.host;
|
||||
|
||||
const rollbarConfig = {
|
||||
environment: ROLLBAR_ENV,
|
||||
accessToken: ROLLBAR_TOKEN,
|
||||
ignoredMessages: ['disconnected'],
|
||||
hostWhiteList: [host],
|
||||
captureUncaught: true,
|
||||
captureUnhandleRejections: true,
|
||||
// checkIgnore,
|
||||
enabled: true,
|
||||
payload: {
|
||||
environment: ROLLBAR_ENV,
|
||||
version: version, // NOTE: workaround for compilation issue
|
||||
client: {
|
||||
javascript: {
|
||||
source_map_enabled: true,
|
||||
guess_uncaught_frames: true,
|
||||
code_version: HASH,
|
||||
},
|
||||
},
|
||||
},
|
||||
environment: ROLLBAR_ENV,
|
||||
accessToken: ROLLBAR_TOKEN,
|
||||
ignoredMessages: ['disconnected'],
|
||||
hostWhiteList: [host],
|
||||
captureUncaught: true,
|
||||
captureUnhandleRejections: true,
|
||||
// checkIgnore,
|
||||
enabled: true,
|
||||
payload: {
|
||||
environment: ROLLBAR_ENV,
|
||||
version: version, // NOTE: workaround for compilation issue
|
||||
client: {
|
||||
javascript: {
|
||||
source_map_enabled: true,
|
||||
guess_uncaught_frames: true,
|
||||
code_version: HASH,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const RollbarService = new InjectionToken<Rollbar>('rollbar');
|
||||
|
||||
export function rollbarFactory() {
|
||||
if (DEVELOPMENT) {
|
||||
return undefined;
|
||||
} else {
|
||||
const rollbar = Rollbar.init(rollbarConfig);
|
||||
rollbar.configure({ checkIgnore: rollbarCheckIgnore });
|
||||
return rollbar;
|
||||
}
|
||||
if (DEVELOPMENT) {
|
||||
return undefined;
|
||||
} else {
|
||||
const rollbar = Rollbar.init(rollbarConfig);
|
||||
rollbar.configure({ checkIgnore: rollbarCheckIgnore });
|
||||
return rollbar;
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class RollbarErrorHandler extends ErrorHandler {
|
||||
constructor(private injector: Injector) {
|
||||
super();
|
||||
}
|
||||
handleError(error: any) {
|
||||
super.handleError(error);
|
||||
constructor(private injector: Injector) {
|
||||
super();
|
||||
}
|
||||
handleError(error: any) {
|
||||
super.handleError(error);
|
||||
|
||||
if (!DEVELOPMENT && rollbarConfig.accessToken) {
|
||||
const rollbar = this.injector.get(RollbarService);
|
||||
const err = error.originalError || error || {};
|
||||
if (!DEVELOPMENT && rollbarConfig.accessToken) {
|
||||
const rollbar = this.injector.get(RollbarService);
|
||||
const err = error.originalError || error || {};
|
||||
|
||||
if (!isIgnoredError(err)) {
|
||||
rollbar.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isIgnoredError(err)) {
|
||||
rollbar.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,34 +6,34 @@ import { ErrorReporter } from './errorReporter';
|
||||
|
||||
@Injectable()
|
||||
export class RollbarErrorReporter extends ErrorReporter {
|
||||
constructor(@Inject(RollbarService) private rollbar?: Rollbar) {
|
||||
super();
|
||||
}
|
||||
configureUser(person: Person) {
|
||||
if (this.rollbar) {
|
||||
this.rollbar.configure({ payload: { person }, checkIgnore: rollbarCheckIgnore });
|
||||
}
|
||||
}
|
||||
configureData(data: any) {
|
||||
if (this.rollbar) {
|
||||
this.rollbar.configure({ payload: data, checkIgnore: rollbarCheckIgnore });
|
||||
}
|
||||
}
|
||||
captureEvent(data: any) {
|
||||
if (this.rollbar) {
|
||||
this.rollbar.captureEvent(data, 'info');
|
||||
}
|
||||
}
|
||||
reportError(error: any, data?: any) {
|
||||
DEVELOPMENT && console.error(error, data);
|
||||
constructor(@Inject(RollbarService) private rollbar?: Rollbar) {
|
||||
super();
|
||||
}
|
||||
configureUser(person: Person) {
|
||||
if (this.rollbar) {
|
||||
this.rollbar.configure({ payload: { person }, checkIgnore: rollbarCheckIgnore });
|
||||
}
|
||||
}
|
||||
configureData(data: any) {
|
||||
if (this.rollbar) {
|
||||
this.rollbar.configure({ payload: data, checkIgnore: rollbarCheckIgnore });
|
||||
}
|
||||
}
|
||||
captureEvent(data: any) {
|
||||
if (this.rollbar) {
|
||||
this.rollbar.captureEvent(data, 'info');
|
||||
}
|
||||
}
|
||||
reportError(error: any, data?: any) {
|
||||
DEVELOPMENT && console.error(error, data);
|
||||
|
||||
if (this.rollbar && !isIgnoredError(error)) {
|
||||
this.rollbar.error(error, data);
|
||||
}
|
||||
}
|
||||
disable() {
|
||||
if (this.rollbar) {
|
||||
this.rollbar.configure({ enabled: false });
|
||||
}
|
||||
}
|
||||
if (this.rollbar && !isIgnoredError(error)) {
|
||||
this.rollbar.error(error, data);
|
||||
}
|
||||
}
|
||||
disable() {
|
||||
if (this.rollbar) {
|
||||
this.rollbar.configure({ enabled: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,39 +5,39 @@ import { Model } from './model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SettingsService {
|
||||
browser: BrowserSettings;
|
||||
private save: (settings: AccountSettings) => boolean = () => false;
|
||||
constructor(private storage: StorageService, private model: Model) {
|
||||
this.browser = this.storage.getJSON('browser-settings', {});
|
||||
}
|
||||
get account(): AccountSettings {
|
||||
return this.model.account ? this.model.account.settings : {};
|
||||
}
|
||||
set account(value) {
|
||||
if (this.model.account) {
|
||||
this.model.account.settings = value;
|
||||
}
|
||||
}
|
||||
saving(save: (settings: AccountSettings) => boolean) {
|
||||
this.save = save;
|
||||
}
|
||||
saveAccountSettings(settings: AccountSettings) {
|
||||
if (this.model.account) {
|
||||
this.model.account.settings = settings;
|
||||
}
|
||||
browser: BrowserSettings;
|
||||
private save: (settings: AccountSettings) => boolean = () => false;
|
||||
constructor(private storage: StorageService, private model: Model) {
|
||||
this.browser = this.storage.getJSON('browser-settings', {});
|
||||
}
|
||||
get account(): AccountSettings {
|
||||
return this.model.account ? this.model.account.settings : {};
|
||||
}
|
||||
set account(value) {
|
||||
if (this.model.account) {
|
||||
this.model.account.settings = value;
|
||||
}
|
||||
}
|
||||
saving(save: (settings: AccountSettings) => boolean) {
|
||||
this.save = save;
|
||||
}
|
||||
saveAccountSettings(settings: AccountSettings) {
|
||||
if (this.model.account) {
|
||||
this.model.account.settings = settings;
|
||||
}
|
||||
|
||||
if (settings.filterWords) {
|
||||
settings.filterWords = settings.filterWords.trim();
|
||||
}
|
||||
if (settings.filterWords) {
|
||||
settings.filterWords = settings.filterWords.trim();
|
||||
}
|
||||
|
||||
if (this.save(settings)) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.model.saveSettings(settings);
|
||||
}
|
||||
}
|
||||
saveBrowserSettings(settings?: BrowserSettings) {
|
||||
this.browser = settings || this.browser;
|
||||
this.storage.setJSON('browser-settings', this.browser);
|
||||
}
|
||||
if (this.save(settings)) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.model.saveSettings(settings);
|
||||
}
|
||||
}
|
||||
saveBrowserSettings(settings?: BrowserSettings) {
|
||||
this.browser = settings || this.browser;
|
||||
this.storage.setJSON('browser-settings', this.browser);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,82 +3,82 @@ import { Injectable } from '@angular/core';
|
||||
/* istanbul ignore next */
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class StorageService {
|
||||
private data?: Map<string, string> = undefined;
|
||||
constructor() {
|
||||
try {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
this.data = new Map();
|
||||
}
|
||||
} catch {
|
||||
this.data = new Map();
|
||||
}
|
||||
}
|
||||
getItem(key: string) {
|
||||
if (this.data) {
|
||||
return this.data.get(key);
|
||||
} else {
|
||||
try {
|
||||
const value = localStorage.getItem(key);
|
||||
return value == null ? undefined : value;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
setItem(key: string, data: string) {
|
||||
try {
|
||||
localStorage.setItem(key, data);
|
||||
this.data = undefined;
|
||||
} catch {
|
||||
if (!this.data) {
|
||||
this.data = new Map();
|
||||
}
|
||||
private data?: Map<string, string> = undefined;
|
||||
constructor() {
|
||||
try {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
this.data = new Map();
|
||||
}
|
||||
} catch {
|
||||
this.data = new Map();
|
||||
}
|
||||
}
|
||||
getItem(key: string) {
|
||||
if (this.data) {
|
||||
return this.data.get(key);
|
||||
} else {
|
||||
try {
|
||||
const value = localStorage.getItem(key);
|
||||
return value == null ? undefined : value;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
setItem(key: string, data: string) {
|
||||
try {
|
||||
localStorage.setItem(key, data);
|
||||
this.data = undefined;
|
||||
} catch {
|
||||
if (!this.data) {
|
||||
this.data = new Map();
|
||||
}
|
||||
|
||||
this.data.set(key, data);
|
||||
}
|
||||
}
|
||||
removeItem(key: string) {
|
||||
if (this.data) {
|
||||
this.data.delete(key);
|
||||
} else {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
clear() {
|
||||
if (this.data) {
|
||||
this.data.clear();
|
||||
} else {
|
||||
try {
|
||||
localStorage.clear();
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
getJSON<T>(key: string, defaultValue: T): T {
|
||||
try {
|
||||
return JSON.parse(this.getItem(key) || '');
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
setJSON(key: string, value: any) {
|
||||
this.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
getInt(key: string) {
|
||||
return parseInt(this.getItem(key) || '0', 10) | 0;
|
||||
}
|
||||
setInt(key: string, value: number) {
|
||||
this.setItem(key, value.toString(10));
|
||||
}
|
||||
getBoolean(key: string) {
|
||||
return this.getItem(key) === 'true';
|
||||
}
|
||||
setBoolean(key: string, value: boolean) {
|
||||
if (value) {
|
||||
this.setItem(key, 'true');
|
||||
} else {
|
||||
this.removeItem(key);
|
||||
}
|
||||
}
|
||||
this.data.set(key, data);
|
||||
}
|
||||
}
|
||||
removeItem(key: string) {
|
||||
if (this.data) {
|
||||
this.data.delete(key);
|
||||
} else {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
clear() {
|
||||
if (this.data) {
|
||||
this.data.clear();
|
||||
} else {
|
||||
try {
|
||||
localStorage.clear();
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
getJSON<T>(key: string, defaultValue: T): T {
|
||||
try {
|
||||
return JSON.parse(this.getItem(key) || '');
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
setJSON(key: string, value: any) {
|
||||
this.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
getInt(key: string) {
|
||||
return parseInt(this.getItem(key) || '0', 10) | 0;
|
||||
}
|
||||
setInt(key: string, value: number) {
|
||||
this.setItem(key, value.toString(10));
|
||||
}
|
||||
getBoolean(key: string) {
|
||||
return this.getItem(key) === 'true';
|
||||
}
|
||||
setBoolean(key: string, value: boolean) {
|
||||
if (value) {
|
||||
this.setItem(key, 'true');
|
||||
} else {
|
||||
this.removeItem(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user