tokens subscription
This commit is contained in:
parent
2a79af8a0e
commit
1c1d3209bf
@ -332,7 +332,7 @@ export class Token implements WithEquals<Token> {
|
||||
public readonly token: string,
|
||||
public member: Member | { id: string } | null,
|
||||
public readonly created: Date,
|
||||
public readonly expires: Date,
|
||||
public readonly expires: Date | null,
|
||||
public readonly source?: unknown
|
||||
) {
|
||||
this.id = token; // for comparison purposes
|
||||
@ -365,6 +365,19 @@ export class Token implements WithEquals<Token> {
|
||||
this.expires === other.expires
|
||||
)
|
||||
}
|
||||
|
||||
static sortFurthestExpiresFirst(a: Token, b: Token) {
|
||||
// reverse-expire time order (most future expires comes first)
|
||||
if (a.expires && b.expires) {
|
||||
return b.expires.getTime() - a.expires.getTime();
|
||||
} else if (a.expires) {
|
||||
return -1;
|
||||
} else if (b.expires) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NotInitializedError extends Error {
|
||||
|
@ -1,63 +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 moment from 'moment';
|
||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { Member, Token } from '../../data-types';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import BaseElements from '../require/base-elements';
|
||||
import Button, { ButtonColorType } from './button';
|
||||
|
||||
export interface InvitesTableProps {
|
||||
guild: CombinedGuild
|
||||
}
|
||||
|
||||
const InvitesTable: FC<InvitesTableProps> = (props: InvitesTableProps) => {
|
||||
const { guild } = props;
|
||||
|
||||
const [ tokens, setTokens ] = useState<Token[] | null>(null);
|
||||
const [ tokensFailed, setTokensFailed ] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const tokens = await guild.fetchTokens();
|
||||
setTokens(tokens);
|
||||
} catch (e: unknown) {
|
||||
LOG.error('unable to fetch tokens', e);
|
||||
setTokensFailed(true);
|
||||
}
|
||||
})();
|
||||
}, [])
|
||||
|
||||
const tokenElements = useMemo(() => {
|
||||
if (tokensFailed) {
|
||||
return <div className="tokens-failed">Unable to load tokens</div>;
|
||||
}
|
||||
return tokens?.map((token: Token) => {
|
||||
const userText = (token.member instanceof Member ? 'Used by ' + token.member.displayName : token.member?.id) ?? 'Unused Token';
|
||||
return (
|
||||
<div key={token.token} className="token-row">
|
||||
<div className="user-token">
|
||||
<div className="user">{userText}</div>
|
||||
<div className="token">{token.token}</div>
|
||||
</div>
|
||||
<div className="created-expires">
|
||||
<div className="created">Created {moment(token.created).fromNow()}</div>
|
||||
<div className="expires">Expires {moment(token.expires).fromNow()}</div>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<Button colorType={ButtonColorType.BRAND}>{BaseElements.DOWNLOAD}</Button>
|
||||
<Button colorType={ButtonColorType.NEGATIVE}>{BaseElements.TRASHCAN}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}, [ tokens, tokensFailed ]);
|
||||
|
||||
return <div className="invites-table">{tokenElements}</div>
|
||||
}
|
||||
|
||||
export default InvitesTable;
|
@ -8,15 +8,15 @@ import { FC } from 'react';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import Display from '../components/display';
|
||||
import InvitePreview from '../components/invite-preview';
|
||||
import { GuildMetadata } from '../../data-types';
|
||||
import { Member, Token } from '../../data-types';
|
||||
import ReactHelper from '../require/react-helper';
|
||||
import { Duration } from 'moment';
|
||||
import moment from 'moment';
|
||||
import DropdownInput from '../components/input-dropdown';
|
||||
import Button from '../components/button';
|
||||
import InvitesTable from '../components/table-invites';
|
||||
import Button, { ButtonColorType } from '../components/button';
|
||||
import GuildSubscriptions from '../require/guild-subscriptions';
|
||||
import ElementsUtil from '../require/elements-util';
|
||||
import BaseElements from '../require/base-elements';
|
||||
|
||||
|
||||
export interface GuildInvitesDisplayProps {
|
||||
@ -27,6 +27,8 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
||||
|
||||
const url = 'https://localhost:3030'; // TODO: this will likely be a dropdown list at some point
|
||||
|
||||
const [ tokens, tokensError ] = GuildSubscriptions.useTokensSubscription(guild);
|
||||
|
||||
const [ guildMeta, guildMetaError ] = GuildSubscriptions.useGuildMetadataSubscription(guild);
|
||||
const [ iconSrc ] = ReactHelper.useAsyncActionSubscription(
|
||||
async () => await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, guildMeta?.iconResourceId ?? null),
|
||||
@ -52,6 +54,31 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
||||
return null;
|
||||
}, [ guildMetaError ]);
|
||||
|
||||
const tokenElements = useMemo(() => {
|
||||
if (tokensError) {
|
||||
return <div className="tokens-failed">Unable to load tokens</div>;
|
||||
}
|
||||
return tokens?.map((token: Token) => {
|
||||
const userText = (token.member instanceof Member ? 'Used by ' + token.member.displayName : token.member?.id) ?? 'Unused Token';
|
||||
return (
|
||||
<div key={token.token} className="token-row">
|
||||
<div className="user-token">
|
||||
<div className="user">{userText}</div>
|
||||
<div className="token">{token.token}</div>
|
||||
</div>
|
||||
<div className="created-expires">
|
||||
<div className="created">Created {moment(token.created).fromNow()}</div>
|
||||
<div className="expires">Expires {moment(token.expires).fromNow()}</div>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<Button colorType={ButtonColorType.BRAND}>{BaseElements.DOWNLOAD}</Button>
|
||||
<Button colorType={ButtonColorType.NEGATIVE}>{BaseElements.TRASHCAN}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}, [ tokens, tokensError ]);
|
||||
|
||||
return (
|
||||
<Display
|
||||
infoMessage={null} errorMessage={errorMessage}
|
||||
@ -78,7 +105,7 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
||||
<div className="divider" />
|
||||
<div className="view-invites">
|
||||
<div className="title">Invite History</div>
|
||||
<InvitesTable guild={guild} />
|
||||
<div className="invites-table">{tokenElements}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Display>
|
||||
|
@ -32,7 +32,7 @@ export default function createTokenLogOverlay(document: Document, q: Q, guild: C
|
||||
// if (token.member != null) {
|
||||
// continue;
|
||||
// }
|
||||
const expired = token.expires < new Date();
|
||||
const expired = token.expires && token.expires < new Date();
|
||||
// NOTE: It may be nice to be able to click-to-copy the token
|
||||
let memberText: string;
|
||||
if (token.member instanceof Member) {
|
||||
|
@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
||||
import Logger from '../../../../logger/logger';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import { GuildMetadata, Resource } from "../../data-types";
|
||||
import { Changes, GuildMetadata, Resource } from "../../data-types";
|
||||
import CombinedGuild from "../../guild-combined";
|
||||
|
||||
import React, { DependencyList, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@ -11,14 +11,24 @@ import { AutoVerifierChangesType } from "../../auto-verifier";
|
||||
import { Conflictable, Connectable } from "../../guild-types";
|
||||
import { EventEmitter } from 'tsee';
|
||||
import { IDQuery } from '../../auto-verifier-with-args';
|
||||
import { Token } from '../../data-types';
|
||||
|
||||
export type SubscriptionEvents = {
|
||||
export type SingleSubscriptionEvents = {
|
||||
'fetch': () => void;
|
||||
'update': () => void;
|
||||
'updated': () => void;
|
||||
'conflict': () => void;
|
||||
'fetch-error': () => void;
|
||||
}
|
||||
|
||||
export type MultipleSubscriptionEvents<T> = {
|
||||
'fetch': () => void;
|
||||
'fetch-error': () => void;
|
||||
'new': (newValues: T[]) => void;
|
||||
'updated': (updatedValues: T[]) => void;
|
||||
'removed': (removedValues: T[]) => void;
|
||||
'conflict': (changes: Changes<T>) => void;
|
||||
}
|
||||
|
||||
interface EffectParams<T> {
|
||||
guild: CombinedGuild;
|
||||
onFetch: (value: T | null) => void;
|
||||
@ -29,11 +39,31 @@ interface EffectParams<T> {
|
||||
|
||||
type Arguments<T> = T extends (...args: infer A) => unknown ? A : never;
|
||||
|
||||
interface EventMappingParams<T, UE extends keyof Connectable, CE extends keyof Conflictable> {
|
||||
updateEventName: UE;
|
||||
updateEventArgsMap: (...args: Arguments<Connectable[UE]>) => T; // should be same as the params list from Connectable
|
||||
interface SingleEventMappingParams<T, UE extends keyof Connectable, CE extends keyof Conflictable> {
|
||||
updatedEventName: UE;
|
||||
updatedEventArgsMap: (...args: Arguments<Connectable[UE]>) => T;
|
||||
conflictEventName: CE;
|
||||
conflictEventArgsMap: (...args: Arguments<Conflictable[CE]>) => T; // should be the same as the params list from Conflictable
|
||||
conflictEventArgsMap: (...args: Arguments<Conflictable[CE]>) => T;
|
||||
}
|
||||
|
||||
interface MultipleEventMappingParams<
|
||||
T,
|
||||
NE extends keyof Connectable,
|
||||
UE extends keyof Connectable,
|
||||
RE extends keyof Connectable,
|
||||
CE extends keyof Conflictable
|
||||
> {
|
||||
newEventName: NE;
|
||||
newEventArgsMap: (...args: Arguments<Connectable[NE]>) => T[]; // list of new elements
|
||||
updatedEventName: UE;
|
||||
updatedEventArgsMap: (...args: Arguments<Connectable[UE]>) => T[]; // list of updated elements
|
||||
removedEventName: RE;
|
||||
removedEventArgsMap: (...args: Arguments<Connectable[RE]>) => T[]; // list of removed elements
|
||||
|
||||
conflictEventName: CE;
|
||||
conflictEventArgsMap: (...args: Arguments<Conflictable[CE]>) => Changes<T>;
|
||||
|
||||
sortFunc: (a: T, b: T) => number;
|
||||
}
|
||||
|
||||
export default class GuildSubscriptions {
|
||||
@ -79,16 +109,19 @@ export default class GuildSubscriptions {
|
||||
}
|
||||
|
||||
private static useSingleGuildSubscription<T, UE extends keyof Connectable, CE extends keyof Conflictable>(
|
||||
guild: CombinedGuild, eventMappingParams: EventMappingParams<T, UE, CE>, fetchFunc: (() => Promise<T>) | (() => Promise<T | null>), fetchDeps: DependencyList
|
||||
): [value: T | null, fetchError: unknown | null, events: EventEmitter<SubscriptionEvents>] {
|
||||
const { updateEventName, updateEventArgsMap, conflictEventName, conflictEventArgsMap } = eventMappingParams;
|
||||
guild: CombinedGuild,
|
||||
eventMappingParams: SingleEventMappingParams<T, UE, CE>,
|
||||
fetchFunc: (() => Promise<T>) | (() => Promise<T | null>),
|
||||
fetchDeps: DependencyList
|
||||
): [value: T | null, fetchError: unknown | null, events: EventEmitter<SingleSubscriptionEvents>] {
|
||||
const { updatedEventName, updatedEventArgsMap, conflictEventName, conflictEventArgsMap } = eventMappingParams;
|
||||
|
||||
const isMountedRef = useRef<boolean>(false);
|
||||
|
||||
const [ fetchError, setFetchError ] = useState<unknown | null>(null);
|
||||
const [ value, setValue ] = useState<T | null>(null);
|
||||
|
||||
const events = useMemo(() => new EventEmitter<SubscriptionEvents>(), []);
|
||||
const events = useMemo(() => new EventEmitter<SingleSubscriptionEvents>(), []);
|
||||
|
||||
const onFetch = useCallback((fetchValue: T | null) => {
|
||||
setValue(fetchValue);
|
||||
@ -102,9 +135,9 @@ export default class GuildSubscriptions {
|
||||
events.emit('fetch-error');
|
||||
}, []);
|
||||
|
||||
const onUpdate = useCallback((updateValue: T) => {
|
||||
const onUpdated = useCallback((updateValue: T) => {
|
||||
setValue(updateValue);
|
||||
events.emit('update');
|
||||
events.emit('updated');
|
||||
}, []);
|
||||
|
||||
const onConflict = useCallback((conflictValue: T) => {
|
||||
@ -116,8 +149,8 @@ export default class GuildSubscriptions {
|
||||
// otherwise, I may have done this wrong. Forcing it to work with these calls
|
||||
const boundUpdateFunc = useCallback((...args: Arguments<Connectable[UE]>): void => {
|
||||
if (!isMountedRef.current) return;
|
||||
const value = updateEventArgsMap(...args);
|
||||
onUpdate(value);
|
||||
const value = updatedEventArgsMap(...args);
|
||||
onUpdated(value);
|
||||
}, []) as (Connectable & Conflictable)[UE];
|
||||
const boundConflictFunc = useCallback((...args: Arguments<Conflictable[CE]>): void => {
|
||||
if (!isMountedRef.current) return;
|
||||
@ -126,11 +159,129 @@ export default class GuildSubscriptions {
|
||||
}, []) as (Connectable & Conflictable)[CE];
|
||||
|
||||
const bindEventsFunc = useCallback(() => {
|
||||
guild.on(updateEventName, boundUpdateFunc);
|
||||
guild.on(updatedEventName, boundUpdateFunc);
|
||||
guild.on(conflictEventName, boundConflictFunc);
|
||||
}, []);
|
||||
const unbindEventsFunc = useCallback(() => {
|
||||
guild.off(updateEventName, boundUpdateFunc);
|
||||
guild.off(updatedEventName, boundUpdateFunc);
|
||||
guild.off(conflictEventName, boundConflictFunc);
|
||||
}, []);
|
||||
|
||||
GuildSubscriptions.useGuildSubscriptionEffect(isMountedRef, {
|
||||
guild,
|
||||
onFetch,
|
||||
onFetchError,
|
||||
bindEventsFunc,
|
||||
unbindEventsFunc
|
||||
}, fetchFunc, fetchDeps);
|
||||
|
||||
return [ value, fetchError, events ];
|
||||
}
|
||||
|
||||
private static useMultipleGuildSubscription<
|
||||
T extends { id: string },
|
||||
NE extends keyof Connectable,
|
||||
UE extends keyof Connectable,
|
||||
RE extends keyof Connectable,
|
||||
CE extends keyof Conflictable
|
||||
>(
|
||||
guild: CombinedGuild,
|
||||
eventMappingParams: MultipleEventMappingParams<T, NE, UE, RE, CE>,
|
||||
fetchFunc: (() => Promise<T[]>) | (() => Promise<T[] | null>),
|
||||
fetchDeps: DependencyList
|
||||
): [value: T[] | null, fetchError: unknown | null, events: EventEmitter<MultipleSubscriptionEvents<T>>] {
|
||||
const {
|
||||
newEventName, newEventArgsMap,
|
||||
updatedEventName, updatedEventArgsMap,
|
||||
removedEventName, removedEventArgsMap,
|
||||
conflictEventName, conflictEventArgsMap,
|
||||
sortFunc
|
||||
} = eventMappingParams;
|
||||
|
||||
const isMountedRef = useRef<boolean>(false);
|
||||
|
||||
const [ fetchError, setFetchError ] = useState<unknown | null>(null);
|
||||
const [ value, setValue ] = useState<T[] | null>(null);
|
||||
|
||||
const events = useMemo(() => new EventEmitter<MultipleSubscriptionEvents<T>>(), []);
|
||||
|
||||
const onFetch = useCallback((fetchValue: T[] | null) => {
|
||||
setValue(fetchValue);
|
||||
setFetchError(null);
|
||||
events.emit('fetch');
|
||||
}, []);
|
||||
|
||||
const onFetchError = useCallback((e: unknown) => {
|
||||
setFetchError(e);
|
||||
setValue(null);
|
||||
events.emit('fetch-error');
|
||||
}, []);
|
||||
|
||||
const onNew = useCallback((newElements: T[]) => {
|
||||
setValue(currentValue => {
|
||||
if (currentValue === null) return null;
|
||||
return currentValue.concat(newElements).sort(sortFunc);
|
||||
})
|
||||
events.emit('new', newElements);
|
||||
}, []);
|
||||
const onUpdated = useCallback((updatedElements: T[]) => {
|
||||
setValue(currentValue => {
|
||||
if (currentValue === null) return null;
|
||||
return currentValue.map(element => updatedElements.find(updatedElement => updatedElement.id === element.id) ?? element).sort(sortFunc);
|
||||
});
|
||||
events.emit('updated', updatedElements);
|
||||
}, []);
|
||||
const onRemoved = useCallback((removedElements: T[]) => {
|
||||
setValue(currentValue => {
|
||||
if (currentValue === null) return null;
|
||||
const deletedIds = new Set(removedElements.map(deletedElement => deletedElement.id));
|
||||
return currentValue.filter(element => !deletedIds.has(element.id)).sort(sortFunc);
|
||||
});
|
||||
events.emit('removed', removedElements);
|
||||
}, []);
|
||||
|
||||
const onConflict = useCallback((changes: Changes<T>) => {
|
||||
setValue(currentValue => {
|
||||
if (currentValue === null) return null;
|
||||
const deletedIds = new Set(changes.deleted.map(deletedElement => deletedElement.id));
|
||||
return currentValue
|
||||
.concat(changes.added)
|
||||
.map(element => changes.updated.find(change => change.newDataPoint.id === element.id)?.newDataPoint ?? element)
|
||||
.filter(element => !deletedIds.has(element.id))
|
||||
.sort(sortFunc);
|
||||
});
|
||||
events.emit('conflict', changes);
|
||||
}, []);
|
||||
|
||||
// I think the typed EventEmitter class isn't ready for this level of insane type safety
|
||||
// otherwise, I may have done this wrong. Forcing it to work with these calls
|
||||
const boundNewFunc = useCallback((...args: Arguments<Connectable[NE]>): void => {
|
||||
if (!isMountedRef.current) return;
|
||||
onNew(newEventArgsMap(...args));
|
||||
}, []) as (Connectable & Conflictable)[NE];
|
||||
const boundUpdateFunc = useCallback((...args: Arguments<Connectable[UE]>): void => {
|
||||
if (!isMountedRef.current) return;
|
||||
onUpdated(updatedEventArgsMap(...args));
|
||||
}, []) as (Connectable & Conflictable)[UE];
|
||||
const boundRemovedFunc = useCallback((...args: Arguments<Connectable[RE]>): void => {
|
||||
if (!isMountedRef.current) return;
|
||||
onRemoved(removedEventArgsMap(...args));
|
||||
}, []) as (Connectable & Conflictable)[RE];
|
||||
const boundConflictFunc = useCallback((...args: Arguments<Conflictable[CE]>): void => {
|
||||
if (!isMountedRef.current) return;
|
||||
onConflict(conflictEventArgsMap(...args));
|
||||
}, []) as (Connectable & Conflictable)[CE];
|
||||
|
||||
const bindEventsFunc = useCallback(() => {
|
||||
guild.on(newEventName, boundNewFunc);
|
||||
guild.on(updatedEventName, boundUpdateFunc);
|
||||
guild.on(removedEventName, boundRemovedFunc);
|
||||
guild.on(conflictEventName, boundConflictFunc);
|
||||
}, []);
|
||||
const unbindEventsFunc = useCallback(() => {
|
||||
guild.off(newEventName, boundNewFunc);
|
||||
guild.off(updatedEventName, boundUpdateFunc);
|
||||
guild.off(removedEventName, boundRemovedFunc);
|
||||
guild.off(conflictEventName, boundConflictFunc);
|
||||
}, []);
|
||||
|
||||
@ -147,17 +298,19 @@ export default class GuildSubscriptions {
|
||||
|
||||
static useGuildMetadataSubscription(guild: CombinedGuild) {
|
||||
return GuildSubscriptions.useSingleGuildSubscription<GuildMetadata, 'update-metadata', 'conflict-metadata'>(guild, {
|
||||
updateEventName: 'update-metadata',
|
||||
updateEventArgsMap: (guildMeta: GuildMetadata) => guildMeta,
|
||||
updatedEventName: 'update-metadata',
|
||||
updatedEventArgsMap: (guildMeta: GuildMetadata) => guildMeta,
|
||||
conflictEventName: 'conflict-metadata',
|
||||
conflictEventArgsMap: (changesType: AutoVerifierChangesType, oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => newGuildMeta
|
||||
}, async () => await guild.fetchMetadata(), [ guild ]);
|
||||
}, async () => {
|
||||
return await guild.fetchMetadata()
|
||||
}, [ guild ]);
|
||||
}
|
||||
|
||||
static useResourceSubscription(guild: CombinedGuild, resourceId: string | null) {
|
||||
return GuildSubscriptions.useSingleGuildSubscription<Resource, 'update-resource', 'conflict-resource'>(guild, {
|
||||
updateEventName: 'update-resource',
|
||||
updateEventArgsMap: (resource: Resource) => resource,
|
||||
updatedEventName: 'update-resource',
|
||||
updatedEventArgsMap: (resource: Resource) => resource,
|
||||
conflictEventName: 'conflict-resource',
|
||||
conflictEventArgsMap: (query: IDQuery, changesType: AutoVerifierChangesType, oldResource: Resource, newResource: Resource) => newResource
|
||||
}, async () => {
|
||||
@ -165,4 +318,20 @@ export default class GuildSubscriptions {
|
||||
return await guild.fetchResource(resourceId);
|
||||
}, [ guild, resourceId ]);
|
||||
}
|
||||
|
||||
static useTokensSubscription(guild: CombinedGuild) {
|
||||
return GuildSubscriptions.useMultipleGuildSubscription<Token, 'new-tokens', 'update-tokens', 'remove-tokens', 'conflict-tokens'>(guild, {
|
||||
newEventName: 'new-tokens',
|
||||
newEventArgsMap: (tokens: Token[]) => tokens,
|
||||
updatedEventName: 'update-tokens',
|
||||
updatedEventArgsMap: (updatedTokens: Token[]) => updatedTokens,
|
||||
removedEventName: 'remove-tokens',
|
||||
removedEventArgsMap: (removedTokens: Token[]) => removedTokens,
|
||||
conflictEventName: 'conflict-tokens',
|
||||
conflictEventArgsMap: (changesType: AutoVerifierChangesType, changes: Changes<Token>) => changes,
|
||||
sortFunc: Token.sortFurthestExpiresFirst
|
||||
}, async () => {
|
||||
return await guild.fetchTokens();
|
||||
}, [ guild ]);
|
||||
}
|
||||
}
|
||||
|
@ -131,7 +131,10 @@ export type Connectable = {
|
||||
|
||||
// TODO: Implement these in server/combined guild
|
||||
'update-resource': (updatedResource: Resource) => void;
|
||||
'remove-resource': (removedResource: Resource) => void;
|
||||
|
||||
'new-tokens': (tokens: Token[]) => void;
|
||||
'update-tokens': (updatedTokens: Token[]) => void;
|
||||
'remove-tokens': (removedTokens: Token[]) => void;
|
||||
}
|
||||
|
||||
// A Conflictable could emit conflict-based events if data changed based on verification
|
||||
@ -165,4 +168,4 @@ export const GuildEventNames = [
|
||||
'conflict-messages',
|
||||
'conflict-tokens',
|
||||
'conflict-resource',
|
||||
]
|
||||
];
|
||||
|
Loading…
Reference in New Issue
Block a user