fixed subscriptions re-requesting because of re-created fetch functions

This commit is contained in:
Michael Peters 2021-12-13 01:32:29 -06:00
parent 1c1d3209bf
commit d875a8f8fb
10 changed files with 141 additions and 42 deletions

View File

@ -323,20 +323,21 @@ export class Resource implements WithEquals<Resource> {
this.hash.toString('hex') === other.hash.toString('hex') this.hash.toString('hex') === other.hash.toString('hex')
); );
} }
toString() {
return 'r#' + this.id;
}
} }
export class Token implements WithEquals<Token> { export class Token implements WithEquals<Token> {
public id: string;
constructor( constructor(
public readonly id: string,
public readonly token: string, public readonly token: string,
public member: Member | { id: string } | null, public member: Member | { id: string } | null,
public readonly created: Date, public readonly created: Date,
public readonly expires: Date | null, public readonly expires: Date | null,
public readonly source?: unknown public readonly source?: unknown
) { ) {}
this.id = token; // for comparison purposes
}
fill(members: Map<string, Member>) { fill(members: Map<string, Member>) {
if (this.member) { if (this.member) {
@ -349,6 +350,7 @@ export class Token implements WithEquals<Token> {
static fromDBData(dataToken: any): Token { static fromDBData(dataToken: any): Token {
return new Token( return new Token(
dataToken.id,
dataToken.token, dataToken.token,
dataToken.member_id ? { id: dataToken.member_id } : null, dataToken.member_id ? { id: dataToken.member_id } : null,
new Date(dataToken.created), new Date(dataToken.created),
@ -359,11 +361,12 @@ export class Token implements WithEquals<Token> {
equals(other: Token) { equals(other: Token) {
return ( return (
this.id === other.id &&
this.token === other.token && this.token === other.token &&
this.member?.id === other.member?.id && this.member?.id === other.member?.id &&
this.created === other.created && this.created === other.created &&
this.expires === other.expires this.expires === other.expires
) );
} }
static sortFurthestExpiresFirst(a: Token, b: Token) { static sortFurthestExpiresFirst(a: Token, b: Token) {
@ -378,6 +381,10 @@ export class Token implements WithEquals<Token> {
return 0; return 0;
} }
} }
toString() {
return `t#${this.id}/${this.token}`;
}
} }
export class NotInitializedError extends Error { export class NotInitializedError extends Error {

View File

@ -72,7 +72,10 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
</div> </div>
<div className="actions"> <div className="actions">
<Button colorType={ButtonColorType.BRAND}>{BaseElements.DOWNLOAD}</Button> <Button colorType={ButtonColorType.BRAND}>{BaseElements.DOWNLOAD}</Button>
<Button colorType={ButtonColorType.NEGATIVE}>{BaseElements.TRASHCAN}</Button> <Button
colorType={ButtonColorType.NEGATIVE}
onClick={async () => await guild.requestDoRevokeToken(token.token)}
>{BaseElements.TRASHCAN}</Button>
</div> </div>
</div> </div>
); );

View File

@ -1,3 +1,8 @@
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 React, { FC, useEffect, useMemo, useState } from "react"; import React, { FC, useEffect, useMemo, useState } from "react";
import CombinedGuild from "../../guild-combined"; import CombinedGuild from "../../guild-combined";
import ChoicesControl from "../components/control-choices"; import ChoicesControl from "../components/control-choices";

View File

@ -70,23 +70,24 @@ export default class GuildSubscriptions {
private static useGuildSubscriptionEffect<T>( private static useGuildSubscriptionEffect<T>(
isMountedRef: React.MutableRefObject<boolean>, isMountedRef: React.MutableRefObject<boolean>,
subscriptionParams: EffectParams<T>, subscriptionParams: EffectParams<T>,
fetchFunc: (() => Promise<T>) | (() => Promise<T | null>), fetchFunc: (() => Promise<T>) | (() => Promise<T | null>)
fetchDeps: DependencyList
) { ) {
const { guild, onFetch, onFetchError, bindEventsFunc, unbindEventsFunc } = subscriptionParams; const { guild, onFetch, onFetchError, bindEventsFunc, unbindEventsFunc } = subscriptionParams;
const fetchManagerFunc = useCallback(async () => { const fetchManagerFunc = useMemo(() => {
if (!isMountedRef.current) return; return async () => {
try {
const value = await fetchFunc();
if (!isMountedRef.current) return; if (!isMountedRef.current) return;
onFetch(value); try {
} catch (e: unknown) { const value = await fetchFunc();
LOG.error('error fetching for subscription', e); if (!isMountedRef.current) return;
if (!isMountedRef.current) return; onFetch(value);
onFetchError(e); } catch (e: unknown) {
LOG.error('error fetching for subscription', e);
if (!isMountedRef.current) return;
onFetchError(e);
}
} }
}, [ ...fetchDeps, fetchFunc ]); }, [ fetchFunc ]);
useEffect(() => { useEffect(() => {
isMountedRef.current = true; isMountedRef.current = true;
@ -111,8 +112,7 @@ export default class GuildSubscriptions {
private static useSingleGuildSubscription<T, UE extends keyof Connectable, CE extends keyof Conflictable>( private static useSingleGuildSubscription<T, UE extends keyof Connectable, CE extends keyof Conflictable>(
guild: CombinedGuild, guild: CombinedGuild,
eventMappingParams: SingleEventMappingParams<T, UE, CE>, eventMappingParams: SingleEventMappingParams<T, UE, CE>,
fetchFunc: (() => Promise<T>) | (() => Promise<T | null>), fetchFunc: (() => Promise<T>) | (() => Promise<T | null>)
fetchDeps: DependencyList
): [value: T | null, fetchError: unknown | null, events: EventEmitter<SingleSubscriptionEvents>] { ): [value: T | null, fetchError: unknown | null, events: EventEmitter<SingleSubscriptionEvents>] {
const { updatedEventName, updatedEventArgsMap, conflictEventName, conflictEventArgsMap } = eventMappingParams; const { updatedEventName, updatedEventArgsMap, conflictEventName, conflictEventArgsMap } = eventMappingParams;
@ -173,7 +173,7 @@ export default class GuildSubscriptions {
onFetchError, onFetchError,
bindEventsFunc, bindEventsFunc,
unbindEventsFunc unbindEventsFunc
}, fetchFunc, fetchDeps); }, fetchFunc);
return [ value, fetchError, events ]; return [ value, fetchError, events ];
} }
@ -187,8 +187,7 @@ export default class GuildSubscriptions {
>( >(
guild: CombinedGuild, guild: CombinedGuild,
eventMappingParams: MultipleEventMappingParams<T, NE, UE, RE, CE>, eventMappingParams: MultipleEventMappingParams<T, NE, UE, RE, CE>,
fetchFunc: (() => Promise<T[]>) | (() => Promise<T[] | null>), fetchFunc: (() => Promise<T[]>) | (() => Promise<T[] | null>)
fetchDeps: DependencyList
): [value: T[] | null, fetchError: unknown | null, events: EventEmitter<MultipleSubscriptionEvents<T>>] { ): [value: T[] | null, fetchError: unknown | null, events: EventEmitter<MultipleSubscriptionEvents<T>>] {
const { const {
newEventName, newEventArgsMap, newEventName, newEventArgsMap,
@ -291,35 +290,43 @@ export default class GuildSubscriptions {
onFetchError, onFetchError,
bindEventsFunc, bindEventsFunc,
unbindEventsFunc unbindEventsFunc
}, fetchFunc, fetchDeps); }, fetchFunc);
return [ value, fetchError, events ]; return [ value, fetchError, events ];
} }
static useGuildMetadataSubscription(guild: CombinedGuild) { static useGuildMetadataSubscription(guild: CombinedGuild) {
const fetchMetadataFunc = useCallback(async () => {
//LOG.silly('fetching metadata for subscription');
return await guild.fetchMetadata();
}, [ guild ]);
return GuildSubscriptions.useSingleGuildSubscription<GuildMetadata, 'update-metadata', 'conflict-metadata'>(guild, { return GuildSubscriptions.useSingleGuildSubscription<GuildMetadata, 'update-metadata', 'conflict-metadata'>(guild, {
updatedEventName: 'update-metadata', updatedEventName: 'update-metadata',
updatedEventArgsMap: (guildMeta: GuildMetadata) => guildMeta, updatedEventArgsMap: (guildMeta: GuildMetadata) => guildMeta,
conflictEventName: 'conflict-metadata', conflictEventName: 'conflict-metadata',
conflictEventArgsMap: (changesType: AutoVerifierChangesType, oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => newGuildMeta conflictEventArgsMap: (changesType: AutoVerifierChangesType, oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => newGuildMeta
}, async () => { }, fetchMetadataFunc);
return await guild.fetchMetadata()
}, [ guild ]);
} }
static useResourceSubscription(guild: CombinedGuild, resourceId: string | null) { static useResourceSubscription(guild: CombinedGuild, resourceId: string | null) {
const fetchResourceFunc = useCallback(async () => {
//LOG.silly('fetching resource for subscription (resourceId: ' + resourceId + ')');
if (resourceId === null) return null;
return await guild.fetchResource(resourceId);
}, [ guild, resourceId ]);
return GuildSubscriptions.useSingleGuildSubscription<Resource, 'update-resource', 'conflict-resource'>(guild, { return GuildSubscriptions.useSingleGuildSubscription<Resource, 'update-resource', 'conflict-resource'>(guild, {
updatedEventName: 'update-resource', updatedEventName: 'update-resource',
updatedEventArgsMap: (resource: Resource) => resource, updatedEventArgsMap: (resource: Resource) => resource,
conflictEventName: 'conflict-resource', conflictEventName: 'conflict-resource',
conflictEventArgsMap: (query: IDQuery, changesType: AutoVerifierChangesType, oldResource: Resource, newResource: Resource) => newResource conflictEventArgsMap: (query: IDQuery, changesType: AutoVerifierChangesType, oldResource: Resource, newResource: Resource) => newResource
}, async () => { }, fetchResourceFunc);
if (resourceId === null) return null;
return await guild.fetchResource(resourceId);
}, [ guild, resourceId ]);
} }
static useTokensSubscription(guild: CombinedGuild) { static useTokensSubscription(guild: CombinedGuild) {
const fetchTokensFunc = useCallback(async () => {
//LOG.silly('fetching tokens for subscription');
return await guild.fetchTokens();
}, [ guild ]);
return GuildSubscriptions.useMultipleGuildSubscription<Token, 'new-tokens', 'update-tokens', 'remove-tokens', 'conflict-tokens'>(guild, { return GuildSubscriptions.useMultipleGuildSubscription<Token, 'new-tokens', 'update-tokens', 'remove-tokens', 'conflict-tokens'>(guild, {
newEventName: 'new-tokens', newEventName: 'new-tokens',
newEventArgsMap: (tokens: Token[]) => tokens, newEventArgsMap: (tokens: Token[]) => tokens,
@ -330,8 +337,6 @@ export default class GuildSubscriptions {
conflictEventName: 'conflict-tokens', conflictEventName: 'conflict-tokens',
conflictEventArgsMap: (changesType: AutoVerifierChangesType, changes: Changes<Token>) => changes, conflictEventArgsMap: (changesType: AutoVerifierChangesType, changes: Changes<Token>) => changes,
sortFunc: Token.sortFurthestExpiresFirst sortFunc: Token.sortFurthestExpiresFirst
}, async () => { }, fetchTokensFunc);
return await guild.fetchTokens();
}, [ guild ]);
} }
} }

View File

@ -123,6 +123,40 @@ export default class CombinedGuild extends EventEmitter<Connectable & Conflictab
this.emit('update-messages', messages); this.emit('update-messages', messages);
}); });
// Resources
this.socketGuild.on('update-resource', async (updatedResource: Resource) => {
LOG.info(`g#${this.id} updated ${updatedResource}`);
this.ramGuild.handleResourceChanged(updatedResource);
this.personalDBGuild.handleResourceChanged(updatedResource);
this.emit('update-resource', updatedResource);
});
// Tokens
this.socketGuild.on('new-tokens', async (newTokens: Token[]) => {
for (const token of newTokens) {
LOG.info(`g#${this.id} new token ${token}`);
}
this.ramGuild.handleTokensAdded(newTokens);
this.personalDBGuild.handleTokensAdded(newTokens);
this.emit('new-tokens', newTokens);
});
this.socketGuild.on('update-tokens', async (updatedTokens: Token[]) => {
for (const token of updatedTokens) {
LOG.info(`g#${this.id} updated ${token}`);
}
this.ramGuild.handleTokensChanged(updatedTokens);
this.personalDBGuild.handleTokensChanged(updatedTokens);
this.emit('update-tokens', updatedTokens);
});
this.socketGuild.on('remove-tokens', async (removedTokens: Token[]) => {
for (const token of removedTokens) {
LOG.info(`g#${this.id} removed ${token}`);
}
this.ramGuild.handleTokensDeleted(removedTokens);
this.personalDBGuild.handleTokensDeleted(removedTokens);
this.emit('remove-tokens', removedTokens);
});
const diskSocket = new PairVerifierFetchable(this.personalDBGuild, this.socketGuild); const diskSocket = new PairVerifierFetchable(this.personalDBGuild, this.socketGuild);
const ramDiskSocket = new PairVerifierFetchable(this.ramGuild, diskSocket); const ramDiskSocket = new PairVerifierFetchable(this.ramGuild, diskSocket);

View File

@ -62,6 +62,27 @@ export default class SocketGuild extends EventEmitter<Connectable> implements As
const message = Message.fromDBData(dataMessage); const message = Message.fromDBData(dataMessage);
this.emit('new-messages', [ message ]); this.emit('new-messages', [ message ]);
}); });
// TODO: The server does not emit update-resource
this.socket.on('update-resource', async (updatedDataResource: any) => {
const updatedResource = Resource.fromDBData(updatedDataResource);
this.emit('update-resource', updatedResource);
});
// TODO: The server does not emit new-token
this.socket.on('new-token', async (newDataToken: any) => {
const newToken = Token.fromDBData(newDataToken);
this.emit('new-tokens', [ newToken ]);
});
// TODO: The server does not emit update-token
this.socket.on('update-token', async (updatedDataToken: any) => {
const updatedToken = Token.fromDBData(updatedDataToken);
this.emit('update-tokens', [ updatedToken ]);
});
this.socket.on('revoke-token', async (revokedDataToken: any) => {
const revokedToken = Token.fromDBData(revokedDataToken);
this.emit('remove-tokens', [ revokedToken ]);
});
} }
public disconnect() { public disconnect() {

View File

@ -162,6 +162,10 @@ export const GuildEventNames = [
'new-messages', 'new-messages',
'update-messages', 'update-messages',
'remove-messages', 'remove-messages',
'update-resource',
'new-tokens',
'update-tokens',
'remove-tokens',
'conflict-metadata', 'conflict-metadata',
'conflict-channels', 'conflict-channels',
'conflict-members', 'conflict-members',

View File

@ -40,6 +40,12 @@ export default class GuildsManager extends EventEmitter<{
'update-messages': (guild: CombinedGuild, updatedMessages: Message[]) => void; 'update-messages': (guild: CombinedGuild, updatedMessages: Message[]) => void;
'remove-messages': (guild: CombinedGuild, removedMessages: Message[]) => void; 'remove-messages': (guild: CombinedGuild, removedMessages: Message[]) => void;
'update-resource': (guild: CombinedGuild, updatedResource: Resource) => void;
'new-tokens': (guild: CombinedGuild, newTokens: Token[]) => void;
'update-tokens': (guild: CombinedGuild, updatedTokens: Token[]) => void;
'remove-tokens': (guild: CombinedGuild, removedTokens: Token[]) => void;
'conflict-metadata': (guild: CombinedGuild, changesType: AutoVerifierChangesType, oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => void; 'conflict-metadata': (guild: CombinedGuild, changesType: AutoVerifierChangesType, oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => void;
'conflict-channels': (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Channel>) => void; 'conflict-channels': (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Channel>) => void;
'conflict-members': (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Member>) => void; 'conflict-members': (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Member>) => void;

View File

@ -596,10 +596,11 @@ export default class DB {
return result; return result;
} }
static async revokeToken(token: string): Promise<void> { static async revokeToken(guildId: string, token: string): Promise<any> {
const result = await db.query('DELETE FROM "tokens" WHERE "token"=$1', [ token ]); const result = await db.query('DELETE FROM "tokens" WHERE "guild_id"=$1 AND "token"=$2 RETURNING *', [ guildId, token ]);
if (result.rowCount != 1) { if (result.rows.length != 1) {
throw new Error('unable to remove token'); throw new Error('unable to remove token');
} }
return result.rows[0];
} }
} }

View File

@ -62,6 +62,10 @@ class SignatureError extends Error {
} }
} }
function guildPrivilegeName(guildId: string, privilege: string) {
return guildId + '&' + privilege;
}
function bindEvent( function bindEvent(
client: socketio.Socket, client: socketio.Socket,
identity: IIdentity | null, identity: IIdentity | null,
@ -283,9 +287,10 @@ function bindChallengeVerificationEvents(io: socketio.Server, client: socketio.S
throw new EventError('invalid signature'); throw new EventError('invalid signature');
} }
let member: any;
try { try {
await DB.setMemberStatus(identity.guildId, identity.memberId, 'online'); await DB.setMemberStatus(identity.guildId, identity.memberId, 'online');
const member = await DB.getMember(identity.guildId, identity.memberId); member = await DB.getMember(identity.guildId, identity.memberId);
io.to(identity.guildId).emit('update-member', member); io.to(identity.guildId).emit('update-member', member);
} catch (e: unknown) { } catch (e: unknown) {
LOG.warn('unable to set status for m#' + identity.memberId, e); LOG.warn('unable to set status for m#' + identity.memberId, e);
@ -293,9 +298,13 @@ function bindChallengeVerificationEvents(io: socketio.Server, client: socketio.S
} }
identity.verified = true; identity.verified = true;
client.join(identity.guildId); // join the socket.io guild room const rooms = [ identity.guildId ].concat(
member.privileges.split(',').map((privilege: string) => guildPrivilegeName(identity.guildId as string, privilege))
);
LOG.debug(`c#${client.id} joining ${rooms.join(', ')}`);
client.join(rooms);
LOG.info(`c#${client.id}: verified as g#${identity.guildId} u#${identity.memberId}`); LOG.info(`c#${client.id}: verified as g#${identity.guildId} u#${identity.memberId}`);
respond(null, identity.memberId); respond(null, identity.memberId);
} }
); );
@ -459,8 +468,12 @@ function bindAdminEvents(io: socketio.Server, client: socketio.Socket, identity:
throw new EventError('token already taken'); throw new EventError('token already taken');
} }
LOG.debug(`u#${identity.memberId}: revoking t#${token}`); LOG.debug(`u#${identity.memberId}: revoking t#${token}`);
await DB.revokeToken(token); const revokedToken = await DB.revokeToken(identity.guildId, token);
respond(null); respond(null);
const targetRoom = guildPrivilegeName(identity.guildId as string, 'modify_members');
LOG.debug('emitting revoke token to members in ' + targetRoom);
io.in(targetRoom).emit('revoke-token', revokedToken);
} }
); );
} }