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

View File

@ -72,7 +72,10 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
</div>
<div className="actions">
<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>
);

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 CombinedGuild from "../../guild-combined";
import ChoicesControl from "../components/control-choices";

View File

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

View File

@ -123,6 +123,40 @@ export default class CombinedGuild extends EventEmitter<Connectable & Conflictab
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 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);
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() {

View File

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

View File

@ -40,6 +40,12 @@ export default class GuildsManager extends EventEmitter<{
'update-messages': (guild: CombinedGuild, updatedMessages: 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-channels': (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Channel>) => void;
'conflict-members': (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Member>) => void;

View File

@ -596,10 +596,11 @@ export default class DB {
return result;
}
static async revokeToken(token: string): Promise<void> {
const result = await db.query('DELETE FROM "tokens" WHERE "token"=$1', [ token ]);
if (result.rowCount != 1) {
static async revokeToken(guildId: string, token: string): Promise<any> {
const result = await db.query('DELETE FROM "tokens" WHERE "guild_id"=$1 AND "token"=$2 RETURNING *', [ guildId, token ]);
if (result.rows.length != 1) {
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(
client: socketio.Socket,
identity: IIdentity | null,
@ -283,9 +287,10 @@ function bindChallengeVerificationEvents(io: socketio.Server, client: socketio.S
throw new EventError('invalid signature');
}
let member: any;
try {
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);
} catch (e: unknown) {
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;
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}`);
respond(null, identity.memberId);
}
);
@ -459,8 +468,12 @@ function bindAdminEvents(io: socketio.Server, client: socketio.Socket, identity:
throw new EventError('token already taken');
}
LOG.debug(`u#${identity.memberId}: revoking t#${token}`);
await DB.revokeToken(token);
const revokedToken = await DB.revokeToken(identity.guildId, token);
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);
}
);
}