Merge branch 'master' into locked_status
This commit is contained in:
commit
9b2981f525
2
.github/workflows/edge_ghpage.yml
vendored
2
.github/workflows/edge_ghpage.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
yarn build
|
yarn build
|
||||||
|
|
||||||
- name: Deploy 🚀
|
- name: Deploy 🚀
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.4.3
|
uses: JamesIves/github-pages-deploy-action@v4.5.0
|
||||||
with:
|
with:
|
||||||
branch: gh-pages
|
branch: gh-pages
|
||||||
folder: build
|
folder: build
|
||||||
|
27
package.json
27
package.json
@ -10,29 +10,28 @@
|
|||||||
"url": "https://github.com/Awesome-Technologies/synapse-admin"
|
"url": "https://github.com/Awesome-Technologies/synapse-admin"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^6.0.0",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"prettier": "^2.2.0"
|
"prettier": "^3.2.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mui/icons-material": "^5.14.19",
|
"@mui/icons-material": "^5.15.7",
|
||||||
"@mui/material": "^5.14.8",
|
"@mui/material": "^5.15.7",
|
||||||
"@mui/styles": "5.14.10",
|
"@mui/styles": "^5.15.8",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"prop-types": "^15.8.1",
|
|
||||||
"ra-language-chinese": "^2.0.10",
|
"ra-language-chinese": "^2.0.10",
|
||||||
"ra-language-french": "^4.16.2",
|
"ra-language-french": "^4.16.9",
|
||||||
"ra-language-german": "^3.13.4",
|
"ra-language-german": "^3.13.4",
|
||||||
"ra-language-italian": "^3.13.1",
|
"ra-language-italian": "^3.13.1",
|
||||||
"react": "^17.0.0",
|
"react": "^18.0.0",
|
||||||
"react-admin": "^4.16.9",
|
"react-admin": "^4.16.9",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.0.0",
|
||||||
"react-scripts": "^5.0.1"
|
"react-scripts": "^5.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -40,6 +40,7 @@ const i18nProvider = polyglotI18nProvider(
|
|||||||
const App = () => (
|
const App = () => (
|
||||||
<Admin
|
<Admin
|
||||||
disableTelemetry
|
disableTelemetry
|
||||||
|
requireAuth
|
||||||
loginPage={LoginPage}
|
loginPage={LoginPage}
|
||||||
authProvider={authProvider}
|
authProvider={authProvider}
|
||||||
dataProvider={dataProvider}
|
dataProvider={dataProvider}
|
@ -6,7 +6,17 @@ import { useRecordContext } from "react-admin";
|
|||||||
const AvatarField = ({ source, ...rest }) => {
|
const AvatarField = ({ source, ...rest }) => {
|
||||||
const record = useRecordContext(rest);
|
const record = useRecordContext(rest);
|
||||||
const src = get(record, source)?.toString();
|
const src = get(record, source)?.toString();
|
||||||
return <Avatar src={src} {...rest} />;
|
const { alt, classes, sizes, sx, variant } = rest;
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
alt={alt}
|
||||||
|
classes={classes}
|
||||||
|
sizes={sizes}
|
||||||
|
src={src}
|
||||||
|
sx={sx}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AvatarField;
|
export default AvatarField;
|
18
src/components/AvatarField.test.js
Normal file
18
src/components/AvatarField.test.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { RecordContextProvider } from "react-admin";
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import AvatarField from "./AvatarField";
|
||||||
|
|
||||||
|
describe("AvatarField", () => {
|
||||||
|
it("shows image", () => {
|
||||||
|
const value = {
|
||||||
|
avatar: "foo",
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<RecordContextProvider value={value}>
|
||||||
|
<AvatarField source="avatar" />
|
||||||
|
</RecordContextProvider>
|
||||||
|
);
|
||||||
|
expect(screen.getByRole("img").getAttribute("src")).toBe("foo");
|
||||||
|
});
|
||||||
|
});
|
@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import {
|
import {
|
||||||
Datagrid,
|
Datagrid,
|
||||||
DateField,
|
DateField,
|
||||||
|
DeleteButton,
|
||||||
List,
|
List,
|
||||||
NumberField,
|
NumberField,
|
||||||
Pagination,
|
Pagination,
|
||||||
@ -10,6 +11,8 @@ import {
|
|||||||
Tab,
|
Tab,
|
||||||
TabbedShowLayout,
|
TabbedShowLayout,
|
||||||
TextField,
|
TextField,
|
||||||
|
TopToolbar,
|
||||||
|
useRecordContext,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import PageviewIcon from "@mui/icons-material/Pageview";
|
import PageviewIcon from "@mui/icons-material/Pageview";
|
||||||
@ -32,7 +35,7 @@ const ReportPagination = () => (
|
|||||||
export const ReportShow = props => {
|
export const ReportShow = props => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
return (
|
return (
|
||||||
<Show {...props}>
|
<Show {...props} actions={<ReportShowActions />}>
|
||||||
<TabbedShowLayout>
|
<TabbedShowLayout>
|
||||||
<Tab
|
<Tab
|
||||||
label={translate("synapseadmin.reports.tabs.basic", {
|
label={translate("synapseadmin.reports.tabs.basic", {
|
||||||
@ -99,6 +102,21 @@ export const ReportShow = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ReportShowActions = () => {
|
||||||
|
const record = useRecordContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TopToolbar>
|
||||||
|
<DeleteButton
|
||||||
|
record={record}
|
||||||
|
mutationMode="pessimistic"
|
||||||
|
confirmTitle="resources.reports.action.erase.title"
|
||||||
|
confirmContent="resources.reports.action.erase.content"
|
||||||
|
/>
|
||||||
|
</TopToolbar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const ReportList = props => (
|
export const ReportList = props => (
|
||||||
<List
|
<List
|
||||||
{...props}
|
{...props}
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
fetchUtils,
|
|
||||||
Form,
|
Form,
|
||||||
FormDataConsumer,
|
FormDataConsumer,
|
||||||
Notification,
|
Notification,
|
||||||
@ -27,6 +26,13 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { styled } from "@mui/material/styles";
|
import { styled } from "@mui/material/styles";
|
||||||
import LockIcon from "@mui/icons-material/Lock";
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
|
import {
|
||||||
|
getServerVersion,
|
||||||
|
getSupportedLoginFlows,
|
||||||
|
getWellKnownUrl,
|
||||||
|
isValidBaseUrl,
|
||||||
|
splitMxid,
|
||||||
|
} from "../synapse/synapse";
|
||||||
|
|
||||||
const FormBox = styled(Box)(({ theme }) => ({
|
const FormBox = styled(Box)(({ theme }) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -113,8 +119,8 @@ const LoginPage = () => {
|
|||||||
typeof error === "string"
|
typeof error === "string"
|
||||||
? error
|
? error
|
||||||
: typeof error === "undefined" || !error.message
|
: typeof error === "undefined" || !error.message
|
||||||
? "ra.auth.sign_in_error"
|
? "ra.auth.sign_in_error"
|
||||||
: error.message
|
: error.message
|
||||||
);
|
);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
@ -155,8 +161,8 @@ const LoginPage = () => {
|
|||||||
typeof error === "string"
|
typeof error === "string"
|
||||||
? error
|
? error
|
||||||
: typeof error === "undefined" || !error.message
|
: typeof error === "undefined" || !error.message
|
||||||
? "ra.auth.sign_in_error"
|
? "ra.auth.sign_in_error"
|
||||||
: error.message,
|
: error.message,
|
||||||
{ type: "warning" }
|
{ type: "warning" }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -170,87 +176,42 @@ const LoginPage = () => {
|
|||||||
window.location.href = ssoFullUrl;
|
window.location.href = ssoFullUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractHomeServer = username => {
|
|
||||||
const usernameRegex = /@[a-zA-Z0-9._=\-/]+:([a-zA-Z0-9\-.]+\.[a-zA-Z]+)/;
|
|
||||||
if (!username) return null;
|
|
||||||
const res = username.match(usernameRegex);
|
|
||||||
if (res) return res[1];
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const UserData = ({ formData }) => {
|
const UserData = ({ formData }) => {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const [serverVersion, setServerVersion] = useState("");
|
const [serverVersion, setServerVersion] = useState("");
|
||||||
|
|
||||||
const handleUsernameChange = _ => {
|
const handleUsernameChange = _ => {
|
||||||
if (formData.base_url || cfg_base_url) return;
|
if (formData.base_url || cfg_base_url) return;
|
||||||
// check if username is a full qualified userId then set base_url accordially
|
// check if username is a full qualified userId then set base_url accordingly
|
||||||
const home_server = extractHomeServer(formData.username);
|
const domain = splitMxid(formData.username)?.domain;
|
||||||
const wellKnownUrl = `https://${home_server}/.well-known/matrix/client`;
|
if (domain) {
|
||||||
if (home_server) {
|
getWellKnownUrl(domain).then(url => form.setValue("base_url", url));
|
||||||
// fetch .well-known entry to get base_url
|
|
||||||
fetchUtils
|
|
||||||
.fetchJson(wellKnownUrl, { method: "GET" })
|
|
||||||
.then(({ json }) => {
|
|
||||||
form.setValue("base_url", json["m.homeserver"].base_url);
|
|
||||||
})
|
|
||||||
.catch(_ => {
|
|
||||||
// if there is no .well-known entry, try the home server name
|
|
||||||
form.setValue("base_url", `https://${home_server}`);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(
|
useEffect(() => {
|
||||||
_ => {
|
if (!isValidBaseUrl(formData.base_url)) return;
|
||||||
if (
|
|
||||||
!formData.base_url ||
|
getServerVersion(formData.base_url)
|
||||||
!formData.base_url.match(
|
.then(serverVersion =>
|
||||||
/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/
|
setServerVersion(
|
||||||
|
`${translate("synapseadmin.auth.server_version")} ${serverVersion}`
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return;
|
.catch(() => setServerVersion(""));
|
||||||
const versionUrl = `${formData.base_url}/_synapse/admin/v1/server_version`;
|
|
||||||
fetchUtils
|
|
||||||
.fetchJson(versionUrl, { method: "GET" })
|
|
||||||
.then(({ json }) => {
|
|
||||||
setServerVersion(
|
|
||||||
`${translate("synapseadmin.auth.server_version")} ${
|
|
||||||
json["server_version"]
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch(_ => {
|
|
||||||
setServerVersion("");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set SSO Url
|
// Set SSO Url
|
||||||
const authMethodUrl = `${formData.base_url}/_matrix/client/r0/login`;
|
getSupportedLoginFlows(formData.base_url)
|
||||||
let supportPass = false,
|
.then(loginFlows => {
|
||||||
supportSSO = false;
|
const supportPass =
|
||||||
fetchUtils
|
loginFlows.find(f => f.type === "m.login.password") !== undefined;
|
||||||
.fetchJson(authMethodUrl, { method: "GET" })
|
const supportSSO =
|
||||||
.then(({ json }) => {
|
loginFlows.find(f => f.type === "m.login.sso") !== undefined;
|
||||||
json.flows.forEach(f => {
|
setSupportPassAuth(supportPass);
|
||||||
if (f.type === "m.login.password") {
|
setSSOBaseUrl(supportSSO ? formData.base_url : "");
|
||||||
supportPass = true;
|
})
|
||||||
} else if (f.type === "m.login.sso") {
|
.catch(() => setSSOBaseUrl(""));
|
||||||
supportSSO = true;
|
}, [formData.base_url]);
|
||||||
}
|
|
||||||
});
|
|
||||||
setSupportPassAuth(supportPass);
|
|
||||||
if (supportSSO) {
|
|
||||||
setSSOBaseUrl(formData.base_url);
|
|
||||||
} else {
|
|
||||||
setSSOBaseUrl("");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(_ => {
|
|
||||||
setSSOBaseUrl("");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[formData.base_url]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
@ -1,78 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
useDelete,
|
|
||||||
useNotify,
|
|
||||||
Confirm,
|
|
||||||
useRecordContext,
|
|
||||||
useRefresh,
|
|
||||||
} from "react-admin";
|
|
||||||
import ActionDelete from "@mui/icons-material/Delete";
|
|
||||||
import { alpha, useTheme } from "@mui/material/styles";
|
|
||||||
|
|
||||||
export const DeviceRemoveButton = props => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const record = useRecordContext();
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const refresh = useRefresh();
|
|
||||||
const notify = useNotify();
|
|
||||||
|
|
||||||
const [removeDevice, { isLoading }] = useDelete();
|
|
||||||
|
|
||||||
if (!record) return null;
|
|
||||||
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
const handleDialogClose = () => setOpen(false);
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
|
||||||
removeDevice(
|
|
||||||
"devices",
|
|
||||||
// needs previousData for user_id
|
|
||||||
{ id: record.id, previousData: record },
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
notify("resources.devices.action.erase.success");
|
|
||||||
refresh();
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
notify("resources.devices.action.erase.failure", { type: "error" });
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
setOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
{...props}
|
|
||||||
label="ra.action.remove"
|
|
||||||
onClick={handleClick}
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: alpha(theme.palette.error.main, 0.12),
|
|
||||||
// Reset on mouse devices
|
|
||||||
"@media (hover: none)": {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ActionDelete />
|
|
||||||
</Button>
|
|
||||||
<Confirm
|
|
||||||
isOpen={open}
|
|
||||||
loading={isLoading}
|
|
||||||
onConfirm={handleConfirm}
|
|
||||||
onClose={handleDialogClose}
|
|
||||||
title="resources.devices.action.erase.title"
|
|
||||||
content="resources.devices.action.erase.content"
|
|
||||||
translateOptions={{
|
|
||||||
id: record.id,
|
|
||||||
name: record.display_name ? record.display_name : record.id,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
51
src/components/devices.jsx
Normal file
51
src/components/devices.jsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
DeleteButton,
|
||||||
|
useDelete,
|
||||||
|
useNotify,
|
||||||
|
useRecordContext,
|
||||||
|
useRefresh,
|
||||||
|
} from "react-admin";
|
||||||
|
|
||||||
|
export const DeviceRemoveButton = props => {
|
||||||
|
const record = useRecordContext();
|
||||||
|
const refresh = useRefresh();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
|
const [removeDevice] = useDelete();
|
||||||
|
|
||||||
|
if (!record) return null;
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
removeDevice(
|
||||||
|
"devices",
|
||||||
|
// needs previousData for user_id
|
||||||
|
{ id: record.id, previousData: record },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
notify("resources.devices.action.erase.success");
|
||||||
|
refresh();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
notify("resources.devices.action.erase.failure", { type: "error" });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DeleteButton
|
||||||
|
{...props}
|
||||||
|
label="ra.action.remove"
|
||||||
|
confirmTitle="resources.devices.action.erase.title"
|
||||||
|
confirmContent="resources.devices.action.erase.content"
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
mutationMode="pessimistic"
|
||||||
|
redirect={false}
|
||||||
|
translateOptions={{
|
||||||
|
id: record.id,
|
||||||
|
name: record.display_name ? record.display_name : record.id,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -98,6 +98,7 @@ export const RoomShow = props => {
|
|||||||
<Tab label="synapseadmin.rooms.tabs.basic" icon={<ViewListIcon />}>
|
<Tab label="synapseadmin.rooms.tabs.basic" icon={<ViewListIcon />}>
|
||||||
<TextField source="room_id" />
|
<TextField source="room_id" />
|
||||||
<TextField source="name" />
|
<TextField source="name" />
|
||||||
|
<TextField source="topic" />
|
||||||
<TextField source="canonical_alias" />
|
<TextField source="canonical_alias" />
|
||||||
<ReferenceField source="creator" reference="users">
|
<ReferenceField source="creator" reference="users">
|
||||||
<TextField source="id" />
|
<TextField source="id" />
|
@ -189,7 +189,7 @@ const de = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
reports: {
|
reports: {
|
||||||
name: "Ereignisbericht |||| Ereignisberichte",
|
name: "Gemeldetes Ereignis |||| Gemeldete Ereignisse",
|
||||||
fields: {
|
fields: {
|
||||||
id: "ID",
|
id: "ID",
|
||||||
received_ts: "Meldezeit",
|
received_ts: "Meldezeit",
|
||||||
@ -211,6 +211,13 @@ const de = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "Gemeldetes Event löschen",
|
||||||
|
content:
|
||||||
|
"Sind Sie sicher dass Sie das gemeldete Event löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
connections: {
|
connections: {
|
||||||
name: "Verbindungen",
|
name: "Verbindungen",
|
||||||
|
@ -208,6 +208,13 @@ const en = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "Delete reported event",
|
||||||
|
content:
|
||||||
|
"Are you sure you want to delete the reported event? This cannot be undone.",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
connections: {
|
connections: {
|
||||||
name: "Connections",
|
name: "Connections",
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom";
|
|
||||||
import App from "./App";
|
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById("root"));
|
|
9
src/index.jsx
Normal file
9
src/index.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
@ -456,7 +456,7 @@ const dataProvider = {
|
|||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
|
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
return jsonClient(`${endpoint_url}/${encodeURIComponent(params.data.id)}`, {
|
return jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(params.data, filterNullValues),
|
body: JSON.stringify(params.data, filterNullValues),
|
||||||
}).then(({ json }) => ({
|
}).then(({ json }) => ({
|
||||||
|
48
src/synapse/synapse.js
Normal file
48
src/synapse/synapse.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { fetchUtils } from "react-admin";
|
||||||
|
|
||||||
|
export const splitMxid = mxid => {
|
||||||
|
const re =
|
||||||
|
/^@(?<name>[a-zA-Z0-9._=\-/]+):(?<domain>[a-zA-Z0-9\-.]+\.[a-zA-Z]+)$/;
|
||||||
|
return re.exec(mxid)?.groups;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isValidBaseUrl = baseUrl =>
|
||||||
|
/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/.test(baseUrl);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the homeserver URL using the well-known lookup
|
||||||
|
* @param domain the domain part of an MXID
|
||||||
|
* @returns homeserver base URL
|
||||||
|
*/
|
||||||
|
export const getWellKnownUrl = async domain => {
|
||||||
|
const wellKnownUrl = `https://${domain}/.well-known/matrix/client`;
|
||||||
|
try {
|
||||||
|
const json = await fetchUtils.fetchJson(wellKnownUrl, { method: "GET" });
|
||||||
|
return json["m.homeserver"].base_url;
|
||||||
|
} catch {
|
||||||
|
// if there is no .well-known entry, return the domain itself
|
||||||
|
return `https://${domain}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get synapse server version
|
||||||
|
* @param base_url the base URL of the homeserver
|
||||||
|
* @returns server version
|
||||||
|
*/
|
||||||
|
export const getServerVersion = async baseUrl => {
|
||||||
|
const versionUrl = `${baseUrl}/_synapse/admin/v1/server_version`;
|
||||||
|
const response = await fetchUtils.fetchJson(versionUrl, { method: "GET" });
|
||||||
|
return response.json.server_version;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get supported login flows
|
||||||
|
* @param baseUrl the base URL of the homeserver
|
||||||
|
* @returns array of supported login flows
|
||||||
|
*/
|
||||||
|
export const getSupportedLoginFlows = async baseUrl => {
|
||||||
|
const loginFlowsUrl = `${baseUrl}/_matrix/client/r0/login`;
|
||||||
|
const response = await fetchUtils.fetchJson(loginFlowsUrl, { method: "GET" });
|
||||||
|
return response.json.flows;
|
||||||
|
};
|
31
src/synapse/synapse.test.js
Normal file
31
src/synapse/synapse.test.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { isValidBaseUrl, splitMxid } from "./synapse";
|
||||||
|
|
||||||
|
describe("splitMxid", () => {
|
||||||
|
it("splits valid MXIDs", () =>
|
||||||
|
expect(splitMxid("@name:domain.tld")).toEqual({
|
||||||
|
name: "name",
|
||||||
|
domain: "domain.tld",
|
||||||
|
}));
|
||||||
|
it("rejects invalid MXIDs", () => expect(splitMxid("foo")).toBeUndefined());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isValidBaseUrl", () => {
|
||||||
|
it("accepts a http URL", () =>
|
||||||
|
expect(isValidBaseUrl("http://foo.bar")).toBeTruthy());
|
||||||
|
it("accepts a https URL", () =>
|
||||||
|
expect(isValidBaseUrl("https://foo.bar")).toBeTruthy());
|
||||||
|
it("accepts a valid URL with port", () =>
|
||||||
|
expect(isValidBaseUrl("https://foo.bar:1234")).toBeTruthy());
|
||||||
|
it("rejects undefined base URLs", () =>
|
||||||
|
expect(isValidBaseUrl(undefined)).toBeFalsy());
|
||||||
|
it("rejects null base URLs", () => expect(isValidBaseUrl(null)).toBeFalsy());
|
||||||
|
it("rejects empty base URLs", () => expect(isValidBaseUrl("")).toBeFalsy());
|
||||||
|
it("rejects non-string base URLs", () =>
|
||||||
|
expect(isValidBaseUrl({})).toBeFalsy());
|
||||||
|
it("rejects base URLs without protocol", () =>
|
||||||
|
expect(isValidBaseUrl("foo.bar")).toBeFalsy());
|
||||||
|
it("rejects base URLs with path", () =>
|
||||||
|
expect(isValidBaseUrl("http://foo.bar/path")).toBeFalsy());
|
||||||
|
it("rejects invalid base URLs", () =>
|
||||||
|
expect(isValidBaseUrl("http:/foo.bar")).toBeFalsy());
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user