mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 21:19:13 +00:00

* Add logic to enable generic hook expiry * Add storage for hook expiry warnings. * Migrate generic hooks / add expiry field * Allow reporting a specific error and status code for generic webhooks * Report the specific error when a message fails to send * Refactor input class to better support datetime * Remove single use of innerChild * Add UI support for expiry configuration * Add new packages * Add warnings when the timer is about to expire. * Add send expiry notice config option * lint * document new option s * Fixup test * Add tests for expiry * Add textual command for setting a duration on a webhook. * Add e2e test for inbound hooks. * changelog * Add a configuration option to force webhooks to expire. * update config.sample.yml * fix field not working
206 lines
8.7 KiB
TypeScript
206 lines
8.7 KiB
TypeScript
import { ComponentChild, FunctionComponent } from "preact";
|
|
import { useCallback, useContext, useEffect, useReducer, useState } from "preact/hooks"
|
|
import { BridgeAPIError } from "../../BridgeAPI";
|
|
import { ListItem, Card } from "../elements";
|
|
import style from "./RoomConfig.module.scss";
|
|
import { GetConnectionsResponseItem } from "../../../src/provisioning/api";
|
|
import { IConnectionState } from "../../../src/Connections";
|
|
import { LoadingSpinner } from '../elements/LoadingSpinner';
|
|
import { ErrCode } from "../../../src/api";
|
|
import { retry } from "../../../src/PromiseUtil";
|
|
import { Alert } from "@vector-im/compound-web";
|
|
import { BridgeContext } from "../../context";
|
|
|
|
export interface ConnectionConfigurationProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> {
|
|
serviceConfig: SConfig;
|
|
loginLabel?: string;
|
|
showAuthPrompt?: boolean;
|
|
onSave: (newConfig: ConnectionState) => void,
|
|
isUpdating: boolean,
|
|
isMigrationCandidate?: boolean,
|
|
existingConnection?: ConnectionType;
|
|
onRemove?: () => void,
|
|
}
|
|
|
|
export interface IRoomConfigText {
|
|
header: string;
|
|
login?: string;
|
|
createNew: string;
|
|
listCanEdit: string;
|
|
listCantEdit: string;
|
|
}
|
|
|
|
interface IRoomConfigProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> {
|
|
roomId: string;
|
|
type: string;
|
|
showAuthPrompt?: boolean;
|
|
showHeader: boolean;
|
|
darkHeaderImg?: boolean;
|
|
headerImg: string;
|
|
text: IRoomConfigText;
|
|
connectionEventType: string;
|
|
listItemName: (c: ConnectionType) => ComponentChild,
|
|
connectionConfigComponent: FunctionComponent<ConnectionConfigurationProps<SConfig, ConnectionType, ConnectionState>>;
|
|
}
|
|
|
|
const MAX_CONNECTION_FETCH_ATTEMPTS = 10;
|
|
|
|
export const RoomConfig = function<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState>(props: IRoomConfigProps<SConfig, ConnectionType, ConnectionState>) {
|
|
const {
|
|
roomId,
|
|
type,
|
|
showAuthPrompt = false,
|
|
darkHeaderImg,
|
|
headerImg,
|
|
showHeader,
|
|
text,
|
|
listItemName,
|
|
connectionEventType,
|
|
} = props;
|
|
const api = useContext(BridgeContext).bridgeApi;
|
|
const ConnectionConfigComponent = props.connectionConfigComponent;
|
|
const [ error, setError ] = useState<null|{header?: string, message: string, isWarning?: boolean, forPrevious?: boolean}>(null);
|
|
const [ connections, setConnections ] = useState<ConnectionType[]|null>(null);
|
|
const [ serviceConfig, setServiceConfig ] = useState<SConfig|null>(null);
|
|
const [ canEditRoom, setCanEditRoom ] = useState<boolean>(false);
|
|
// We need to increment this every time we create a connection in order to properly reset the state.
|
|
const [ newConnectionKey, incrementConnectionKey ] = useReducer<number, undefined>(n => n+1, 0);
|
|
const [ updatingConnection, isUpdatingConnection ] = useState<boolean>(false);
|
|
|
|
const clearCurrentError = () => {
|
|
setError(error => error?.forPrevious ? error : null);
|
|
}
|
|
|
|
useEffect(() => {
|
|
const fetchConnections = retry(
|
|
() => {
|
|
return api.getConnectionsForService<ConnectionType>(roomId, type);
|
|
},
|
|
MAX_CONNECTION_FETCH_ATTEMPTS,
|
|
1000,
|
|
(ex) => ex instanceof BridgeAPIError && ex.errcode === ErrCode.NotInRoom
|
|
);
|
|
|
|
fetchConnections.then((res) => {
|
|
setCanEditRoom(res.canEdit);
|
|
setConnections(res.connections);
|
|
clearCurrentError();
|
|
}).catch(ex => {
|
|
setError({
|
|
header: "Failed to fetch existing connections",
|
|
message: ex instanceof BridgeAPIError ? ex.message : "Unknown error"
|
|
});
|
|
})
|
|
}, [api, roomId, type, newConnectionKey]);
|
|
|
|
const canSendMessages = connections?.every(c => c.canSendMessages) ?? true;
|
|
|
|
useEffect(() => {
|
|
api.getServiceConfig<SConfig>(type)
|
|
.then(setServiceConfig)
|
|
.then(clearCurrentError)
|
|
.catch(ex => {
|
|
console.warn("Failed to fetch service config", ex);
|
|
setError({
|
|
header: "Failed to fetch service config",
|
|
message: ex instanceof BridgeAPIError ? ex.message : "Unknown error"
|
|
});
|
|
})
|
|
}, [api, type]);
|
|
|
|
const handleSaveOnCreation = useCallback((config: ConnectionState) => {
|
|
isUpdatingConnection(true);
|
|
api.createConnection(roomId, connectionEventType, config).then(result => {
|
|
// Force reload
|
|
incrementConnectionKey(undefined);
|
|
setError(!result.warning ? null : {
|
|
header: result.warning.header,
|
|
message: result.warning.message,
|
|
isWarning: true,
|
|
forPrevious: true,
|
|
});
|
|
}).catch(ex => {
|
|
console.warn("Failed to create connection", ex);
|
|
setError({
|
|
header: "Failed to create connection",
|
|
message: ex instanceof BridgeAPIError ? ex.message : "Unknown error"
|
|
});
|
|
}).finally(() => {
|
|
isUpdatingConnection(false);
|
|
});
|
|
}, [api, roomId, connectionEventType]);
|
|
|
|
return <Card>
|
|
<main>
|
|
{ showHeader &&
|
|
<header className={style.header}>
|
|
<img alt="" className={darkHeaderImg ? style.invert : undefined} src={headerImg} />
|
|
<h1>{text.header}</h1>
|
|
</header>
|
|
}
|
|
{
|
|
error &&
|
|
<Alert type="critical" text={error.header || error.isWarning ? "Warning" : "Error"}>{error.message}</Alert>
|
|
}
|
|
{ !canSendMessages && canEditRoom &&
|
|
<Alert type="info" title={"Misconfigured permissions"}>
|
|
This room does not permit the bot to send messages.
|
|
Please go to the room settings in your client and adjust permissions.
|
|
</Alert>
|
|
}
|
|
{ canEditRoom && <section>
|
|
<h2>{text.createNew}</h2>
|
|
{serviceConfig && <ConnectionConfigComponent
|
|
key={newConnectionKey}
|
|
serviceConfig={serviceConfig}
|
|
onSave={handleSaveOnCreation}
|
|
loginLabel={text.login}
|
|
showAuthPrompt={showAuthPrompt}
|
|
isUpdating={updatingConnection}
|
|
/>}
|
|
</section>}
|
|
{ !error && connections === null && <LoadingSpinner /> }
|
|
{ !!connections?.length && <section>
|
|
<h2>{ canEditRoom ? text.listCanEdit : text.listCantEdit }</h2>
|
|
{ serviceConfig && connections?.map(c => <ListItem key={c.id} text={listItemName(c)}>
|
|
<ConnectionConfigComponent
|
|
serviceConfig={serviceConfig}
|
|
existingConnection={c}
|
|
onSave={(config) => {
|
|
isUpdatingConnection(true);
|
|
api.updateConnection(roomId, c.id, config).then(() => {
|
|
c.config = config;
|
|
// Force reload
|
|
incrementConnectionKey(undefined);
|
|
setError(null);
|
|
}).catch(ex => {
|
|
console.warn("Failed to create connection", ex);
|
|
setError({
|
|
header: "Failed to create connection",
|
|
message: ex instanceof BridgeAPIError ? ex.message : "Unknown error"
|
|
});
|
|
}).finally(() => {
|
|
isUpdatingConnection(false);
|
|
});
|
|
}}
|
|
onRemove={() => {
|
|
api.removeConnection(roomId, c.id).then(() => {
|
|
setConnections(conns => conns?.filter(conn => c.id !== conn.id) || []);
|
|
setError(null);
|
|
}).catch(ex => {
|
|
console.warn("Failed to remove connection", ex);
|
|
setError({
|
|
header: "Failed to remove connection",
|
|
message: ex instanceof BridgeAPIError ? ex.message : "Unknown error"
|
|
});
|
|
});
|
|
}}
|
|
isUpdating={updatingConnection}
|
|
/>
|
|
</ListItem>)
|
|
}
|
|
</section>}
|
|
</main>
|
|
</Card>;
|
|
};
|