Cap simultaneous API queries to 2 and prevent multiple requests trying to refresh the API ticket at the same time

This commit is contained in:
Mr. Stallion 2020-06-30 10:45:29 -05:00
parent f862d53243
commit 168d659785
7 changed files with 160 additions and 99 deletions

View File

@ -124,16 +124,14 @@ import {InlineDisplayMode} from '../interfaces';
} }
}); });
core.connection.onEvent('closed', (isReconnect) => { core.connection.onEvent('closed', (isReconnect) => {
if(process.env.NODE_ENV !== 'production') { log.debug(
log.debug( 'connection.closed',
'connection.closed', {
{ character: core.characters.ownCharacter?.name,
character: core.characters.ownCharacter?.name, error: this.error,
error: this.error, isReconnect
isReconnect }
} );
);
}
if(isReconnect) (<Modal>this.$refs['reconnecting']).show(true); if(isReconnect) (<Modal>this.$refs['reconnecting']).show(true);
if(this.connected) core.notifications.playSound('logout'); if(this.connected) core.notifications.playSound('logout');
@ -148,14 +146,12 @@ import {InlineDisplayMode} from '../interfaces';
core.connection.onEvent('connecting', async() => { core.connection.onEvent('connecting', async() => {
this.connecting = true; this.connecting = true;
if(process.env.NODE_ENV !== 'production') { log.debug(
log.debug( 'connection.connecting',
'connection.connecting', {
{ character: core.characters.ownCharacter?.name
character: core.characters.ownCharacter?.name }
} );
);
}
profileApiInit({ profileApiInit({
defaultCharacter: this.defaultCharacter, animateEicons: core.state.settings.animatedEicons, fuzzyDates: true, defaultCharacter: this.defaultCharacter, animateEicons: core.state.settings.animatedEicons, fuzzyDates: true,
@ -164,14 +160,12 @@ import {InlineDisplayMode} from '../interfaces';
if(core.state.settings.notifications) await core.notifications.requestPermission(); if(core.state.settings.notifications) await core.notifications.requestPermission();
}); });
core.connection.onEvent('connected', () => { core.connection.onEvent('connected', () => {
if(process.env.NODE_ENV !== 'production') { log.debug(
log.debug( 'connection.connected',
'connection.connected', {
{ character: core.characters.ownCharacter?.name
character: core.characters.ownCharacter?.name }
} );
);
}
(<Modal>this.$refs['reconnecting']).hide(); (<Modal>this.$refs['reconnecting']).hide();
this.error = ''; this.error = '';
@ -184,15 +178,13 @@ import {InlineDisplayMode} from '../interfaces';
document.title = (hasNew ? '💬 ' : '') + l(core.connection.isOpen ? 'title.connected' : 'title', core.connection.character); document.title = (hasNew ? '💬 ' : '') + l(core.connection.isOpen ? 'title.connected' : 'title', core.connection.character);
}); });
core.connection.onError((e) => { core.connection.onError((e) => {
if(process.env.NODE_ENV !== 'production') { log.debug(
log.debug( 'connection.error',
'connection.error', {
{ error: errorToString(e),
error: errorToString(e), character: core.characters.ownCharacter?.name
character: core.characters.ownCharacter?.name }
} );
);
}
if((<Error & {request?: object}>e).request !== undefined) {//catch axios network errors if((<Error & {request?: object}>e).request !== undefined) {//catch axios network errors
this.error = l('login.connectError', errorToString(e)); this.error = l('login.connectError', errorToString(e));

View File

@ -3,9 +3,15 @@ import { ipcRenderer, IpcRendererEvent } from 'electron';
import log from 'electron-log'; //tslint:disable-line:match-default-export-name import log from 'electron-log'; //tslint:disable-line:match-default-export-name
import core from '../core'; import core from '../core';
interface PendingAd {
resolve(): void,
reject(err: Error): void,
from: number;
}
export class AdCoordinatorGuest { export class AdCoordinatorGuest {
protected pendingAds: Record<string, any> = {}; protected pendingAds: Record<string, PendingAd> = {};
protected adCounter = 0; protected adCounter = 0;
constructor() { constructor() {
@ -26,7 +32,7 @@ export class AdCoordinatorGuest {
} }
requestTurnToPostAd(): Promise<void> { async requestTurnToPostAd(): Promise<void> {
return new Promise( return new Promise(
(resolve, reject) => { (resolve, reject) => {
const adId = `${Math.round(Math.random() * 1000000)}-${this.adCounter++}-${Date.now()}`; const adId = `${Math.round(Math.random() * 1000000)}-${this.adCounter++}-${Date.now()}`;
@ -42,7 +48,7 @@ export class AdCoordinatorGuest {
clear(): void { clear(): void {
_.each(this.pendingAds, (pa) => (pa.reject())); _.each(this.pendingAds, (pa) => (pa.reject(new Error('Pending ad cleared'))));
console.debug('adid.clear', _.keys(this.pendingAds), core.characters.ownCharacter?.name); console.debug('adid.clear', _.keys(this.pendingAds), core.characters.ownCharacter?.name);

View File

@ -69,19 +69,17 @@ export class AdManager {
const delayTime = Date.now(); const delayTime = Date.now();
if (process.env.NODE_ENV !== 'production') { log.debug(
log.debug( 'adManager.sendAdToChannel',
'adManager.sendAdToChannel', {
{ character: core.characters.ownCharacter?.name,
character: core.characters.ownCharacter?.name, channel: conv.channel.name,
channel: conv.channel.name, throatDelta: throatTime - initTime,
throatDelta: throatTime - initTime, delayDelta: delayTime - throatTime,
delayDelta: delayTime - throatTime, totalWait: delayTime - initTime,
totalWait: delayTime - initTime, msg
msg }
} );
);
}
await conv.sendAd(msg); await conv.sendAd(msg);
} }

View File

@ -415,19 +415,17 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
core.connection.send('LRP', {channel: this.channel.id, message: text}); core.connection.send('LRP', {channel: this.channel.id, message: text});
core.cache.markLastPostTime(); core.cache.markLastPostTime();
if (process.env.NODE_ENV !== 'production') { log.debug(
log.debug( 'conversation.sendAd',
'conversation.sendAd', {
{ character: core.characters.ownCharacter?.name,
character: core.characters.ownCharacter?.name, channel: this.channel.name,
channel: this.channel.name, throatDelta: throatTime - initTime,
throatDelta: throatTime - initTime, delayDelta: delayTime - throatTime,
delayDelta: delayTime - throatTime, totalWait: delayTime - initTime,
totalWait: delayTime - initTime, text
text }
} );
);
}
await this.addMessage( await this.addMessage(
createMessage(MessageType.Ad, core.characters.ownCharacter, text, new Date()) createMessage(MessageType.Ad, core.characters.ownCharacter, text, new Date())

View File

@ -4,6 +4,7 @@ import {Connection as Interfaces, WebSocketConnection} from './interfaces';
import ReadyState = WebSocketConnection.ReadyState; import ReadyState = WebSocketConnection.ReadyState;
import log from 'electron-log'; //tslint:disable-line:match-default-export-name import log from 'electron-log'; //tslint:disable-line:match-default-export-name
import core from '../chat/core'; import core from '../chat/core';
import throat from 'throat';
const fatalErrors = [2, 3, 4, 9, 30, 31, 33, 39, 40, 62, -4]; const fatalErrors = [2, 3, 4, 9, 30, 31, 33, 39, 40, 62, -4];
const dieErrors = [9, 30, 31, 39, 40]; const dieErrors = [9, 30, 31, 39, 40];
@ -11,12 +12,12 @@ const dieErrors = [9, 30, 31, 39, 40];
let lastFetch = Date.now(); let lastFetch = Date.now();
let lastApiTicketFetch = Date.now(); let lastApiTicketFetch = Date.now();
const queryApiThroat = throat(2);
const queryTicketThroat = throat(1);
async function queryApi(this: void, endpoint: string, data: object): Promise<AxiosResponse> { async function queryApi(this: void, endpoint: string, data: object): Promise<AxiosResponse> {
if (false) { lastFetch = Date.now();
console.log(`https://www.f-list.net/json/api/${endpoint}, gap: ${Date.now() - lastFetch}ms`);
lastFetch = Date.now();
}
return Axios.post(`https://www.f-list.net/json/api/${endpoint}`, qs.stringify(data)); return Axios.post(`https://www.f-list.net/json/api/${endpoint}`, qs.stringify(data));
} }
@ -115,13 +116,59 @@ export default class Connection implements Interfaces.Connection {
if(!keepState) this.character = ''; if(!keepState) this.character = '';
} }
get isOpen(): boolean { get isOpen(): boolean {
return this.socket !== undefined && this.socket.readyState === ReadyState.OPEN; return this.socket !== undefined && this.socket.readyState === ReadyState.OPEN;
} }
async queryApi<T = object>(endpoint: string, data?: {account?: string, ticket?: string}): Promise<T> { async queryApi<T = object>(endpoint: string, data?: {account?: string, ticket?: string}): Promise<T> {
return queryApiThroat(async() => this.queryApiExec<T>(endpoint, data));
}
protected async refreshTicket(oldTicket: string): Promise<string> {
if (this.ticket !== oldTicket) {
log.debug(
'api.ticket.renew.resolve.cache',
{
character: core.characters.ownCharacter?.name
}
);
return this.ticket;
}
if (!this.ticketProvider) {
throw new Error('No credentials set!');
}
this.ticket = await queryTicketThroat(async() => this.ticketProvider!());
log.debug(
'api.ticket.renew.resolve.refresh',
{
character: core.characters.ownCharacter?.name
}
);
return this.ticket;
}
protected async queryApiExec<T = object>(endpoint: string, data?: {account?: string, ticket?: string}): Promise<T> {
if(!this.ticketProvider) throw new Error('No credentials set!'); if(!this.ticketProvider) throw new Error('No credentials set!');
log.debug(
'api.query.start',
{
endpoint,
character: core.characters.ownCharacter?.name,
deltaToLastApiCall: Date.now() - lastFetch,
deltaToLastApiTicket: Date.now() - lastApiTicketFetch
}
);
if(data === undefined) data = {}; if(data === undefined) data = {};
data.account = this.account; data.account = this.account;
@ -140,13 +187,13 @@ export default class Connection implements Interfaces.Connection {
} }
); );
data.ticket = this.ticket = await this.ticketProvider(); data.ticket = await this.refreshTicket(data.ticket);
res = <T & {error: string}>(await queryApi(endpoint, data)).data; res = <T & {error: string}>(await queryApi(endpoint, data)).data;
} }
if(res.error !== '') { if(res.error !== '') {
log.debug( log.debug(
'error.api.query', 'api.query.error',
{ {
error: res.error, error: res.error,
endpoint, endpoint,
@ -160,6 +207,17 @@ export default class Connection implements Interfaces.Connection {
(<Error & {request: true}>error).request = true; (<Error & {request: true}>error).request = true;
throw error; throw error;
} }
log.debug(
'api.query.success',
{
endpoint,
character: core.characters.ownCharacter?.name,
deltaToLastApiCall: Date.now() - lastFetch,
deltaToLastApiTicket: Date.now() - lastApiTicketFetch
}
);
return res; return res;
} }
@ -232,43 +290,48 @@ export default class Connection implements Interfaces.Connection {
private async getTicket(password: string): Promise<string> { private async getTicket(password: string): Promise<string> {
console.log('Acquiring new API ticket'); console.log('Acquiring new API ticket');
const oldLastApiTicketFetch = lastApiTicketFetch;
if(process.env.NODE_ENV !== 'production') { log.debug(
console.log(`https://www.f-list.net/json/getApiTicket.php, gap: ${Date.now() - lastApiTicketFetch}ms`); 'api.getTicket.start',
{
character: core.characters.ownCharacter?.name,
deltaToLastApiCall: Date.now() - lastFetch,
deltaToLastApiTicket: Date.now() - oldLastApiTicketFetch
}
);
log.debug( lastApiTicketFetch = Date.now();
'api.getTicket',
{
character: core.characters.ownCharacter?.name,
deltaToLastApiCall: Date.now() - lastFetch,
deltaToLastApiTicket: Date.now() - lastApiTicketFetch
}
);
lastApiTicketFetch = Date.now();
}
const data = <{ticket?: string, error: string}>(await Axios.post('https://www.f-list.net/json/getApiTicket.php', qs.stringify( const data = <{ticket?: string, error: string}>(await Axios.post('https://www.f-list.net/json/getApiTicket.php', qs.stringify(
{account: this.account, password, no_friends: true, no_bookmarks: true, no_characters: true}))).data; {account: this.account, password, no_friends: true, no_bookmarks: true, no_characters: true}))).data;
if(data.ticket !== undefined) return data.ticket; if(data.ticket !== undefined) {
log.debug(
if(process.env.NODE_ENV !== 'production') { 'api.getTicket.success',
console.error('API Ticket Error', data.error);
log.error(
'error.api.getTicket',
{ {
character: core.characters.ownCharacter.name, character: core.characters.ownCharacter?.name,
error: data.error,
deltaToLastApiCall: Date.now() - lastFetch, deltaToLastApiCall: Date.now() - lastFetch,
deltaToLastApiTicket: Date.now() - lastApiTicketFetch deltaToLastApiTicket: Date.now() - oldLastApiTicketFetch
} }
); );
lastApiTicketFetch = Date.now(); return data.ticket;
} }
console.error('API Ticket Error', data.error);
log.error(
'error.api.getTicket',
{
character: core.characters.ownCharacter.name,
error: data.error,
deltaToLastApiCall: Date.now() - lastFetch,
deltaToLastApiTicket: Date.now() - oldLastApiTicketFetch
}
);
throw new Error(data.error); throw new Error(data.error);
} }

View File

@ -51,4 +51,4 @@ initCore(connection, Logs, SettingsStore, Notifications);
new Index({ //tslint:disable-line:no-unused-expression new Index({ //tslint:disable-line:no-unused-expression
el: '#app' el: '#app'
}); });

View File

@ -295,10 +295,14 @@
async updateMeta(name: string): Promise<void> { async updateMeta(name: string): Promise<void> {
await this.updateImages(); await Promise.all(
await this.updateGuestbook(); [
await this.updateFriends(); this.updateImages(),
await this.updateGroups(); this.updateGuestbook(),
this.updateFriends(),
this.updateGroups()
]
);
await core.cache.profileCache.registerMeta( await core.cache.profileCache.registerMeta(
name, name,