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) => {
if(process.env.NODE_ENV !== 'production') {
log.debug(
'connection.closed',
{
character: core.characters.ownCharacter?.name,
error: this.error,
isReconnect
}
);
}
log.debug(
'connection.closed',
{
character: core.characters.ownCharacter?.name,
error: this.error,
isReconnect
}
);
if(isReconnect) (<Modal>this.$refs['reconnecting']).show(true);
if(this.connected) core.notifications.playSound('logout');
@ -148,14 +146,12 @@ import {InlineDisplayMode} from '../interfaces';
core.connection.onEvent('connecting', async() => {
this.connecting = true;
if(process.env.NODE_ENV !== 'production') {
log.debug(
'connection.connecting',
{
character: core.characters.ownCharacter?.name
}
);
}
log.debug(
'connection.connecting',
{
character: core.characters.ownCharacter?.name
}
);
profileApiInit({
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();
});
core.connection.onEvent('connected', () => {
if(process.env.NODE_ENV !== 'production') {
log.debug(
'connection.connected',
{
character: core.characters.ownCharacter?.name
}
);
}
log.debug(
'connection.connected',
{
character: core.characters.ownCharacter?.name
}
);
(<Modal>this.$refs['reconnecting']).hide();
this.error = '';
@ -184,15 +178,13 @@ import {InlineDisplayMode} from '../interfaces';
document.title = (hasNew ? '💬 ' : '') + l(core.connection.isOpen ? 'title.connected' : 'title', core.connection.character);
});
core.connection.onError((e) => {
if(process.env.NODE_ENV !== 'production') {
log.debug(
'connection.error',
{
error: errorToString(e),
character: core.characters.ownCharacter?.name
}
);
}
log.debug(
'connection.error',
{
error: errorToString(e),
character: core.characters.ownCharacter?.name
}
);
if((<Error & {request?: object}>e).request !== undefined) {//catch axios network errors
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 core from '../core';
interface PendingAd {
resolve(): void,
reject(err: Error): void,
from: number;
}
export class AdCoordinatorGuest {
protected pendingAds: Record<string, any> = {};
protected pendingAds: Record<string, PendingAd> = {};
protected adCounter = 0;
constructor() {
@ -26,7 +32,7 @@ export class AdCoordinatorGuest {
}
requestTurnToPostAd(): Promise<void> {
async requestTurnToPostAd(): Promise<void> {
return new Promise(
(resolve, reject) => {
const adId = `${Math.round(Math.random() * 1000000)}-${this.adCounter++}-${Date.now()}`;
@ -42,7 +48,7 @@ export class AdCoordinatorGuest {
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);

View File

@ -69,19 +69,17 @@ export class AdManager {
const delayTime = Date.now();
if (process.env.NODE_ENV !== 'production') {
log.debug(
'adManager.sendAdToChannel',
{
character: core.characters.ownCharacter?.name,
channel: conv.channel.name,
throatDelta: throatTime - initTime,
delayDelta: delayTime - throatTime,
totalWait: delayTime - initTime,
msg
}
);
}
log.debug(
'adManager.sendAdToChannel',
{
character: core.characters.ownCharacter?.name,
channel: conv.channel.name,
throatDelta: throatTime - initTime,
delayDelta: delayTime - throatTime,
totalWait: delayTime - initTime,
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.cache.markLastPostTime();
if (process.env.NODE_ENV !== 'production') {
log.debug(
'conversation.sendAd',
{
character: core.characters.ownCharacter?.name,
channel: this.channel.name,
throatDelta: throatTime - initTime,
delayDelta: delayTime - throatTime,
totalWait: delayTime - initTime,
text
}
);
}
log.debug(
'conversation.sendAd',
{
character: core.characters.ownCharacter?.name,
channel: this.channel.name,
throatDelta: throatTime - initTime,
delayDelta: delayTime - throatTime,
totalWait: delayTime - initTime,
text
}
);
await this.addMessage(
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 log from 'electron-log'; //tslint:disable-line:match-default-export-name
import core from '../chat/core';
import throat from 'throat';
const fatalErrors = [2, 3, 4, 9, 30, 31, 33, 39, 40, 62, -4];
const dieErrors = [9, 30, 31, 39, 40];
@ -11,12 +12,12 @@ const dieErrors = [9, 30, 31, 39, 40];
let lastFetch = 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> {
if (false) {
console.log(`https://www.f-list.net/json/api/${endpoint}, gap: ${Date.now() - lastFetch}ms`);
lastFetch = Date.now();
}
lastFetch = Date.now();
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 = '';
}
get isOpen(): boolean {
return this.socket !== undefined && this.socket.readyState === ReadyState.OPEN;
}
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!');
log.debug(
'api.query.start',
{
endpoint,
character: core.characters.ownCharacter?.name,
deltaToLastApiCall: Date.now() - lastFetch,
deltaToLastApiTicket: Date.now() - lastApiTicketFetch
}
);
if(data === undefined) data = {};
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;
}
if(res.error !== '') {
log.debug(
'error.api.query',
'api.query.error',
{
error: res.error,
endpoint,
@ -160,6 +207,17 @@ export default class Connection implements Interfaces.Connection {
(<Error & {request: true}>error).request = true;
throw error;
}
log.debug(
'api.query.success',
{
endpoint,
character: core.characters.ownCharacter?.name,
deltaToLastApiCall: Date.now() - lastFetch,
deltaToLastApiTicket: Date.now() - lastApiTicketFetch
}
);
return res;
}
@ -232,43 +290,48 @@ export default class Connection implements Interfaces.Connection {
private async getTicket(password: string): Promise<string> {
console.log('Acquiring new API ticket');
const oldLastApiTicketFetch = lastApiTicketFetch;
if(process.env.NODE_ENV !== 'production') {
console.log(`https://www.f-list.net/json/getApiTicket.php, gap: ${Date.now() - lastApiTicketFetch}ms`);
log.debug(
'api.getTicket.start',
{
character: core.characters.ownCharacter?.name,
deltaToLastApiCall: Date.now() - lastFetch,
deltaToLastApiTicket: Date.now() - oldLastApiTicketFetch
}
);
log.debug(
'api.getTicket',
{
character: core.characters.ownCharacter?.name,
deltaToLastApiCall: Date.now() - lastFetch,
deltaToLastApiTicket: Date.now() - lastApiTicketFetch
}
);
lastApiTicketFetch = Date.now();
}
lastApiTicketFetch = Date.now();
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;
if(data.ticket !== undefined) return data.ticket;
if(process.env.NODE_ENV !== 'production') {
console.error('API Ticket Error', data.error);
log.error(
'error.api.getTicket',
if(data.ticket !== undefined) {
log.debug(
'api.getTicket.success',
{
character: core.characters.ownCharacter.name,
error: data.error,
character: core.characters.ownCharacter?.name,
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);
}

View File

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

View File

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