Enforce 5s cooldown between ads across all connected characters
This commit is contained in:
parent
9879195a41
commit
d2de87b554
|
@ -2,7 +2,10 @@
|
|||
|
||||
## Canary
|
||||
* Fix caching issue that causes cache misses on charater page metadata
|
||||
* URL preview fixes for Redgifs, Gelbooru, Tumblr, and Gifmixxx
|
||||
* Fix ad posting issue that sometimes disconnects characters if multiple characters are in use
|
||||
* URL preview fixes for Redgifs, Gelbooru, Tumblr, and Gifmixxx
|
||||
* All dependencies are now up to date
|
||||
|
||||
|
||||
## 1.0.1
|
||||
|
||||
|
|
|
@ -126,8 +126,8 @@ import {InlineDisplayMode} from '../interfaces';
|
|||
core.connection.onEvent('closed', (isReconnect) => {
|
||||
if(process.env.NODE_ENV !== 'production') {
|
||||
log.debug(
|
||||
'connection.closed',
|
||||
{
|
||||
type: 'connection.closed',
|
||||
character: core.characters.ownCharacter?.name,
|
||||
error: this.error,
|
||||
isReconnect
|
||||
|
@ -141,6 +141,7 @@ import {InlineDisplayMode} from '../interfaces';
|
|||
this.connecting = false;
|
||||
|
||||
AdManager.onConnectionClosed();
|
||||
core.adCoordinator.clear();
|
||||
|
||||
document.title = l('title');
|
||||
});
|
||||
|
@ -149,8 +150,8 @@ import {InlineDisplayMode} from '../interfaces';
|
|||
|
||||
if(process.env.NODE_ENV !== 'production') {
|
||||
log.debug(
|
||||
'connection.connecting',
|
||||
{
|
||||
type: 'connection.connecting',
|
||||
character: core.characters.ownCharacter?.name
|
||||
}
|
||||
);
|
||||
|
@ -165,8 +166,8 @@ import {InlineDisplayMode} from '../interfaces';
|
|||
core.connection.onEvent('connected', () => {
|
||||
if(process.env.NODE_ENV !== 'production') {
|
||||
log.debug(
|
||||
'connection.connected',
|
||||
{
|
||||
type: 'connection.connected',
|
||||
character: core.characters.ownCharacter?.name
|
||||
}
|
||||
);
|
||||
|
@ -185,8 +186,8 @@ import {InlineDisplayMode} from '../interfaces';
|
|||
core.connection.onError((e) => {
|
||||
if(process.env.NODE_ENV !== 'production') {
|
||||
log.debug(
|
||||
'connection.error',
|
||||
{
|
||||
type: 'connection.error',
|
||||
error: errorToString(e),
|
||||
character: core.characters.ownCharacter?.name
|
||||
}
|
||||
|
@ -214,7 +215,7 @@ import {InlineDisplayMode} from '../interfaces';
|
|||
|
||||
// skipping await
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
await core.notifications.initSounds(['attention', 'login', 'logout', 'modalert', 'newnote']);
|
||||
core.notifications.initSounds(['attention', 'login', 'logout', 'modalert', 'newnote']);
|
||||
|
||||
core.connection.connect(this.selectedCharacter.name);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import _ from 'lodash';
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
||||
import log from 'electron-log'; //tslint:disable-line:match-default-export-name
|
||||
import core from '../core';
|
||||
|
||||
|
||||
export class AdCoordinatorGuest {
|
||||
protected pendingAds: Record<string, any> = {};
|
||||
protected adCounter = 0;
|
||||
|
||||
constructor() {
|
||||
ipcRenderer.on('grant-send-ad', (_event: IpcRendererEvent, adId: string) => this.processPendingAd(adId));
|
||||
}
|
||||
|
||||
processPendingAd(adId: string): void {
|
||||
if (!(adId in this.pendingAds)) {
|
||||
log.debug('adid.pending.miss', {adId, character: core.characters.ownCharacter?.name});
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug('adid.pending.process', {adId, character: core.characters.ownCharacter?.name});
|
||||
|
||||
this.pendingAds[adId].resolve();
|
||||
|
||||
delete this.pendingAds[adId];
|
||||
}
|
||||
|
||||
|
||||
requestTurnToPostAd(): Promise<void> {
|
||||
return new Promise(
|
||||
(resolve, reject) => {
|
||||
const adId = `${Math.round(Math.random() * 1000000)}-${this.adCounter++}-${Date.now()}`;
|
||||
|
||||
this.pendingAds[adId] = { resolve, reject, from: Date.now() };
|
||||
|
||||
log.debug('adid.request', {adId, character: core.characters.ownCharacter?.name});
|
||||
|
||||
ipcRenderer.send('request-send-ad', adId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
clear(): void {
|
||||
_.each(this.pendingAds, (pa) => (pa.reject()));
|
||||
|
||||
console.debug('adid.clear', _.keys(this.pendingAds), core.characters.ownCharacter?.name);
|
||||
|
||||
this.pendingAds = {};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import throat from 'throat';
|
||||
import Bluebird from 'bluebird';
|
||||
import { IpcMainEvent } from 'electron';
|
||||
import log from 'electron-log'; //tslint:disable-line:match-default-export-name
|
||||
|
||||
const adCoordinatorThroat = throat(1);
|
||||
|
||||
|
||||
export class AdCoordinatorHost {
|
||||
static readonly MIN_DISTANCE = 5000;
|
||||
private lastPost = Date.now();
|
||||
|
||||
async processAdRequest(event: IpcMainEvent, adId: string) {
|
||||
await adCoordinatorThroat(
|
||||
async() => {
|
||||
const sinceLastPost = Date.now() - this.lastPost;
|
||||
const waitTime = Math.max(0, AdCoordinatorHost.MIN_DISTANCE - sinceLastPost);
|
||||
|
||||
log.debug('adid.request.host', {adId, sinceLastPost, waitTime});
|
||||
|
||||
await Bluebird.delay(waitTime);
|
||||
|
||||
log.debug('adid.request.host.grant', {adId, sinceLastPost, waitTime});
|
||||
|
||||
event.reply('grant-send-ad', adId);
|
||||
|
||||
this.lastPost = Date.now();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -71,8 +71,8 @@ export class AdManager {
|
|||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
log.debug(
|
||||
'adManager.sendAdToChannel',
|
||||
{
|
||||
type: 'sendAdToChannel',
|
||||
character: core.characters.ownCharacter?.name,
|
||||
channel: conv.channel.name,
|
||||
throatDelta: throatTime - initTime,
|
||||
|
|
|
@ -147,6 +147,7 @@ abstract class Conversation implements Interfaces.Conversation {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class PrivateConversation extends Conversation implements Interfaces.PrivateConversation {
|
||||
readonly name = this.character.name;
|
||||
readonly context = CommandContext.Private;
|
||||
|
@ -402,7 +403,12 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
|
|||
async() => {
|
||||
const throatTime = Date.now();
|
||||
|
||||
await Conversation.testPostDelay();
|
||||
await Promise.all(
|
||||
[
|
||||
await Conversation.testPostDelay(),
|
||||
await core.adCoordinator.requestTurnToPostAd()
|
||||
]
|
||||
);
|
||||
|
||||
const delayTime = Date.now();
|
||||
|
||||
|
@ -411,8 +417,8 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
|
|||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
log.debug(
|
||||
'conversation.sendAd',
|
||||
{
|
||||
type: 'sendAd',
|
||||
character: core.characters.ownCharacter?.name,
|
||||
channel: this.channel.name,
|
||||
throatDelta: throatTime - initTime,
|
||||
|
|
|
@ -5,6 +5,7 @@ import BBCodeParser from './bbcode';
|
|||
import {Settings as SettingsImpl} from './common';
|
||||
import Conversations from './conversations';
|
||||
import {Channel, Character, Connection, Conversation, Logs, Notifications, Settings, State as StateInterface} from './interfaces';
|
||||
import { AdCoordinatorGuest } from './ads/ad-coordinator-guest';
|
||||
|
||||
function createBBCodeParser(): BBCodeParser {
|
||||
const parser = new BBCodeParser();
|
||||
|
@ -65,6 +66,8 @@ const data = {
|
|||
characters: <Character.State | undefined>undefined,
|
||||
notifications: <Notifications | undefined>undefined,
|
||||
cache: <CacheManager | undefined>undefined,
|
||||
adCoordinator: <AdCoordinatorGuest | undefined>undefined,
|
||||
|
||||
register<K extends 'characters' | 'conversations' | 'channels'>(module: K, subState: VueState[K]): void {
|
||||
Vue.set(vue, module, subState);
|
||||
(<VueState[K]>data[module]) = subState;
|
||||
|
@ -86,6 +89,7 @@ export function init(this: any, connection: Connection, logsClass: new() => Logs
|
|||
data.settingsStore = new settingsClass();
|
||||
data.notifications = new notificationsClass();
|
||||
data.cache = new CacheManager();
|
||||
data.adCoordinator = new AdCoordinatorGuest();
|
||||
|
||||
// tslint:disable-next-line no-floating-promises
|
||||
data.cache.start();
|
||||
|
@ -111,6 +115,7 @@ export interface Core {
|
|||
readonly bbCodeParser: BBCodeParser
|
||||
readonly notifications: Notifications
|
||||
readonly cache: CacheManager
|
||||
readonly adCoordinator: AdCoordinatorGuest;
|
||||
|
||||
watch<T>(getter: (this: VueState) => T, callback: WatchHandler<T>): void
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@ import fetch from 'node-fetch';
|
|||
import MenuItemConstructorOptions = Electron.MenuItemConstructorOptions;
|
||||
import * as _ from 'lodash';
|
||||
import DownloadItem = Electron.DownloadItem;
|
||||
import { AdCoordinatorHost } from '../chat/ads/ad-coordinator-host';
|
||||
import { IpcMainEvent } from 'electron';
|
||||
|
||||
//tslint:disable-next-line:no-require-imports
|
||||
const pck = require('./package.json');
|
||||
|
@ -542,6 +544,12 @@ function onReady(): void {
|
|||
const index = characters.indexOf(character);
|
||||
if(index !== -1) characters.splice(index, 1);
|
||||
});
|
||||
|
||||
|
||||
const adCoordinator = new AdCoordinatorHost();
|
||||
electron.ipcMain.on('request-send-ad', (event: IpcMainEvent, adId: string) => (adCoordinator.processAdRequest(event, adId)));
|
||||
|
||||
|
||||
const emptyBadge = electron.nativeImage.createEmpty();
|
||||
|
||||
//tslint:disable-next-line:no-require-imports no-unsafe-any
|
||||
|
|
|
@ -131,8 +131,8 @@ export default class Connection implements Interfaces.Connection {
|
|||
|
||||
if(res.error === 'Invalid ticket.' || res.error === 'Your login ticket has expired (five minutes) or no ticket requested.') {
|
||||
log.debug(
|
||||
'api.ticket.loss',
|
||||
{
|
||||
type: 'api.ticket.loss',
|
||||
error: res.error,
|
||||
character: core.characters.ownCharacter?.name,
|
||||
deltaToLastApiCall: Date.now() - lastFetch,
|
||||
|
@ -146,8 +146,8 @@ export default class Connection implements Interfaces.Connection {
|
|||
|
||||
if(res.error !== '') {
|
||||
log.debug(
|
||||
'error.api.query',
|
||||
{
|
||||
type: 'error.api.query',
|
||||
error: res.error,
|
||||
endpoint,
|
||||
character: core.characters.ownCharacter?.name,
|
||||
|
@ -237,8 +237,8 @@ export default class Connection implements Interfaces.Connection {
|
|||
console.log(`https://www.f-list.net/json/getApiTicket.php, gap: ${Date.now() - lastApiTicketFetch}ms`);
|
||||
|
||||
log.debug(
|
||||
'api.getTicket',
|
||||
{
|
||||
type: 'api.getTicket',
|
||||
character: core.characters.ownCharacter?.name,
|
||||
deltaToLastApiCall: Date.now() - lastFetch,
|
||||
deltaToLastApiTicket: Date.now() - lastApiTicketFetch
|
||||
|
@ -257,8 +257,8 @@ export default class Connection implements Interfaces.Connection {
|
|||
console.error('API Ticket Error', data.error);
|
||||
|
||||
log.error(
|
||||
'error.api.getTicket',
|
||||
{
|
||||
type: 'error.api.getTicket',
|
||||
character: core.characters.ownCharacter.name,
|
||||
error: data.error,
|
||||
deltaToLastApiCall: Date.now() - lastFetch,
|
||||
|
|
Loading…
Reference in New Issue