mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 13:17:08 +00:00
Convert bridgeAPI usages to preact context. (#871)
* Fix widget client only talking to localhost * Improve error text around widget communication. * changelog * Remove unused. * Simplify code by using a context for bridge API.
This commit is contained in:
parent
caaabbc300
commit
6d3800a018
1
changelog.d/870.bugfix
Normal file
1
changelog.d/870.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fix widgets failing with "Request timed out".
|
41
web/App.tsx
41
web/App.tsx
@ -7,6 +7,7 @@ import { LoadingSpinner } from './components/elements/LoadingSpinner';
|
||||
import AdminSettings from './components/AdminSettings';
|
||||
import RoomConfigView from './components/RoomConfigView';
|
||||
import { Alert } from '@vector-im/compound-web';
|
||||
import { BridgeContext } from './context';
|
||||
|
||||
interface IMinimalState {
|
||||
error: string|null,
|
||||
@ -115,43 +116,39 @@ export default class App extends Component<void, IState> {
|
||||
}
|
||||
|
||||
render() {
|
||||
// Return the App component.
|
||||
let content;
|
||||
const style = {
|
||||
padding: 'embedType' in this.state && this.state.embedType === EmbedType.IntegrationManager ? "0" : "16px",
|
||||
};
|
||||
if (this.state.error) {
|
||||
content = <Alert type="critical" title="An error occured">{this.state.error}</Alert>;
|
||||
return <div style={style}><Alert type="critical" title="An error occured">{this.state.error}</Alert></div>;
|
||||
} else if (this.state.busy) {
|
||||
content = <LoadingSpinner />;
|
||||
return <div style={style}><LoadingSpinner /></div>;
|
||||
} else if ("kind" in this.state === false) {
|
||||
console.warn("invalid state", this.state);
|
||||
return <div style={style}><Alert type="critical" title="An error occured">Widget got into an invalid state.</Alert></div>;
|
||||
}
|
||||
|
||||
if ("kind" in this.state) {
|
||||
if (this.state.roomState && this.state.kind === "admin") {
|
||||
content = <AdminSettings bridgeApi={this.state.bridgeApi} roomState={this.state.roomState} />;
|
||||
} else if (this.state.kind === "invite") {
|
||||
// Fall through for now, we don't support invite widgets *just* yet.
|
||||
// Return the App component.
|
||||
let content;
|
||||
|
||||
if (this.state.kind === "admin") {
|
||||
content = <AdminSettings roomState={this.state.roomState} />;
|
||||
}else if (this.state.kind === "roomConfig") {
|
||||
content = <RoomConfigView
|
||||
roomId={this.state.roomId}
|
||||
supportedServices={this.state.supportedServices}
|
||||
serviceScope={this.state.serviceScope}
|
||||
embedType={this.state.embedType}
|
||||
bridgeApi={this.state.bridgeApi}
|
||||
widgetApi={this.state.widgetApi}
|
||||
/>;
|
||||
} else {
|
||||
return <div style={style}><Alert type="critical" title="An error occured">Unknown widget kind.</Alert></div>;
|
||||
}
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
console.warn("invalid state", this.state);
|
||||
content = <b>Invalid state</b>;
|
||||
}
|
||||
|
||||
const embedType = 'embedType' in this.state ? this.state.embedType : EmbedType.Default;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
padding: embedType === "integration-manager" ? "0" : "16px",
|
||||
}}>
|
||||
<div style={style}>
|
||||
<BridgeContext.Provider value={{bridgeApi: this.state.bridgeApi}}>
|
||||
{content}
|
||||
</BridgeContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -171,7 +171,6 @@ export enum EmbedType {
|
||||
}
|
||||
|
||||
export type BridgeConfig = FunctionComponent<{
|
||||
api: BridgeAPI,
|
||||
roomId: string,
|
||||
showHeader: boolean,
|
||||
}>;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { WidgetApi } from "matrix-widget-api";
|
||||
import { useState } from "preact/hooks"
|
||||
import { BridgeAPI, BridgeConfig, EmbedType } from "../BridgeAPI";
|
||||
import { BridgeConfig, EmbedType } from "../BridgeAPI";
|
||||
import style from "./RoomConfigView.module.scss";
|
||||
import { ConnectionCard } from "./ConnectionCard";
|
||||
import { FeedsConfig } from "./roomConfig/FeedsConfig";
|
||||
@ -17,8 +16,6 @@ import WebhookIcon from "../icons/webhook.png";
|
||||
|
||||
|
||||
interface IProps {
|
||||
widgetApi: WidgetApi,
|
||||
bridgeApi: BridgeAPI,
|
||||
supportedServices: {[service: string]: boolean},
|
||||
serviceScope?: string,
|
||||
embedType: EmbedType,
|
||||
@ -86,7 +83,6 @@ export default function RoomConfigView(props: IProps) {
|
||||
const ConfigComponent = connections[activeConnectionType].component;
|
||||
content = <ConfigComponent
|
||||
roomId={props.roomId}
|
||||
api={props.bridgeApi}
|
||||
showHeader={props.embedType !== EmbedType.IntegrationManager}
|
||||
/>;
|
||||
} else {
|
||||
@ -109,6 +105,7 @@ export default function RoomConfigView(props: IProps) {
|
||||
}
|
||||
|
||||
return <div className={style.root}>
|
||||
|
||||
{!serviceScope && activeConnectionType &&
|
||||
<header>
|
||||
<span className={style.backButton} onClick={() => setActiveConnectionType(null)}>
|
||||
|
@ -1,24 +1,23 @@
|
||||
import { useCallback, useEffect, useState } from "preact/hooks";
|
||||
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
||||
import { GetAuthResponse } from "../../../src/Widgets/BridgeWidgetInterface";
|
||||
import { BridgeAPI } from "../../BridgeAPI";
|
||||
import { Button } from "../elements";
|
||||
import { BridgeContext } from "../../context";
|
||||
|
||||
const PollAuthEveryMs = 3000;
|
||||
|
||||
|
||||
export const ServiceAuth = ({
|
||||
api,
|
||||
service,
|
||||
loginLabel = "Log in",
|
||||
authState,
|
||||
onAuthSucceeded,
|
||||
}: {
|
||||
api: BridgeAPI,
|
||||
service: string,
|
||||
authState: GetAuthResponse,
|
||||
onAuthSucceeded: () => void,
|
||||
loginLabel?: string,
|
||||
}) => {
|
||||
const api = useContext(BridgeContext).bridgeApi;
|
||||
const [pollStateId, setPollStateId] = useState<string|null>();
|
||||
|
||||
const pollAuth = useCallback(async (pollId) => {
|
||||
|
@ -92,12 +92,11 @@ const roomConfigText: IRoomConfigText = {
|
||||
|
||||
const RoomConfigListItemFunc = (c: FeedResponseItem) => c.config.label || c.config.url;
|
||||
|
||||
export const FeedsConfig: BridgeConfig = ({ api, roomId, showHeader }) => {
|
||||
export const FeedsConfig: BridgeConfig = ({ roomId, showHeader }) => {
|
||||
|
||||
return <RoomConfig<ServiceConfig, FeedResponseItem, FeedConnectionState>
|
||||
headerImg={FeedsIcon}
|
||||
showHeader={showHeader}
|
||||
api={api}
|
||||
roomId={roomId}
|
||||
type="feeds"
|
||||
connectionEventType="uk.half-shot.matrix-hookshot.feed"
|
||||
|
@ -111,12 +111,11 @@ const RoomConfigText = {
|
||||
|
||||
const RoomConfigListItemFunc = (c: GenericHookResponseItem) => c.config.name;
|
||||
|
||||
export const GenericWebhookConfig: BridgeConfig = ({ api, roomId, showHeader }) => {
|
||||
export const GenericWebhookConfig: BridgeConfig = ({ roomId, showHeader }) => {
|
||||
return <RoomConfig<ServiceConfig, GenericHookResponseItem, GenericHookConnectionState>
|
||||
headerImg={WebhookIcon}
|
||||
darkHeaderImg={true}
|
||||
showHeader={showHeader}
|
||||
api={api}
|
||||
roomId={roomId}
|
||||
type="generic"
|
||||
connectionEventType="uk.half-shot.matrix-hookshot.generic.hook"
|
||||
|
@ -5,11 +5,12 @@ import { EventHookCheckbox } from '../elements/EventHookCheckbox';
|
||||
import { FunctionComponent, createRef } from "preact";
|
||||
import { GitHubRepoConnectionState, GitHubRepoResponseItem, GitHubRepoConnectionRepoTarget, GitHubRepoConnectionOrgTarget } from "../../../src/Connections/GithubRepo";
|
||||
import { InputField, ButtonSet, Button } from "../elements";
|
||||
import { useState, useCallback, useMemo, useEffect } from "preact/hooks";
|
||||
import { useState, useCallback, useMemo, useEffect, useContext } from "preact/hooks";
|
||||
import { DropItem } from "../elements/DropdownSearch";
|
||||
import ConnectionSearch from "../elements/ConnectionSearch";
|
||||
import { ServiceAuth } from "./Auth";
|
||||
import { GetAuthResponse } from "../../../src/Widgets/BridgeWidgetInterface";
|
||||
import { BridgeContext } from "../../context";
|
||||
|
||||
const EventType = "uk.half-shot.matrix-hookshot.github.repository";
|
||||
|
||||
@ -18,11 +19,12 @@ function getRepoFullName(state: GitHubRepoConnectionState) {
|
||||
}
|
||||
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitHubRepoResponseItem, GitHubRepoConnectionState>> = ({
|
||||
showAuthPrompt, loginLabel, serviceConfig, api, existingConnection, onSave, onRemove, isUpdating
|
||||
showAuthPrompt, loginLabel, serviceConfig, existingConnection, onSave, onRemove, isUpdating
|
||||
}) => {
|
||||
// Assume true if we have no auth prompt.
|
||||
const [authedResponse, setAuthResponse] = useState<GetAuthResponse|null>(null);
|
||||
const [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []);
|
||||
const api = useContext(BridgeContext).bridgeApi;
|
||||
|
||||
const checkAuth = useCallback(() => {
|
||||
api.getAuth("github").then((res) => {
|
||||
@ -106,7 +108,7 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
||||
const consideredAuthenticated = (authedResponse?.authenticated || !showAuthPrompt);
|
||||
|
||||
return <form onSubmit={handleSave}>
|
||||
{authedResponse && <ServiceAuth onAuthSucceeded={checkAuth} authState={authedResponse} service="github" loginLabel={loginLabel} api={api} />}
|
||||
{authedResponse && <ServiceAuth onAuthSucceeded={checkAuth} authState={authedResponse} service="github" loginLabel={loginLabel} />}
|
||||
{!existingConnection && consideredAuthenticated && <ConnectionSearch
|
||||
serviceName="GitHub"
|
||||
addNewInstanceUrl={newInstallationUrl}
|
||||
@ -175,12 +177,11 @@ const roomConfigText: IRoomConfigText = {
|
||||
|
||||
const RoomConfigListItemFunc = (c: GitHubRepoResponseItem) => getRepoFullName(c.config);
|
||||
|
||||
export const GithubRepoConfig: BridgeConfig = ({ api, roomId, showHeader }) => {
|
||||
export const GithubRepoConfig: BridgeConfig = ({ roomId, showHeader }) => {
|
||||
return <RoomConfig<never, GitHubRepoResponseItem, GitHubRepoConnectionState>
|
||||
headerImg={GitHubIcon}
|
||||
darkHeaderImg={true}
|
||||
showHeader={showHeader}
|
||||
api={api}
|
||||
roomId={roomId}
|
||||
type="github"
|
||||
showAuthPrompt={true}
|
||||
|
@ -5,13 +5,15 @@ import { EventHookCheckbox } from '../elements/EventHookCheckbox';
|
||||
import { GitLabRepoConnectionState, GitLabRepoResponseItem, GitLabRepoConnectionProjectTarget, GitLabRepoConnectionInstanceTarget } from "../../../src/Connections/GitlabRepo";
|
||||
import { InputField, ButtonSet, Button } from "../elements";
|
||||
import { FunctionComponent, createRef } from "preact";
|
||||
import { useState, useCallback, useMemo } from "preact/hooks";
|
||||
import { useState, useCallback, useMemo, useContext } from "preact/hooks";
|
||||
import { DropItem } from "../elements/DropdownSearch";
|
||||
import { ConnectionSearch } from "../elements/ConnectionSearch";
|
||||
import { BridgeContext } from "../../context";
|
||||
|
||||
const EventType = "uk.half-shot.matrix-hookshot.gitlab.repository";
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitLabRepoResponseItem, GitLabRepoConnectionState>> = ({api, existingConnection, onSave, onRemove, isUpdating }) => {
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitLabRepoResponseItem, GitLabRepoConnectionState>> = ({existingConnection, onSave, onRemove, isUpdating }) => {
|
||||
const [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []);
|
||||
const api = useContext(BridgeContext).bridgeApi;
|
||||
|
||||
const toggleEnabledHook = useCallback((evt: any) => {
|
||||
const key = (evt.target as HTMLElement).getAttribute('x-event-name');
|
||||
@ -123,11 +125,10 @@ const RoomConfigText = {
|
||||
|
||||
const RoomConfigListItemFunc = (c: GitLabRepoResponseItem) => c.config.path;
|
||||
|
||||
export const GitlabRepoConfig: BridgeConfig = ({ api, roomId, showHeader }) => {
|
||||
export const GitlabRepoConfig: BridgeConfig = ({ roomId, showHeader }) => {
|
||||
return <RoomConfig<never, GitLabRepoResponseItem, GitLabRepoConnectionState>
|
||||
headerImg={GitLabIcon}
|
||||
showHeader={showHeader}
|
||||
api={api}
|
||||
roomId={roomId}
|
||||
type="gitlab"
|
||||
text={RoomConfigText}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FunctionComponent, createRef } from "preact";
|
||||
import { useState, useCallback, useMemo } from "preact/hooks";
|
||||
import { useState, useCallback, useMemo, useContext } from "preact/hooks";
|
||||
import { BridgeConfig } from "../../BridgeAPI";
|
||||
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig";
|
||||
import { JiraProjectConnectionState, JiraProjectResponseItem, JiraProjectConnectionProjectTarget, JiraProjectConnectionInstanceTarget } from "../../../src/Connections/JiraProject";
|
||||
@ -8,11 +8,13 @@ import { EventHookCheckbox } from '../elements/EventHookCheckbox';
|
||||
import JiraIcon from "../../icons/jira.png";
|
||||
import ConnectionSearch from "../elements/ConnectionSearch";
|
||||
import { DropItem } from "../elements/DropdownSearch";
|
||||
import { BridgeContext } from "../../context";
|
||||
|
||||
const EventType = "uk.half-shot.matrix-hookshot.jira.project";
|
||||
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, JiraProjectResponseItem, JiraProjectConnectionState>> = ({api, existingConnection, onSave, onRemove, isUpdating }) => {
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, JiraProjectResponseItem, JiraProjectConnectionState>> = ({existingConnection, onSave, onRemove, isUpdating }) => {
|
||||
const [allowedEvents, setAllowedEvents] = useState<string[]>(existingConnection?.config.events || ['issue_created']);
|
||||
const api = useContext(BridgeContext).bridgeApi;
|
||||
|
||||
const toggleEvent = useCallback((evt: Event) => {
|
||||
const key = (evt.target as HTMLElement).getAttribute('x-event-name');
|
||||
@ -108,11 +110,10 @@ const RoomConfigText = {
|
||||
|
||||
const RoomConfigListItemFunc = (c: JiraProjectResponseItem) => c.config.url;
|
||||
|
||||
export const JiraProjectConfig: BridgeConfig = ({ api, roomId, showHeader }) => {
|
||||
export const JiraProjectConfig: BridgeConfig = ({ roomId, showHeader }) => {
|
||||
return <RoomConfig<never, JiraProjectResponseItem, JiraProjectConnectionState>
|
||||
headerImg={JiraIcon}
|
||||
showHeader={showHeader}
|
||||
api={api}
|
||||
roomId={roomId}
|
||||
type="jira"
|
||||
text={RoomConfigText}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FunctionComponent } from "preact";
|
||||
import { useCallback, useEffect, useReducer, useState } from "preact/hooks"
|
||||
import { BridgeAPI, BridgeAPIError } from "../../BridgeAPI";
|
||||
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";
|
||||
@ -9,6 +9,7 @@ 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;
|
||||
@ -19,7 +20,6 @@ export interface ConnectionConfigurationProps<SConfig, ConnectionType extends Ge
|
||||
isMigrationCandidate?: boolean,
|
||||
existingConnection?: ConnectionType;
|
||||
onRemove?: () => void,
|
||||
api: BridgeAPI;
|
||||
}
|
||||
|
||||
export interface IRoomConfigText {
|
||||
@ -31,7 +31,6 @@ export interface IRoomConfigText {
|
||||
}
|
||||
|
||||
interface IRoomConfigProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> {
|
||||
api: BridgeAPI;
|
||||
roomId: string;
|
||||
type: string;
|
||||
showAuthPrompt?: boolean;
|
||||
@ -48,7 +47,6 @@ const MAX_CONNECTION_FETCH_ATTEMPTS = 10;
|
||||
|
||||
export const RoomConfig = function<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState>(props: IRoomConfigProps<SConfig, ConnectionType, ConnectionState>) {
|
||||
const {
|
||||
api,
|
||||
roomId,
|
||||
type,
|
||||
showAuthPrompt = false,
|
||||
@ -59,6 +57,7 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
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);
|
||||
@ -153,7 +152,6 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
<h2>{text.createNew}</h2>
|
||||
{serviceConfig && <ConnectionConfigComponent
|
||||
key={newConnectionKey}
|
||||
api={api}
|
||||
serviceConfig={serviceConfig}
|
||||
onSave={handleSaveOnCreation}
|
||||
loginLabel={text.login}
|
||||
@ -166,7 +164,6 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
<h2>{ canEditRoom ? text.listCanEdit : text.listCantEdit }</h2>
|
||||
{ serviceConfig && connections?.map(c => <ListItem key={c.id} text={listItemName(c)}>
|
||||
<ConnectionConfigComponent
|
||||
api={api}
|
||||
serviceConfig={serviceConfig}
|
||||
existingConnection={c}
|
||||
onSave={(config) => {
|
||||
|
15
web/context.ts
Normal file
15
web/context.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { createContext } from "preact";
|
||||
import type { BridgeAPI } from "./BridgeAPI";
|
||||
|
||||
interface IBridgeContext {
|
||||
bridgeApi: BridgeAPI;
|
||||
}
|
||||
|
||||
const fakeBridgeContext = {
|
||||
get bridgeApi(): BridgeAPI {
|
||||
throw Error('No context provided');
|
||||
}
|
||||
}
|
||||
|
||||
export const BridgeContext = createContext<IBridgeContext>(fakeBridgeContext);
|
||||
|
Loading…
x
Reference in New Issue
Block a user