conflictable completed

This commit is contained in:
Michael Peters 2021-11-20 19:06:50 -06:00
parent f9221bd9a6
commit a6097f9ccb
11 changed files with 488 additions and 409 deletions

View File

@ -1,15 +1,19 @@
// Intended to be used with message lists
import { stringify } from "querystring";
import { AutoVerifier } from "./auto-verifier";
import { AutoVerifier, AutoVerifierChangesType } from "./auto-verifier";
import { Changes, WithEquals } from "./data-types";
export interface PartialListQuery {
export interface PartialMessageListQuery {
channelId: string;
messageId: string | null;
number: number;
}
export interface IDQuery {
id: string;
}
export class AutoVerifierWithArg<T, K> {
private tokenAutoVerifiers = new Map<string, AutoVerifier<T>>();
private tokenQueries = new Map<string, K>();
@ -23,17 +27,35 @@ export class AutoVerifierWithArg<T, K> {
private verifyFunc: (query: K, primaryResult: T | null, trustedResult: T | null) => Promise<void>
) {}
static createPartialListAutoVerifier<T extends WithEquals<T> & { id: string }>(
primaryFunc: (query: PartialListQuery) => Promise<T[] | null>,
trustedFunc: (query: PartialListQuery) => Promise<T[] | null>,
changesFunc: (query: PartialListQuery, changes: Changes<T>) => Promise<void>
static createStandardPartialMessageListAutoVerifier<T extends WithEquals<T> & { id: string }>(
primaryFunc: (query: PartialMessageListQuery) => Promise<T[] | null>,
trustedFunc: (query: PartialMessageListQuery) => Promise<T[] | null>,
changesFunc: (query: PartialMessageListQuery, changesType: AutoVerifierChangesType, changes: Changes<T>) => Promise<void>
) {
return new AutoVerifierWithArg<T[], PartialListQuery>(
return new AutoVerifierWithArg<T[], PartialMessageListQuery>(
query => `ch#${query.channelId} m#${query.messageId}->${query.number}`,
query => primaryFunc(query),
query => trustedFunc(query),
async (query: PartialListQuery, primaryResult: T[] | null, trustedResult: T[] | null) => {
await changesFunc(query, AutoVerifier.getChanges(primaryResult, trustedResult));
async (query: PartialMessageListQuery, primaryResult: T[] | null, trustedResult: T[] | null) => {
let changes = AutoVerifier.getChanges<T>(primaryResult, trustedResult);
let changesType = AutoVerifier.getListChangesType<T>(primaryResult, trustedResult, changes);
await changesFunc(query, changesType, changes);
}
);
}
static createStandardIDQueriedSingleAutoVerifier<T extends WithEquals<T> & { id: string }>(
primaryFunc: (query: IDQuery) => Promise<T | null>,
trustedFunc: (query: IDQuery) => Promise<T | null>,
changesFunc: (query: IDQuery, changesType: AutoVerifierChangesType, primaryResult: T | null, trustedResult: T | null) => Promise<void>
) {
return new AutoVerifierWithArg<T, IDQuery>(
query => `id#${query.id}`,
query => primaryFunc(query),
query => trustedFunc(query),
async (query: IDQuery, primaryResult: T | null, trustedResult: T | null) => {
let changesType = AutoVerifier.getSingleChangesType<T>(primaryResult, trustedResult);
await changesFunc(query, changesType, primaryResult, trustedResult);
}
);
}

View File

@ -4,12 +4,21 @@ import Logger from '../../logger/logger';
import { Changes, WithEquals } from './data-types';
const LOG = Logger.create(__filename, electronConsole);
export enum AutoVerifierChangesType {
NONE, // Both primaryFunc and trustedFunc returned null
PRIMARY_ONLY, // primaryFunc returned non-null and trustedFunc returned null
TRUSTED_ONLY, // trustedFunc returned non-null and primaryFunc returned null
VERIFIED, // primaryFunc and trustedFunc returned the same non-null result
CONFLICT, // primaryFunc and trustedFunc returned conflicting non-null results
};
/**
* This is probably complex piece of code in this entire project.
* Some complexity comes because the result of the trusted function
* can be invalidated while we are awaiting the trusted function.
* That complexity stacks on top of the already complex cache-verification
* to make this one fiesta of a class.
* If you have to edit this it's a very sad day.
*/
export class AutoVerifier<T> {
private trustedPromise: Promise<T | null> | null = null;
@ -61,16 +70,61 @@ export class AutoVerifier<T> {
return changes;
}
static getListChangesType<T>(primaryResult: T[] | null, trustedResult: T[] | null, changes: Changes<T>): AutoVerifierChangesType {
if (primaryResult === null && trustedResult === null) {
return AutoVerifierChangesType.NONE;
} else if (trustedResult === null) {
return AutoVerifierChangesType.PRIMARY_ONLY;
} else if (primaryResult === null) {
return AutoVerifierChangesType.TRUSTED_ONLY;
} else if (changes.added.length === 0 && changes.updated.length === 0 && changes.deleted.length === 0) {
return AutoVerifierChangesType.VERIFIED;
} else {
return AutoVerifierChangesType.CONFLICT;
}
}
static getSingleChangesType<T extends WithEquals<T>>(primaryResult: T | null, trustedResult: T | null): AutoVerifierChangesType {
if (primaryResult === null && trustedResult === null) {
return AutoVerifierChangesType.NONE;
} else if (trustedResult === null) {
return AutoVerifierChangesType.PRIMARY_ONLY;
} else if (primaryResult === null) {
return AutoVerifierChangesType.TRUSTED_ONLY;
} else if (primaryResult.equals(trustedResult)) {
return AutoVerifierChangesType.VERIFIED;
} else {
return AutoVerifierChangesType.CONFLICT;
}
}
static createStandardListAutoVerifier<T extends WithEquals<T> & { id: string }>(
primaryFunc: () => Promise<T[] | null>,
trustedFunc: () => Promise<T[] | null>,
changesFunc: (changes: Changes<T>) => Promise<void>
changesFunc: (changesType: AutoVerifierChangesType, changes: Changes<T>) => Promise<void>
) {
return new AutoVerifier<T[]>(
primaryFunc,
trustedFunc,
async (primaryResult: T[] | null, trustedResult: T[] | null) => {
await changesFunc(AutoVerifier.getChanges(primaryResult, trustedResult));
let changes = AutoVerifier.getChanges<T>(primaryResult, trustedResult);
let changesType = AutoVerifier.getListChangesType<T>(primaryResult, trustedResult, changes);
await changesFunc(changesType, changes);
}
);
}
static createStandardSingleAutoVerifier<T extends WithEquals<T>>(
primaryFunc: () => Promise<T | null>,
trustedFunc: () => Promise<T | null>,
changesFunc: (changesType: AutoVerifierChangesType, primaryResult: T | null, trustedResult: T | null) => Promise<void>
) {
return new AutoVerifier<T>(
primaryFunc,
trustedFunc,
async (primaryResult: T | null, trustedResult: T | null) => {
let changesType = AutoVerifier.getSingleChangesType<T>(primaryResult, trustedResult);
await changesFunc(changesType, primaryResult, trustedResult);
}
);
}
@ -85,7 +139,7 @@ export class AutoVerifier<T> {
// If the primary fetchable returns null but has not been verified yet, this will return the result of the trusted fetchable
// If the trusted fetchable has not been used to verify the primary fetchable yet, this queries the trusted fetchable and calls verify
async fetchAndVerifyIfNeeded(): Promise<T | null> {
return await new Promise<T | null>(async (resolve, reject) => {
return await new Promise<T | null>(async (resolve: (result: T | null) => void, reject: (error: Error) => void) => {
let resolved = false;
try {
let primaryPromise = this.primaryFunc();
@ -128,18 +182,21 @@ export class AutoVerifier<T> {
return;
}
if (!resolved) {
resolve(trustedResult);
resolved = true;
}
// Make sure to verify BEFORE potentially resolving
// This way the conflicts can be resolved before the result is returned
await this.verifyFunc(primaryResult, trustedResult);
if (this.trustedPromise === origTrustedPromise) {
this.trustedStatus = 'verified';
} else {
LOG.warn('RARE ALERT: we got unverified during verification!');
// We don't have to re-resolve since we already would have resolved with the correct trusted result
}
if (!resolved && trustedResult) {
resolve(trustedResult);
resolved = true;
}
} else {
// Some code is already dealing with (or dealt with) verifying the trusted result
// Await the same trusted promise and return its result if we didn't get a result

View File

@ -1,3 +0,0 @@
/**
* A data source is able to handle events
*/

View File

@ -0,0 +1,38 @@
import { GuildMetadata, Member, Channel, Message, Resource, Token } from "./data-types";
import { Fetchable, GuaranteedFetchable } from "./guild-types";
export default class EnsuredFetchable implements GuaranteedFetchable {
constructor(
private fetchable: Fetchable
) {}
private static guarantee<T>(value: T | null): T {
if (!value) throw new Error('got null value');
return value;
}
async fetchMetadata(): Promise<GuildMetadata> {
return EnsuredFetchable.guarantee(await this.fetchable.fetchMetadata());
}
async fetchMembers(): Promise<Member[]> {
return EnsuredFetchable.guarantee(await this.fetchable.fetchMembers());
}
async fetchChannels(): Promise<Channel[]> {
return EnsuredFetchable.guarantee(await this.fetchable.fetchChannels());
}
async fetchMessagesRecent(channelId: string, number: number): Promise<Message[]> {
return EnsuredFetchable.guarantee(await this.fetchable.fetchMessagesRecent(channelId, number));
}
async fetchMessagesBefore(channelId: string, messageId: string, number: number): Promise<Message[]> {
return EnsuredFetchable.guarantee(await this.fetchable.fetchMessagesBefore(channelId, messageId, number));
}
async fetchMessagesAfter(channelId: string, messageId: string, number: number): Promise<Message[]> {
return EnsuredFetchable.guarantee(await this.fetchable.fetchMessagesAfter(channelId, messageId, number));
}
async fetchResource(resourceId: string): Promise<Resource> {
return EnsuredFetchable.guarantee(await this.fetchable.fetchResource(resourceId));
}
async fetchTokens(): Promise<Token[]> {
return EnsuredFetchable.guarantee(await this.fetchable.fetchTokens());
}
}

View File

@ -0,0 +1,176 @@
import { Changes, Channel, GuildMetadata, Member, Message, Resource, Token } from './data-types';
import { AsyncFetchable, Fetchable, Lackable, Conflictable } from './guild-types';
import { AutoVerifier, AutoVerifierChangesType } from './auto-verifier';
import { AutoVerifierWithArg, PartialMessageListQuery, IDQuery } from './auto-verifier-with-args';
import { EventEmitter } from 'tsee';
export default class GuildPairVerifierGuild extends EventEmitter<Conflictable> implements AsyncFetchable {
private readonly fetchMetadataVerifier: AutoVerifier<GuildMetadata>;
private readonly fetchMembersVerifier: AutoVerifier<Member[]>;
private readonly fetchChannelsVerifier: AutoVerifier<Channel[]>;
private readonly fetchTokensVerifier: AutoVerifier<Token[]>;
private readonly fetchResourceVerifier: AutoVerifierWithArg<Resource, IDQuery>
private readonly fetchMessagesRecentVerifier: AutoVerifierWithArg<Message[], PartialMessageListQuery>;
private readonly fetchMessagesBeforeVerifier: AutoVerifierWithArg<Message[], PartialMessageListQuery>;
private readonly fetchMessagesAfterVerifier: AutoVerifierWithArg<Message[], PartialMessageListQuery>;
constructor(
primary: Fetchable & Lackable,
trusted: Fetchable
) {
super();
this.fetchMetadataVerifier = AutoVerifier.createStandardSingleAutoVerifier<GuildMetadata>(
async () => await primary.fetchMetadata(),
async () => await trusted.fetchMetadata(),
async (changesType: AutoVerifierChangesType, primaryMetadata: GuildMetadata | null, trustedMetadata: GuildMetadata | null) => {
if (changesType === AutoVerifierChangesType.TRUSTED_ONLY) {
await primary.handleMetadataChanged(trustedMetadata as GuildMetadata);
} else if (changesType === AutoVerifierChangesType.CONFLICT) {
await primary.handleMetadataChanged(trustedMetadata as GuildMetadata);
this.emit('conflict-metadata', primaryMetadata as GuildMetadata, trustedMetadata as GuildMetadata);
}
}
)
this.fetchMembersVerifier = AutoVerifier.createStandardListAutoVerifier<Member>(
async () => await primary.fetchMembers(),
async () => await trusted.fetchMembers(),
async (changesType: AutoVerifierChangesType, changes: Changes<Member>) => {
if (changes.added.length > 0) await primary.handleMembersAdded(changes.added);
if (changes.updated.length > 0) await primary.handleMembersChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) await primary.handleMembersDeleted(changes.deleted);
if (changesType === AutoVerifierChangesType.CONFLICT) {
this.emit('conflict-members', changes);
}
}
);
this.fetchChannelsVerifier = AutoVerifier.createStandardListAutoVerifier<Channel>(
async () => await primary.fetchChannels(),
async () => await trusted.fetchChannels(),
async (changesType: AutoVerifierChangesType, changes: Changes<Channel>) => {
if (changes.added.length > 0) await primary.handleChannelsAdded(changes.added);
if (changes.updated.length > 0) await primary.handleChannelsChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) await primary.handleChannelsDeleted(changes.deleted);
if (changesType === AutoVerifierChangesType.CONFLICT) {
this.emit('conflict-channels', changes);
}
}
);
this.fetchTokensVerifier = AutoVerifier.createStandardListAutoVerifier<Token>(
async () => await primary.fetchTokens(),
async () => await trusted.fetchTokens(),
async (changesType: AutoVerifierChangesType, changes: Changes<Token>) => {
if (changes.added.length > 0) await primary.handleTokensAdded(changes.added);
if (changes.updated.length > 0) await primary.handleTokensChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) await primary.handleTokensDeleted(changes.deleted);
if (changesType === AutoVerifierChangesType.CONFLICT) {
this.emit('conflict-tokens', changes);
}
}
);
this.fetchResourceVerifier = AutoVerifierWithArg.createStandardIDQueriedSingleAutoVerifier<Resource>(
async (query: IDQuery) => await primary.fetchResource(query.id),
async (query: IDQuery) => await trusted.fetchResource(query.id),
async (query: IDQuery, changesType: AutoVerifierChangesType, primaryResource: Resource | null, trustedResource: Resource | null) => {
if (changesType === AutoVerifierChangesType.PRIMARY_ONLY) {
await primary.handleResourceDeleted(trustedResource as Resource);
} else if (changesType === AutoVerifierChangesType.TRUSTED_ONLY) {
await primary.handleResourceAdded(trustedResource as Resource);
} else if (changesType === AutoVerifierChangesType.CONFLICT) {
await primary.handleResourceChanged(trustedResource as Resource);
this.emit('conflict-resource', primaryResource as Resource, trustedResource as Resource);
}
}
);
this.fetchMessagesRecentVerifier = AutoVerifierWithArg.createStandardPartialMessageListAutoVerifier(
async (query: PartialMessageListQuery) => await primary.fetchMessagesRecent(query.channelId, query.number),
async (query: PartialMessageListQuery) => await trusted.fetchMessagesRecent(query.channelId, query.number),
async (query: PartialMessageListQuery, changesType: AutoVerifierChangesType, changes: Changes<Message>) => {
if (changes.added.length > 0) await primary.handleMessagesAdded(changes.added);
if (changes.updated.length > 0) await primary.handleMessagesChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) await primary.handleMessagesDeleted(changes.deleted);
if (changesType === AutoVerifierChangesType.CONFLICT) {
this.emit('conflict-messages', changes);
}
}
);
this.fetchMessagesBeforeVerifier = AutoVerifierWithArg.createStandardPartialMessageListAutoVerifier(
async (query: PartialMessageListQuery) => await primary.fetchMessagesBefore(query.channelId, query.messageId as string, query.number),
async (query: PartialMessageListQuery) => await trusted.fetchMessagesBefore(query.channelId, query.messageId as string, query.number),
async (query: PartialMessageListQuery, changesType: AutoVerifierChangesType, changes: Changes<Message>) => {
if (changes.added.length > 0) await primary.handleMessagesAdded(changes.added);
if (changes.updated.length > 0) await primary.handleMessagesChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) await primary.handleMessagesDeleted(changes.deleted);
if (changesType === AutoVerifierChangesType.CONFLICT) {
this.emit('conflict-messages', changes);
}
}
);
this.fetchMessagesAfterVerifier = AutoVerifierWithArg.createStandardPartialMessageListAutoVerifier(
async (query: PartialMessageListQuery) => await primary.fetchMessagesAfter(query.channelId, query.messageId as string, query.number),
async (query: PartialMessageListQuery) => await trusted.fetchMessagesAfter(query.channelId, query.messageId as string, query.number),
async (query: PartialMessageListQuery, changesType: AutoVerifierChangesType, changes: Changes<Message>) => {
if (changes.added.length > 0) await primary.handleMessagesAdded(changes.added);
if (changes.updated.length > 0) await primary.handleMessagesChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) await primary.handleMessagesDeleted(changes.deleted);
if (changesType === AutoVerifierChangesType.CONFLICT) {
this.emit('conflict-messages', changes);
}
}
);
}
unverify() {
this.fetchMetadataVerifier.unverify();
this.fetchMembersVerifier.unverify();
this.fetchChannelsVerifier.unverify();
this.fetchTokensVerifier.unverify();
this.fetchResourceVerifier.unverifyAll();
this.fetchMessagesRecentVerifier.unverifyAll();
this.fetchMessagesBeforeVerifier.unverifyAll();
this.fetchMessagesAfterVerifier.unverifyAll();
}
async fetchMetadata(): Promise<GuildMetadata | null> {
return await this.fetchMetadataVerifier.fetchAndVerifyIfNeeded();
}
async fetchMembers(): Promise<Member[] | null> {
return await this.fetchMembersVerifier.fetchAndVerifyIfNeeded();
}
async fetchChannels(): Promise<Channel[] | null> {
return await this.fetchChannelsVerifier.fetchAndVerifyIfNeeded();
}
async fetchMessagesRecent(channelId: string, number: number): Promise<Message[] | null> {
return await this.fetchMessagesRecentVerifier.fetchAndVerifyIfNeded({ channelId, messageId: null, number });
}
async fetchMessagesBefore(channelId: string, messageId: string, number: number): Promise<Message[] | null> {
return await this.fetchMessagesBeforeVerifier.fetchAndVerifyIfNeded({ channelId, messageId, number });
}
async fetchMessagesAfter(channelId: string, messageId: string, number: number): Promise<Message[] | null> {
return await this.fetchMessagesAfterVerifier.fetchAndVerifyIfNeded({ channelId, messageId, number });
}
async fetchResource(resourceId: string): Promise<Resource | null> {
return await this.fetchResourceVerifier.fetchAndVerifyIfNeded({ id: resourceId });
}
async fetchTokens(): Promise<Token[] | null> {
return await this.fetchTokensVerifier.fetchAndVerifyIfNeeded();
}
}

View File

@ -13,17 +13,17 @@ import MessageRAMCache from "./message-ram-cache";
import PersonalDB from "./personal-db";
import ResourceRAMCache from "./resource-ram-cache";
import SocketVerifier from './socket-verifier';
import { Connectable, AsyncGuaranteedFetchable } from './guild-types';
import { AutoVerifier } from './auto-verifier';
import { AutoVerifierWithArg, PartialListQuery } from './auto-verifier-with-args';
import GuildPairVerifierGuild from './guild-pair-verifier';
import { Connectable, AsyncGuaranteedFetchable, Conflictable, AsyncRequestable } from './guild-types';
import PairVerifierFetchable from './fetchable-pair-verifier';
import EnsuredFetchable from './fetchable-ensured';
import { EventEmitter } from 'tsee';
export default class GuildController extends Connectable implements AsyncGuaranteedFetchable {
export default class CombinedGuild extends EventEmitter<Connectable & Conflictable> implements AsyncGuaranteedFetchable, AsyncRequestable {
private readonly ramGuild: RAMGuild;
private readonly personalDBGuild: PersonalDBGuild;
private readonly socketGuild: SocketGuild;
private readonly pairVerifiers: GuildPairVerifierGuild[];
private readonly pairVerifiers: PairVerifierFetchable[];
private readonly fetchable: AsyncGuaranteedFetchable;
constructor(
@ -43,6 +43,7 @@ export default class GuildController extends Connectable implements AsyncGuarant
// TODO: Only unverify the personaldb->socket connection on d/c?
// Connect/Disconnect
this.socketGuild.on('connect', () => {
LOG.info(`g#${this.id} connected`);
this.emit('connect');
@ -53,83 +54,98 @@ export default class GuildController extends Connectable implements AsyncGuarant
this.emit('disconnect');
});
// Metadata
this.socketGuild.on('update-metadata', (guildMeta: GuildMetadata) => {
LOG.info(`g#${this.id} updated metadata: ${guildMeta}`);
this.emit('update-metadata', guildMeta);
});
let listenForList = <T>(
event: 'new-messages' | 'update-messages' | 'new-members' | 'update-members' | 'new-channels' | 'update-channels',
logFunc: (arg: T) => void,
guildUpdater: (args: T[]) => void
) => {
this.socketGuild.on(event, (args: any[]) => {
for (let arg of args) {
logFunc(arg);
}
guildUpdater(args);
this.emit(event, args);
});
}
// Members
this.socketGuild.on('new-members', async (members: Member[]) => {
for (let member of members) {
LOG.info(`g#${this.id} ${member}`);
}
this.ramGuild.handleMembersAdded(members);
await this.personalDBGuild.handleMembersAdded(members);
this.emit('new-members', members);
});
this.socketGuild.on('update-members', async (members: Member[]) => {
for (let member of members) {
LOG.info(`g#${this.id} updated ${member}`);
}
this.ramGuild.handleMembersChanged(members);
await this.personalDBGuild.handleMembersChanged(members);
this.emit('update-members', members);
});
listenForList<Member>('new-members',
(member: Member) => LOG.info(`g#${this.id} ${member}`),
(members: Member[]) => {
this.ramGuild.handleMembersAdded(members);
this.personalDBGuild.handleMembersAdded(members);
// Channels
this.socketGuild.on('new-channels', async (channels: Channel[]) => {
for (let channel of channels) {
LOG.info(`g#${this.id} ${channel}`);
}
);
listenForList<Member>('update-members',
(member: Member) => LOG.info(`g#${this.id} updated ${member}`),
(members: Member[]) => {
this.ramGuild.handleMembersChanged(members);
this.personalDBGuild.handleMembersChanged(members);
this.ramGuild.handleChannelsAdded(channels);
await this.personalDBGuild.handleChannelsAdded(channels);
this.emit('new-channels', channels);
});
this.socketGuild.on('update-channels', async (channels: Channel[]) => {
for (let channel of channels) {
LOG.info(`g#${this.id} updated ${channel}`);
}
);
this.ramGuild.handleChannelsChanged(channels);
await this.personalDBGuild.handleChannelsChanged(channels);
this.emit('update-channels', channels);
});
listenForList<Channel>('new-members',
(channel: Channel) => LOG.info(`g#${this.id} ${channel}`),
(channels: Channel[]) => {
this.ramGuild.handleChannelsAdded(channels);
this.personalDBGuild.handleChannelsAdded(channels);
// Messages
this.socketGuild.on('new-messages', async (messages: Message[]) => {
for (let message of messages) {
LOG.info(`g#${this.id} ${message}`);
}
);
listenForList<Channel>('update-members',
(channel: Channel) => LOG.info(`g#${this.id} updated ${channel}`),
(channels: Channel[]) => {
this.ramGuild.handleChannelsChanged(channels);
this.personalDBGuild.handleChannelsChanged(channels);
this.ramGuild.handleMessagesAdded(messages);
await this.personalDBGuild.handleMessagesAdded(messages);
this.emit('new-messages', messages);
});
this.socketGuild.on('update-messages', async (messages: Message[]) => {
for (let message of messages) {
LOG.info(`g#${this.id} updated ${message}`);
}
);
this.ramGuild.handleMessagesChanged(messages);
await this.personalDBGuild.handleMessagesChanged(messages);
this.emit('update-messages', messages);
});
listenForList<Message>('new-messages',
(message: Message) => LOG.info(`g#${this.id} ${message}`),
(messages: Message[]) => {
this.ramGuild.handleMessagesAdded(messages);
this.personalDBGuild.handleMessagesAdded(messages);
}
);
listenForList<Message>('update-messages',
(message: Message) => LOG.info(`g#${this.id} updated ${message}`),
(messages: Message[]) => {
this.ramGuild.handleMessagesChanged(messages);
this.personalDBGuild.handleMessagesChanged(messages);
}
);
let personalDBSocketPairVerifier = new PairVerifierFetchable(this.personalDBGuild, this.socketGuild);
let ramPersonalDBSocketPairVerifier = new PairVerifierFetchable(this.ramGuild, personalDBSocketPairVerifier);
let personalDBSocketPairVerifier = new GuildPairVerifierGuild(
this.personalDBGuild,
this.socketGuild
);
let ramPersonalDBSocketPairVerifier = new GuildPairVerifierGuild(
this.ramGuild,
personalDBSocketPairVerifier
);
// Forward the conflict events from the last verifier in the chain
ramPersonalDBSocketPairVerifier.on('conflict-metadata', (oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => {
LOG.info(`g#${this.id} metadata conflict`, { oldGuildMeta, newGuildMeta });
this.emit('conflict-metadata', oldGuildMeta, newGuildMeta);
});
ramPersonalDBSocketPairVerifier.on('conflict-members', (changes: Changes<Member>) => {
LOG.info(`g#${this.id} members conflict`, { changes });
this.emit('conflict-members', changes);
});
ramPersonalDBSocketPairVerifier.on('conflict-channels', (changes: Changes<Channel>) => {
LOG.info(`g#${this.id} channels conflict`, { changes });
this.emit('conflict-channels', changes);
});
ramPersonalDBSocketPairVerifier.on('conflict-messages', (changes: Changes<Message>) => {
LOG.info(`g#${this.id} messages conflict`, { changes });
this.emit('conflict-messages', changes);
});
ramPersonalDBSocketPairVerifier.on('conflict-tokens', (changes: Changes<Token>) => {
LOG.info(`g#${this.id} tokens conflict`, { changes });
this.emit('conflict-tokens', changes);
});
ramPersonalDBSocketPairVerifier.on('conflict-resource', (oldResource: Resource, newResource: Resource) => {
LOG.warn(`g#${this.id} resource conflict`, { oldResource, newResource });
this.emit('conflict-resource', oldResource, newResource);
});
this.pairVerifiers = [ personalDBSocketPairVerifier, ramPersonalDBSocketPairVerifier ];
this.fetchable = ramPersonalDBSocketPairVerifier;
this.fetchable = new EnsuredFetchable(ramPersonalDBSocketPairVerifier);
}
static async create(
@ -145,7 +161,7 @@ export default class GuildController extends Connectable implements AsyncGuarant
});
let socketVerifier = new SocketVerifier(socket, socketConfig.publicKey, socketConfig.privateKey);
let memberId = await socketVerifier.verify();
return new GuildController(
return new CombinedGuild(
personalGuildId,
memberId,
socket,
@ -166,14 +182,17 @@ export default class GuildController extends Connectable implements AsyncGuarant
if (this.ramGuild.getMembers().size === 0) {
await this.fetchMembers();
}
if (this.ramGuild.getMembers().size === 0) throw new Error('RAM Members was not updated through fetchMembers');
}
async ensureRAMChannels() {
if (this.ramGuild.getChannels().size === 0) {
await this.fetchChannels();
}
if (this.ramGuild.getChannels().size === 0) throw new Error('RAM Channels was not updated through fetchChannels');
}
// Fetched through the triple-cache system (RAM -> Disk -> Server)
async fetchMetadata(): Promise<GuildMetadata> {
return await this.fetchable.fetchMetadata();
}
@ -198,4 +217,36 @@ export default class GuildController extends Connectable implements AsyncGuarant
async fetchTokens(): Promise<Token[]> {
return await this.fetchable.fetchTokens();
}
// Simply forwarded to the socket guild
async requestSendMessage(channelId: string, text: string): Promise<void> {
await this.socketGuild.requestSendMessage(channelId, text);
}
async requestSendMessageWithResource(channelId: string, text: string | null, resource: Buffer, resourceName: string): Promise<void> {
await this.socketGuild.requestSendMessageWithResource(channelId, text, resource, resourceName);
}
async requestSetStatus(status: string): Promise<void> {
await this.socketGuild.requestSetStatus(status);
}
async requestSetDisplayName(displayName: string): Promise<void> {
await this.socketGuild.requestSetDisplayName(displayName);
}
async requestSetAvatar(avatar: Buffer): Promise<void> {
await this.socketGuild.requestSetAvatar(avatar);
}
async requestSetServerName(serverName: string): Promise<void> {
await this.socketGuild.requestSetServerName(serverName);
}
async requestSetServerIcon(serverIcon: Buffer): Promise<void> {
await this.socketGuild.requestSetServerIcon(serverIcon);
}
async requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise<void> {
await this.socketGuild.requestDoUpdateChannel(channelId, name, flavorText);
}
async requestDoCreateChannel(name: string, flavorText: string | null): Promise<void> {
await this.socketGuild.requestDoCreateChannel(name, flavorText);
}
async requestDoRevokeToken(token: string): Promise<void> {
await this.socketGuild.requestDoRevokeToken(token);
}
}

View File

@ -1,172 +0,0 @@
import * as electronRemote from '@electron/remote';
const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import * as socketio from 'socket.io-client';
import PersonalDBGuild from './guild-personal-db';
import RAMGuild from './guild-ram';
import SocketGuild from './guild-socket';
import { Changes, Channel, GuildMetadata, Member, Message, Resource, ServerMetaData, SocketConfig, Token } from './data-types';
import MessageRAMCache from "./message-ram-cache";
import PersonalDB from "./personal-db";
import ResourceRAMCache from "./resource-ram-cache";
import SocketVerifier from './socket-verifier';
import { Connectable, AsyncGuaranteedFetchable, Fetchable, Lackable, GuaranteedFetchable } from './guild-types';
import { AutoVerifier } from './auto-verifier';
import { AutoVerifierWithArg, PartialListQuery } from './auto-verifier-with-args';
export default class GuildPairVerifierGuild implements AsyncGuaranteedFetchable {
// Verifiers for the personaldb to the socket
private readonly fetchMetadataVerifier: AutoVerifier<GuildMetadata>;
private readonly fetchMembersVerifier: AutoVerifier<Member[]>;
private readonly fetchChannelsVerifier: AutoVerifier<Channel[]>;
private readonly fetchTokensVerifier: AutoVerifier<Token[]>;
private readonly fetchResourceVerifier: AutoVerifierWithArg<Resource, { resourceId: string }>
private readonly fetchMessagesRecentVerifier: AutoVerifierWithArg<Message[], PartialListQuery>;
private readonly fetchMessagesBeforeVerifier: AutoVerifierWithArg<Message[], PartialListQuery>;
private readonly fetchMessagesAfterVerifier: AutoVerifierWithArg<Message[], PartialListQuery>;
// TODO: Figure out what to do to verify the resources
// TODO: Figure out what to do to verify the messages
constructor(
readonly primary: Fetchable & Lackable,
readonly trusted: GuaranteedFetchable
) {
this.fetchMetadataVerifier = new AutoVerifier<GuildMetadata>(
async () => await this.primary.fetchMetadata(),
async () => await this.trusted.fetchMetadata(),
async (personalDBMetadata: GuildMetadata | null, socketMetadata: GuildMetadata | null) => {
if (personalDBMetadata === null && socketMetadata === null) {
return;
} else if (personalDBMetadata === null) {
this.primary.handleMetadataChanged(socketMetadata as GuildMetadata);
} else if (socketMetadata === null) {
return;
} else {
this.primary.handleMetadataChanged(socketMetadata as GuildMetadata);
}
}
)
this.fetchMembersVerifier = AutoVerifier.createStandardListAutoVerifier<Member>(
async () => await this.primary.fetchMembers(),
async () => await this.trusted.fetchMembers(),
async (changes: Changes<Member>) => {
if (changes.added.length > 0) this.primary.handleMembersAdded(changes.added);
if (changes.updated.length > 0) this.primary.handleMembersChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) this.primary.handleMembersDeleted(changes.deleted);
}
);
this.fetchChannelsVerifier = AutoVerifier.createStandardListAutoVerifier<Channel>(
async () => await this.primary.fetchChannels(),
async () => await this.trusted.fetchChannels(),
async (changes: Changes<Channel>) => {
if (changes.added.length > 0) this.primary.handleChannelsAdded(changes.added);
if (changes.updated.length > 0) this.primary.handleChannelsChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) this.primary.handleChannelsDeleted(changes.deleted);
}
);
this.fetchTokensVerifier = AutoVerifier.createStandardListAutoVerifier<Token>(
async () => await this.primary.fetchTokens(),
async () => await this.trusted.fetchTokens(),
async (changes: Changes<Token>) => {
if (changes.added.length > 0) this.primary.handleTokensAdded(changes.added);
if (changes.updated.length > 0) this.primary.handleTokensChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) this.primary.handleTokensDeleted(changes.deleted);
}
);
this.fetchResourceVerifier = new AutoVerifierWithArg<Resource, { resourceId: string }>(
(query: { resourceId: string }) => query.resourceId,
async (query: { resourceId: string }) => await this.primary.fetchResource(query.resourceId),
async (query: { resourceId: string }) => await this.trusted.fetchResource(query.resourceId),
async (query: { resourceId: string }, personalDBResource: Resource | null, socketResource: Resource | null) => {
if (personalDBResource === null && socketResource === null) {
return;
} else if (personalDBResource === null) {
this.primary.handleResourceAdded(socketResource as Resource);
} else if (socketResource === null) {
this.primary.handleResourceDeleted(personalDBResource as Resource);
} else {
this.primary.handleResourceChanged(socketResource as Resource);
}
}
);
this.fetchMessagesRecentVerifier = AutoVerifierWithArg.createPartialListAutoVerifier(
async (query: PartialListQuery) => await this.primary.fetchMessagesRecent(query.channelId, query.number),
async (query: PartialListQuery) => await this.trusted.fetchMessagesRecent(query.channelId, query.number),
async (query: PartialListQuery, changes: Changes<Message>) => {
if (changes.added.length > 0) this.primary.handleMessagesAdded(changes.added);
if (changes.updated.length > 0) this.primary.handleMessagesChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) this.primary.handleMessagesDeleted(changes.deleted);
}
);
this.fetchMessagesBeforeVerifier = AutoVerifierWithArg.createPartialListAutoVerifier(
async (query: PartialListQuery) => await this.primary.fetchMessagesBefore(query.channelId, query.messageId as string, query.number),
async (query: PartialListQuery) => await this.trusted.fetchMessagesBefore(query.channelId, query.messageId as string, query.number),
async (query: PartialListQuery, changes: Changes<Message>) => {
if (changes.added.length > 0) this.primary.handleMessagesAdded(changes.added);
if (changes.updated.length > 0) this.primary.handleMessagesChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) this.primary.handleMessagesDeleted(changes.deleted);
}
);
this.fetchMessagesAfterVerifier = AutoVerifierWithArg.createPartialListAutoVerifier(
async (query: PartialListQuery) => await this.primary.fetchMessagesAfter(query.channelId, query.messageId as string, query.number),
async (query: PartialListQuery) => await this.trusted.fetchMessagesAfter(query.channelId, query.messageId as string, query.number),
async (query: PartialListQuery, changes: Changes<Message>) => {
if (changes.added.length > 0) this.primary.handleMessagesAdded(changes.added);
if (changes.updated.length > 0) this.primary.handleMessagesChanged(changes.updated.map(change => change.newDataPoint));
if (changes.deleted.length > 0) this.primary.handleMessagesDeleted(changes.deleted);
}
);
}
unverify() {
this.fetchMetadataVerifier.unverify();
this.fetchMembersVerifier.unverify();
this.fetchChannelsVerifier.unverify();
this.fetchTokensVerifier.unverify();
this.fetchResourceVerifier.unverifyAll();
this.fetchMessagesRecentVerifier.unverifyAll();
this.fetchMessagesBeforeVerifier.unverifyAll();
this.fetchMessagesAfterVerifier.unverifyAll();
}
async fetchMetadata(): Promise<GuildMetadata> {
return await this.fetchMetadataVerifier.fetchAndVerifyIfNeeded() as GuildMetadata; // can use 'as' since socket is a Guaranteed fetchable
}
async fetchMembers(): Promise<Member[]> {
return await this.fetchMembersVerifier.fetchAndVerifyIfNeeded() as Member[];
}
async fetchChannels(): Promise<Channel[]> {
return await this.fetchChannelsVerifier.fetchAndVerifyIfNeeded() as Channel[];
}
async fetchMessagesRecent(channelId: string, number: number): Promise<Message[]> {
return await this.fetchMessagesRecentVerifier.fetchAndVerifyIfNeded({ channelId, messageId: null, number }) as Message[];
}
async fetchMessagesBefore(channelId: string, messageId: string, number: number): Promise<Message[]> {
return await this.fetchMessagesBeforeVerifier.fetchAndVerifyIfNeded({ channelId, messageId, number }) as Message[];
}
async fetchMessagesAfter(channelId: string, messageId: string, number: number): Promise<Message[]> {
return await this.fetchMessagesAfterVerifier.fetchAndVerifyIfNeded({ channelId, messageId, number }) as Message[];
}
async fetchResource(resourceId: string): Promise<Resource> {
return await this.fetchResourceVerifier.fetchAndVerifyIfNeded({ resourceId }) as Resource;
}
async fetchTokens(): Promise<Token[]> {
return await this.fetchTokensVerifier.fetchAndVerifyIfNeeded() as Token[];
}
}

View File

@ -1,16 +1,14 @@
import { AsyncFetchable, AsyncLackable, AsyncLackableConnectable, Connectable } from "./guild-types";
import { Channel, GuildMetadata, Member, Message, Resource, ServerMetaData, Token } from "./data-types";
import { AsyncFetchable, AsyncLackable } from "./guild-types";
import { Channel, GuildMetadata, Member, Message, Resource, Token } from "./data-types";
import PersonalDB from "./personal-db";
export default class PersonalDBGuild extends AsyncLackableConnectable implements AsyncFetchable {
export default class PersonalDBGuild implements AsyncFetchable, AsyncLackable {
constructor(
private readonly db: PersonalDB,
private readonly guildId: number,
private readonly memberId: string, // would be used if we want to cache-update the status/display name of members
) {
super();
}
) {}
// Fetched Methods
@ -54,68 +52,53 @@ export default class PersonalDBGuild extends AsyncLackableConnectable implements
(async () => { await this.db.updateGuildIcon(this.guildId, changedMetaData.iconResourceId); })();
}
async handleMembersAdded(hadData: boolean, addedMembers: Member[]): Promise<void> {
async handleMembersAdded(addedMembers: Member[]): Promise<void> {
await this.db.addMembers(this.guildId, addedMembers);
await super.handleMembersAdded(addedMembers);
}
async handleMembersChanged(changedMembers: Member[]): Promise<void> {
await this.db.updateMembers(this.guildId, changedMembers);
await super.handleMembersChanged(changedMembers);
}
async handleMembersDeleted(deletedMembers: Member[]): Promise<void> {
await this.db.deleteMembers(this.guildId, deletedMembers);
await super.handleMembersDeleted(deletedMembers);
}
async handleChannelsAdded(hadData: boolean, addedChannels: Channel[]): Promise<void> {
async handleChannelsAdded(addedChannels: Channel[]): Promise<void> {
await this.db.addChannels(this.guildId, addedChannels);
await super.handleChannelsAdded(addedChannels);
}
async handleChannelsChanged(changedChannels: Channel[]): Promise<void> {
await this.db.updateChannels(this.guildId, changedChannels);
await super.handleChannelsChanged(changedChannels);
}
async handleChannelsDeleted(deletedChannels: Channel[]): Promise<void> {
await this.db.deleteChannels(this.guildId, deletedChannels);
await super.handleChannelsDeleted(deletedChannels);
}
async handleMessagesAdded(hadData: boolean, addedMessages: Message[]): Promise<void> {
async handleMessagesAdded(addedMessages: Message[]): Promise<void> {
await this.db.addMessages(this.guildId, addedMessages);
await super.handleMessagesAdded(addedMessages);
}
async handleMessagesChanged(changedMessages: Message[]): Promise<void> {
await this.db.updateMessages(this.guildId, changedMessages);
await super.handleMembersAdded(addedMembers);
}
async handleMessagesDeleted(deletedMessages: Message[]): Promise<void> {
await this.db.deleteMessages(this.guildId, deletedMessages);
await super.handleMessagesDeleted(deletedMessages);
}
async handleResourceAdded(hadData: boolean, addedResource: Resource): Promise<void> {
async handleResourceAdded(addedResource: Resource): Promise<void> {
await this.db.addResources(this.guildId, [ addedResource ]);
await super.handleResourceAdded(addedResource);
}
async handleResourceChanged(changedResource: Resource): Promise<void> {
await this.db.updateResources(this.guildId, [ changedResource ]);
await super.handleResourceChanged(changedResource);
}
async handleResourceDeleted(deletedResource: Resource): Promise<void> {
await this.db.deleteResources(this.guildId, [ deletedResource ]);
await super.handleResourceDeleted(deletedResource);
}
async handleTokensAdded(hadData: boolean, addedTokens: Token[]): Promise<void> {
await super.handleTokensAdded(addedTokens);
async handleTokensAdded(addedTokens: Token[]): Promise<void> {
return; // cache currently does not handle tokens
}
async handleTokensChanged(changedTokens: Token[]): Promise<void> {
await super.handleTokensChanged(changedTokens);
return; // cache currently does not handle tokens
}
async handleTokensDeleted(deletedTokens: Token[]): Promise<void> {
await super.handleTokensDeleted(deletedTokens);
return; // cache currently does not handle tokens
}
}

View File

@ -50,7 +50,7 @@ export default class RAMGuild implements SyncFetchable, SyncLackable {
this.metadata = changedMetaData;
}
handleMembersAdded(hadData: boolean, addedMembers: Member[]): void {
handleMembersAdded(addedMembers: Member[]): void {
for (let member of addedMembers) {
this.members.set(member.id, member);
}
@ -66,7 +66,7 @@ export default class RAMGuild implements SyncFetchable, SyncLackable {
}
}
handleChannelsAdded(hadData: boolean, addedChannels: Channel[]): void {
handleChannelsAdded(addedChannels: Channel[]): void {
for (let channel of addedChannels) {
this.channels.set(channel.id, channel);
}
@ -82,7 +82,7 @@ export default class RAMGuild implements SyncFetchable, SyncLackable {
}
}
handleMessagesAdded(hadData: boolean, addedMessages: Message[]): void {
handleMessagesAdded(addedMessages: Message[]): void {
let byChannel = new Map<string, Message[]>();
for (let message of addedMessages) {
if (!byChannel.has(message.channel.id)) {
@ -122,7 +122,7 @@ export default class RAMGuild implements SyncFetchable, SyncLackable {
}
}
handleResourceAdded(hadData: boolean, addedResource: Resource): void {
handleResourceAdded(addedResource: Resource): void {
this.resources.putResource(this.guildId, addedResource);
}
handleResourceChanged(changedResource: Resource): void {
@ -135,7 +135,7 @@ export default class RAMGuild implements SyncFetchable, SyncLackable {
return;
}
handleTokensAdded(hadData: boolean, addedTokens: Token[]): void {
handleTokensAdded(addedTokens: Token[]): void {
return; // TODO: token ram cache
}
handleTokensChanged(changedTokens: Token[]): void {

View File

@ -10,9 +10,10 @@ import { Connectable, AsyncRequestable, AsyncGuaranteedFetchable } from './guild
import DedupAwaiter from './dedup-awaiter';
import Util from './util';
import SocketVerifier from './socket-verifier';
import { EventEmitter } from 'tsee';
// Note: you should not be calling the eventemitter functions on outside classes
export default class SocketGuild extends Connectable implements AsyncGuaranteedFetchable, AsyncRequestable {
export default class SocketGuild extends EventEmitter<Connectable> implements AsyncGuaranteedFetchable, AsyncRequestable {
private queryDedups = new Map<string, DedupAwaiter<any>>();
constructor(
@ -20,7 +21,6 @@ export default class SocketGuild extends Connectable implements AsyncGuaranteedF
private verifier: SocketVerifier
) {
super();
// TODO: These requests should be 'bindable' through a connectable structure
this.socket.on('connect', async () => {
this.emit('connect');
});
@ -28,18 +28,6 @@ export default class SocketGuild extends Connectable implements AsyncGuaranteedF
this.emit('disconnect');
});
// TODO: Make sure the UI updates when we get an added/updated member
// TODO: The server does not emit update-message
this.socket.on('update-message', async (updatedDataMessage: any) => {
let updatedMessage = Message.fromDBData(updatedDataMessage);
this.emit('update-messages', [ updatedMessage ]);
});
this.socket.on('new-message', async (dataMessage: any) => {
let message = Message.fromDBData(dataMessage);
this.emit('new-messages', [ message ]);
});
this.socket.on('update-member', async (updatedDataMember: any) => {
let updatedMember = Member.fromDBData(updatedDataMember);
this.emit('update-members', [ updatedMember ]);
@ -63,6 +51,16 @@ export default class SocketGuild extends Connectable implements AsyncGuaranteedF
let guildMeta = GuildMetadata.fromDBData(dataMeta);
this.emit('update-metadata', guildMeta);
});
// TODO: The server does not emit update-message
this.socket.on('update-message', async (updatedDataMessage: any) => {
let updatedMessage = Message.fromDBData(updatedDataMessage);
this.emit('update-messages', [ updatedMessage ]);
});
this.socket.on('new-message', async (dataMessage: any) => {
let message = Message.fromDBData(dataMessage);
this.emit('new-messages', [ message ]);
});
}
// server helper functions

View File

@ -1,5 +1,5 @@
import { Channel, GuildMetadata, Member, Message, Resource, Token } from './data-types';
import { EventEmitter } from 'tsee';
import { Changes, Channel, GuildMetadata, Member, Message, Resource, Token } from './data-types';
import { DefaultEventMap, EventEmitter } from 'tsee';
// Fetchable
@ -60,23 +60,23 @@ export type Requestable = AsyncRequestable;
export interface AsyncLackable {
handleMetadataChanged(changedMetaData: GuildMetadata): Promise<void>;
handleMembersAdded(hadData: boolean, addedMembers: Member[]): Promise<void>;
handleMembersAdded(addedMembers: Member[]): Promise<void>;
handleMembersChanged(changedMembers: Member[]): Promise<void>;
handleMembersDeleted(deletedMembers: Member[]): Promise<void>;
handleChannelsAdded(hadData: boolean, addedChannels: Channel[]): Promise<void>;
handleChannelsAdded(addedChannels: Channel[]): Promise<void>;
handleChannelsChanged(changedChannels: Channel[]): Promise<void>;
handleChannelsDeleted(deletedChannels: Channel[]): Promise<void>;
handleMessagesAdded(hadData: boolean, addedMessages: Message[]): Promise<void>;
handleMessagesAdded(addedMessages: Message[]): Promise<void>;
handleMessagesChanged(changedMessages: Message[]): Promise<void>;
handleMessagesDeleted(deletedMessages: Message[]): Promise<void>;
handleResourceAdded(hadData: boolean, addedResource: Resource): Promise<void>;
handleResourceAdded(addedResource: Resource): Promise<void>;
handleResourceChanged(changedResource: Resource): Promise<void>;
handleResourceDeleted(deletedResource: Resource): Promise<void>;
handleTokensAdded(hadData: boolean, addedTokens: Token[]): Promise<void>;
handleTokensAdded(addedTokens: Token[]): Promise<void>;
handleTokensChanged(changedTokens: Token[]): Promise<void>;
handleTokensDeleted(deletedTokens: Token[]): Promise<void>;
}
@ -84,126 +84,55 @@ export interface AsyncLackable {
export interface SyncLackable {
handleMetadataChanged(changedMetaData: GuildMetadata): void;
handleMembersAdded(hadData: boolean, addedMembers: Member[]): void;
handleMembersAdded(addedMembers: Member[]): void;
handleMembersChanged(changedMembers: Member[]): void;
handleMembersDeleted(deletedMembers: Member[]): void;
handleChannelsAdded(hadData: boolean, addedChannels: Channel[]): void;
handleChannelsAdded(addedChannels: Channel[]): void;
handleChannelsChanged(changedChannels: Channel[]): void;
handleChannelsDeleted(deletedChannels: Channel[]): void;
handleMessagesAdded(hadData: boolean, addedMessages: Message[]): void;
handleMessagesAdded(addedMessages: Message[]): void;
handleMessagesChanged(changedMessages: Message[]): void;
handleMessagesDeleted(deletedMessages: Message[]): void;
handleResourceAdded(hadData: boolean, addedResource: Resource): void;
handleResourceAdded(addedResource: Resource): void;
handleResourceChanged(changedResource: Resource): void;
handleResourceDeleted(deletedResource: Resource): void;
handleTokensAdded(hadData: boolean, addedTokens: Token[]): void;
handleTokensAdded(addedTokens: Token[]): void;
handleTokensChanged(changedTokens: Token[]): void;
handleTokensDeleted(deletedTokens: Token[]): void;
}
export type Lackable = AsyncLackable | SyncLackable;
// Connectable
export class Connectable extends EventEmitter<{
'connect': () => void,
'disconnect': () => void,
'update-metadata': (guildMeta: GuildMetadata) => void
// A Connectable can emit server-like events
export type Connectable = {
'connect': () => void;
'disconnect': () => void;
'new-channels': (channels: Channel[]) => void,
'update-channels': (updatedChannels: Channel[]) => void,
'update-metadata': (guildMeta: GuildMetadata) => void;
'new-members': (members: Member[]) => void,
'update-members': (updatedMembers: Member[]) => void,
'new-channels': (channels: Channel[]) => void;
'update-channels': (updatedChannels: Channel[]) => void;
'new-messages': (messages: Message[]) => void,
'update-messages': (updatedMessages: Message[]) => void,
}> {
constructor() { super(); }
'new-members': (members: Member[]) => void;
'update-members': (updatedMembers: Member[]) => void;
'new-messages': (messages: Message[]) => void;
'update-messages': (updatedMessages: Message[]) => void;
}
export class AsyncLackableConnectable extends EventEmitter<{
'updated-metadata': (guildMeta: GuildMetadata) => void,
'added-channels': (channels: Channel[]) => void,
'updated-channels': (updatedChannels: Channel[]) => void,
'deleted-channels': (channels: Channel[]) => void,
'added-members': (members: Member[]) => void,
'updated-members': (updatedMembers: Member[]) => void,
'deleted-members': (members: Member[]) => void,
'added-messages': (messages: Message[]) => void,
'updated-messages': (updatedMessages: Message[]) => void,
'deleted-messages': (messages: Message[]) => void,
'added-resource': (resource: Resource) => void,
'updated-resource': (updatedResource: Resource) => void,
'deleted-resource': (resource: Resource) => void,
'added-tokens': (tokens: Token[]) => void,
'updated-tokens': (updatedTokens: Token[]) => void,
'deleted-tokens': (tokens: Token[]) => void,
}> implements AsyncLackable {
constructor() { super(); }
async handleMetadataChanged(changedMetaData: GuildMetadata): Promise<void> {
this.emit('updated-metadata', changedMetaData);
}
async handleMembersAdded(hadData: boolean, addedMembers: Member[]): Promise<void> {
if (hadData) this.emit('added-members', addedMembers);
}
async handleMembersChanged(changedMembers: Member[]): Promise<void> {
this.emit('updated-members', changedMembers);
}
async handleMembersDeleted(deletedMembers: Member[]): Promise<void> {
this.emit('deleted-members', deletedMembers);
}
async handleChannelsAdded(hadData: boolean, addedChannels: Channel[]): Promise<void> {
if (hadData) this.emit('added-channels', addedChannels);
}
async handleChannelsChanged(changedChannels: Channel[]): Promise<void> {
this.emit('updated-channels', changedChannels);
}
async handleChannelsDeleted(deletedChannels: Channel[]): Promise<void> {
this.emit('deleted-channels', deletedChannels);
}
// TODO: These are most likely pretty messy...
async handleMessagesAdded(hadData: boolean, addedMessages: Message[]): Promise<void> {
if (hadData) this.emit('added-messages', addedMessages);
}
async handleMessagesChanged(changedMessages: Message[]): Promise<void> {
this.emit('updated-messages', changedMessages);
}
async handleMessagesDeleted(deletedMessages: Message[]): Promise<void> {
this.emit('deleted-messages', deletedMessages);
}
async handleResourceAdded(hadData: boolean, addedResource: Resource): Promise<void> {
if (hadData) this.emit('added-resource', addedResource);
}
async handleResourceChanged(changedResource: Resource): Promise<void> {
this.emit('updated-resource', changedResource);
}
async handleResourceDeleted(deletedResource: Resource): Promise<void> {
this.emit('deleted-resource', deletedResource);
}
async handleTokensAdded(hadData: boolean, addedTokens: Token[]): Promise<void> {
if (hadData) this.emit('added-tokens', addedTokens);
}
async handleTokensChanged(changedTokens: Token[]): Promise<void> {
this.emit('updated-tokens', changedTokens);
}
async handleTokensDeleted(deletedTokens: Token[]): Promise<void> {
this.emit('deleted-tokens', deletedTokens);
}
// A Conflictable could emit conflict-based events if data changed based on verification
// These events should be emitted *after* the conflicts have been resolved
export type Conflictable = {
'conflict-metadata': (oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => void;
'conflict-channels': (changes: Changes<Channel>) => void;
'conflict-members': (changes: Changes<Member>) => void;
'conflict-messages': (changes: Changes<Message>) => void;
'conflict-tokens': (changes: Changes<Token>) => void;
'conflict-resource': (oldResource: Resource, newResource: Resource) => void;
}