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:
Will Hunt 2024-01-16 09:44:51 +00:00 committed by GitHub
parent caaabbc300
commit 6d3800a018
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 71 additions and 65 deletions

1
changelog.d/870.bugfix Normal file
View File

@ -0,0 +1 @@
Fix widgets failing with "Request timed out".

View File

@ -7,6 +7,7 @@ import { LoadingSpinner } from './components/elements/LoadingSpinner';
import AdminSettings from './components/AdminSettings'; import AdminSettings from './components/AdminSettings';
import RoomConfigView from './components/RoomConfigView'; import RoomConfigView from './components/RoomConfigView';
import { Alert } from '@vector-im/compound-web'; import { Alert } from '@vector-im/compound-web';
import { BridgeContext } from './context';
interface IMinimalState { interface IMinimalState {
error: string|null, error: string|null,
@ -115,43 +116,39 @@ export default class App extends Component<void, IState> {
} }
render() { render() {
const style = {
padding: 'embedType' in this.state && this.state.embedType === EmbedType.IntegrationManager ? "0" : "16px",
};
if (this.state.error) {
return <div style={style}><Alert type="critical" title="An error occured">{this.state.error}</Alert></div>;
} else if (this.state.busy) {
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>;
}
// Return the App component. // Return the App component.
let content; let content;
if (this.state.error) {
content = <Alert type="critical" title="An error occured">{this.state.error}</Alert>;
} else if (this.state.busy) {
content = <LoadingSpinner />;
}
if ("kind" in this.state) { if (this.state.kind === "admin") {
if (this.state.roomState && this.state.kind === "admin") { content = <AdminSettings roomState={this.state.roomState} />;
content = <AdminSettings bridgeApi={this.state.bridgeApi} roomState={this.state.roomState} />; }else if (this.state.kind === "roomConfig") {
} else if (this.state.kind === "invite") { content = <RoomConfigView
// Fall through for now, we don't support invite widgets *just* yet. roomId={this.state.roomId}
} else if (this.state.kind === "roomConfig") { supportedServices={this.state.supportedServices}
content = <RoomConfigView serviceScope={this.state.serviceScope}
roomId={this.state.roomId} embedType={this.state.embedType}
supportedServices={this.state.supportedServices} />;
serviceScope={this.state.serviceScope} } else {
embedType={this.state.embedType} return <div style={style}><Alert type="critical" title="An error occured">Unknown widget kind.</Alert></div>;
bridgeApi={this.state.bridgeApi}
widgetApi={this.state.widgetApi}
/>;
}
} }
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 ( return (
<div style={{ <div style={style}>
padding: embedType === "integration-manager" ? "0" : "16px", <BridgeContext.Provider value={{bridgeApi: this.state.bridgeApi}}>
}}> {content}
{content} </BridgeContext.Provider>
</div> </div>
); );
} }

View File

@ -171,7 +171,6 @@ export enum EmbedType {
} }
export type BridgeConfig = FunctionComponent<{ export type BridgeConfig = FunctionComponent<{
api: BridgeAPI,
roomId: string, roomId: string,
showHeader: boolean, showHeader: boolean,
}>; }>;

View File

@ -1,6 +1,5 @@
import { WidgetApi } from "matrix-widget-api";
import { useState } from "preact/hooks" import { useState } from "preact/hooks"
import { BridgeAPI, BridgeConfig, EmbedType } from "../BridgeAPI"; import { BridgeConfig, EmbedType } from "../BridgeAPI";
import style from "./RoomConfigView.module.scss"; import style from "./RoomConfigView.module.scss";
import { ConnectionCard } from "./ConnectionCard"; import { ConnectionCard } from "./ConnectionCard";
import { FeedsConfig } from "./roomConfig/FeedsConfig"; import { FeedsConfig } from "./roomConfig/FeedsConfig";
@ -17,8 +16,6 @@ import WebhookIcon from "../icons/webhook.png";
interface IProps { interface IProps {
widgetApi: WidgetApi,
bridgeApi: BridgeAPI,
supportedServices: {[service: string]: boolean}, supportedServices: {[service: string]: boolean},
serviceScope?: string, serviceScope?: string,
embedType: EmbedType, embedType: EmbedType,
@ -86,7 +83,6 @@ export default function RoomConfigView(props: IProps) {
const ConfigComponent = connections[activeConnectionType].component; const ConfigComponent = connections[activeConnectionType].component;
content = <ConfigComponent content = <ConfigComponent
roomId={props.roomId} roomId={props.roomId}
api={props.bridgeApi}
showHeader={props.embedType !== EmbedType.IntegrationManager} showHeader={props.embedType !== EmbedType.IntegrationManager}
/>; />;
} else { } else {
@ -109,6 +105,7 @@ export default function RoomConfigView(props: IProps) {
} }
return <div className={style.root}> return <div className={style.root}>
{!serviceScope && activeConnectionType && {!serviceScope && activeConnectionType &&
<header> <header>
<span className={style.backButton} onClick={() => setActiveConnectionType(null)}> <span className={style.backButton} onClick={() => setActiveConnectionType(null)}>

View File

@ -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 { GetAuthResponse } from "../../../src/Widgets/BridgeWidgetInterface";
import { BridgeAPI } from "../../BridgeAPI";
import { Button } from "../elements"; import { Button } from "../elements";
import { BridgeContext } from "../../context";
const PollAuthEveryMs = 3000; const PollAuthEveryMs = 3000;
export const ServiceAuth = ({ export const ServiceAuth = ({
api,
service, service,
loginLabel = "Log in", loginLabel = "Log in",
authState, authState,
onAuthSucceeded, onAuthSucceeded,
}: { }: {
api: BridgeAPI,
service: string, service: string,
authState: GetAuthResponse, authState: GetAuthResponse,
onAuthSucceeded: () => void, onAuthSucceeded: () => void,
loginLabel?: string, loginLabel?: string,
}) => { }) => {
const api = useContext(BridgeContext).bridgeApi;
const [pollStateId, setPollStateId] = useState<string|null>(); const [pollStateId, setPollStateId] = useState<string|null>();
const pollAuth = useCallback(async (pollId) => { const pollAuth = useCallback(async (pollId) => {

View File

@ -92,12 +92,11 @@ const roomConfigText: IRoomConfigText = {
const RoomConfigListItemFunc = (c: FeedResponseItem) => c.config.label || c.config.url; 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> return <RoomConfig<ServiceConfig, FeedResponseItem, FeedConnectionState>
headerImg={FeedsIcon} headerImg={FeedsIcon}
showHeader={showHeader} showHeader={showHeader}
api={api}
roomId={roomId} roomId={roomId}
type="feeds" type="feeds"
connectionEventType="uk.half-shot.matrix-hookshot.feed" connectionEventType="uk.half-shot.matrix-hookshot.feed"

View File

@ -111,12 +111,11 @@ const RoomConfigText = {
const RoomConfigListItemFunc = (c: GenericHookResponseItem) => c.config.name; 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> return <RoomConfig<ServiceConfig, GenericHookResponseItem, GenericHookConnectionState>
headerImg={WebhookIcon} headerImg={WebhookIcon}
darkHeaderImg={true} darkHeaderImg={true}
showHeader={showHeader} showHeader={showHeader}
api={api}
roomId={roomId} roomId={roomId}
type="generic" type="generic"
connectionEventType="uk.half-shot.matrix-hookshot.generic.hook" connectionEventType="uk.half-shot.matrix-hookshot.generic.hook"

View File

@ -5,11 +5,12 @@ import { EventHookCheckbox } from '../elements/EventHookCheckbox';
import { FunctionComponent, createRef } from "preact"; import { FunctionComponent, createRef } from "preact";
import { GitHubRepoConnectionState, GitHubRepoResponseItem, GitHubRepoConnectionRepoTarget, GitHubRepoConnectionOrgTarget } from "../../../src/Connections/GithubRepo"; import { GitHubRepoConnectionState, GitHubRepoResponseItem, GitHubRepoConnectionRepoTarget, GitHubRepoConnectionOrgTarget } from "../../../src/Connections/GithubRepo";
import { InputField, ButtonSet, Button } from "../elements"; 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 { DropItem } from "../elements/DropdownSearch";
import ConnectionSearch from "../elements/ConnectionSearch"; import ConnectionSearch from "../elements/ConnectionSearch";
import { ServiceAuth } from "./Auth"; import { ServiceAuth } from "./Auth";
import { GetAuthResponse } from "../../../src/Widgets/BridgeWidgetInterface"; import { GetAuthResponse } from "../../../src/Widgets/BridgeWidgetInterface";
import { BridgeContext } from "../../context";
const EventType = "uk.half-shot.matrix-hookshot.github.repository"; const EventType = "uk.half-shot.matrix-hookshot.github.repository";
@ -18,11 +19,12 @@ function getRepoFullName(state: GitHubRepoConnectionState) {
} }
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitHubRepoResponseItem, 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. // Assume true if we have no auth prompt.
const [authedResponse, setAuthResponse] = useState<GetAuthResponse|null>(null); const [authedResponse, setAuthResponse] = useState<GetAuthResponse|null>(null);
const [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []); const [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []);
const api = useContext(BridgeContext).bridgeApi;
const checkAuth = useCallback(() => { const checkAuth = useCallback(() => {
api.getAuth("github").then((res) => { api.getAuth("github").then((res) => {
@ -106,7 +108,7 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
const consideredAuthenticated = (authedResponse?.authenticated || !showAuthPrompt); const consideredAuthenticated = (authedResponse?.authenticated || !showAuthPrompt);
return <form onSubmit={handleSave}> 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 {!existingConnection && consideredAuthenticated && <ConnectionSearch
serviceName="GitHub" serviceName="GitHub"
addNewInstanceUrl={newInstallationUrl} addNewInstanceUrl={newInstallationUrl}
@ -175,12 +177,11 @@ const roomConfigText: IRoomConfigText = {
const RoomConfigListItemFunc = (c: GitHubRepoResponseItem) => getRepoFullName(c.config); 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> return <RoomConfig<never, GitHubRepoResponseItem, GitHubRepoConnectionState>
headerImg={GitHubIcon} headerImg={GitHubIcon}
darkHeaderImg={true} darkHeaderImg={true}
showHeader={showHeader} showHeader={showHeader}
api={api}
roomId={roomId} roomId={roomId}
type="github" type="github"
showAuthPrompt={true} showAuthPrompt={true}

View File

@ -5,13 +5,15 @@ import { EventHookCheckbox } from '../elements/EventHookCheckbox';
import { GitLabRepoConnectionState, GitLabRepoResponseItem, GitLabRepoConnectionProjectTarget, GitLabRepoConnectionInstanceTarget } from "../../../src/Connections/GitlabRepo"; import { GitLabRepoConnectionState, GitLabRepoResponseItem, GitLabRepoConnectionProjectTarget, GitLabRepoConnectionInstanceTarget } from "../../../src/Connections/GitlabRepo";
import { InputField, ButtonSet, Button } from "../elements"; import { InputField, ButtonSet, Button } from "../elements";
import { FunctionComponent, createRef } from "preact"; 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 { DropItem } from "../elements/DropdownSearch";
import { ConnectionSearch } from "../elements/ConnectionSearch"; import { ConnectionSearch } from "../elements/ConnectionSearch";
import { BridgeContext } from "../../context";
const EventType = "uk.half-shot.matrix-hookshot.gitlab.repository"; 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 [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []);
const api = useContext(BridgeContext).bridgeApi;
const toggleEnabledHook = useCallback((evt: any) => { const toggleEnabledHook = useCallback((evt: any) => {
const key = (evt.target as HTMLElement).getAttribute('x-event-name'); const key = (evt.target as HTMLElement).getAttribute('x-event-name');
@ -123,11 +125,10 @@ const RoomConfigText = {
const RoomConfigListItemFunc = (c: GitLabRepoResponseItem) => c.config.path; 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> return <RoomConfig<never, GitLabRepoResponseItem, GitLabRepoConnectionState>
headerImg={GitLabIcon} headerImg={GitLabIcon}
showHeader={showHeader} showHeader={showHeader}
api={api}
roomId={roomId} roomId={roomId}
type="gitlab" type="gitlab"
text={RoomConfigText} text={RoomConfigText}

View File

@ -1,5 +1,5 @@
import { FunctionComponent, createRef } from "preact"; 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 { BridgeConfig } from "../../BridgeAPI";
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig"; import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig";
import { JiraProjectConnectionState, JiraProjectResponseItem, JiraProjectConnectionProjectTarget, JiraProjectConnectionInstanceTarget } from "../../../src/Connections/JiraProject"; 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 JiraIcon from "../../icons/jira.png";
import ConnectionSearch from "../elements/ConnectionSearch"; import ConnectionSearch from "../elements/ConnectionSearch";
import { DropItem } from "../elements/DropdownSearch"; import { DropItem } from "../elements/DropdownSearch";
import { BridgeContext } from "../../context";
const EventType = "uk.half-shot.matrix-hookshot.jira.project"; 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 [allowedEvents, setAllowedEvents] = useState<string[]>(existingConnection?.config.events || ['issue_created']);
const api = useContext(BridgeContext).bridgeApi;
const toggleEvent = useCallback((evt: Event) => { const toggleEvent = useCallback((evt: Event) => {
const key = (evt.target as HTMLElement).getAttribute('x-event-name'); const key = (evt.target as HTMLElement).getAttribute('x-event-name');
@ -108,11 +110,10 @@ const RoomConfigText = {
const RoomConfigListItemFunc = (c: JiraProjectResponseItem) => c.config.url; 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> return <RoomConfig<never, JiraProjectResponseItem, JiraProjectConnectionState>
headerImg={JiraIcon} headerImg={JiraIcon}
showHeader={showHeader} showHeader={showHeader}
api={api}
roomId={roomId} roomId={roomId}
type="jira" type="jira"
text={RoomConfigText} text={RoomConfigText}

View File

@ -1,6 +1,6 @@
import { FunctionComponent } from "preact"; import { FunctionComponent } from "preact";
import { useCallback, useEffect, useReducer, useState } from "preact/hooks" import { useCallback, useContext, useEffect, useReducer, useState } from "preact/hooks"
import { BridgeAPI, BridgeAPIError } from "../../BridgeAPI"; import { BridgeAPIError } from "../../BridgeAPI";
import { ListItem, Card } from "../elements"; import { ListItem, Card } from "../elements";
import style from "./RoomConfig.module.scss"; import style from "./RoomConfig.module.scss";
import { GetConnectionsResponseItem } from "../../../src/provisioning/api"; import { GetConnectionsResponseItem } from "../../../src/provisioning/api";
@ -9,6 +9,7 @@ import { LoadingSpinner } from '../elements/LoadingSpinner';
import { ErrCode } from "../../../src/api"; import { ErrCode } from "../../../src/api";
import { retry } from "../../../src/PromiseUtil"; import { retry } from "../../../src/PromiseUtil";
import { Alert } from "@vector-im/compound-web"; import { Alert } from "@vector-im/compound-web";
import { BridgeContext } from "../../context";
export interface ConnectionConfigurationProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> { export interface ConnectionConfigurationProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> {
serviceConfig: SConfig; serviceConfig: SConfig;
@ -19,7 +20,6 @@ export interface ConnectionConfigurationProps<SConfig, ConnectionType extends Ge
isMigrationCandidate?: boolean, isMigrationCandidate?: boolean,
existingConnection?: ConnectionType; existingConnection?: ConnectionType;
onRemove?: () => void, onRemove?: () => void,
api: BridgeAPI;
} }
export interface IRoomConfigText { export interface IRoomConfigText {
@ -31,7 +31,6 @@ export interface IRoomConfigText {
} }
interface IRoomConfigProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> { interface IRoomConfigProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> {
api: BridgeAPI;
roomId: string; roomId: string;
type: string; type: string;
showAuthPrompt?: boolean; 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>) { export const RoomConfig = function<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState>(props: IRoomConfigProps<SConfig, ConnectionType, ConnectionState>) {
const { const {
api,
roomId, roomId,
type, type,
showAuthPrompt = false, showAuthPrompt = false,
@ -59,6 +57,7 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
listItemName, listItemName,
connectionEventType, connectionEventType,
} = props; } = props;
const api = useContext(BridgeContext).bridgeApi;
const ConnectionConfigComponent = props.connectionConfigComponent; const ConnectionConfigComponent = props.connectionConfigComponent;
const [ error, setError ] = useState<null|{header?: string, message: string, isWarning?: boolean, forPrevious?: boolean}>(null); const [ error, setError ] = useState<null|{header?: string, message: string, isWarning?: boolean, forPrevious?: boolean}>(null);
const [ connections, setConnections ] = useState<ConnectionType[]|null>(null); const [ connections, setConnections ] = useState<ConnectionType[]|null>(null);
@ -153,7 +152,6 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
<h2>{text.createNew}</h2> <h2>{text.createNew}</h2>
{serviceConfig && <ConnectionConfigComponent {serviceConfig && <ConnectionConfigComponent
key={newConnectionKey} key={newConnectionKey}
api={api}
serviceConfig={serviceConfig} serviceConfig={serviceConfig}
onSave={handleSaveOnCreation} onSave={handleSaveOnCreation}
loginLabel={text.login} loginLabel={text.login}
@ -166,7 +164,6 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
<h2>{ canEditRoom ? text.listCanEdit : text.listCantEdit }</h2> <h2>{ canEditRoom ? text.listCanEdit : text.listCantEdit }</h2>
{ serviceConfig && connections?.map(c => <ListItem key={c.id} text={listItemName(c)}> { serviceConfig && connections?.map(c => <ListItem key={c.id} text={listItemName(c)}>
<ConnectionConfigComponent <ConnectionConfigComponent
api={api}
serviceConfig={serviceConfig} serviceConfig={serviceConfig}
existingConnection={c} existingConnection={c}
onSave={(config) => { onSave={(config) => {

15
web/context.ts Normal file
View 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);