refactoring (#178)
* unify components import * refactor config and app context * refactor icons * refactor date, error, mxid and storage * refactor synapse utils
This commit is contained in:
parent
ea0c7a73fd
commit
392fec3186
@ -35,7 +35,7 @@ In this case, you could provide the configuration in the `/.well-known/matrix/cl
|
|||||||
* `menu` - add custom menu items to the main menu (sidebar) by providing a `menu` array in the config.
|
* `menu` - add custom menu items to the main menu (sidebar) by providing a `menu` array in the config.
|
||||||
Each `menu` item can contain the following fields:
|
Each `menu` item can contain the following fields:
|
||||||
* `label` (required): The text to display in the menu.
|
* `label` (required): The text to display in the menu.
|
||||||
* `icon` (optional): The icon to display next to the label, one of the [../src/components/icons.ts] icons, otherwise a default icon will be used.
|
* `icon` (optional): The icon to display next to the label, one of the [src/utils/icons.ts](../src/utils/icons.ts) icons, otherwise a default icon will be used.
|
||||||
* `url` (required): The URL to navigate to when the menu item is clicked.
|
* `url` (required): The URL to navigate to when the menu item is clicked.
|
||||||
[More details](custom-menu.md)
|
[More details](custom-menu.md)
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ The examples below contain the configuration settings to add a link to the [Syna
|
|||||||
Each `menu` item can contain the following fields:
|
Each `menu` item can contain the following fields:
|
||||||
|
|
||||||
* `label` (required): The text to display in the menu.
|
* `label` (required): The text to display in the menu.
|
||||||
* `icon` (optional): The icon to display next to the label, one of the [../src/components/icons.ts] icons, otherwise a
|
* `icon` (optional): The icon to display next to the label, one of the [src/utils/icons.ts](../src/utils/icons.ts) icons, otherwise a
|
||||||
default icon will be used.
|
default icon will be used.
|
||||||
* `url` (required): The URL to navigate to when the menu item is clicked.
|
* `url` (required): The URL to navigate to when the menu item is clicked.
|
||||||
|
|
||||||
|
14
src/App.tsx
14
src/App.tsx
@ -2,10 +2,11 @@ import { merge } from "lodash";
|
|||||||
import polyglotI18nProvider from "ra-i18n-polyglot";
|
import polyglotI18nProvider from "ra-i18n-polyglot";
|
||||||
|
|
||||||
import { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin";
|
import { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin";
|
||||||
|
import { createContext, useContext } from "react";
|
||||||
import { Route } from "react-router-dom";
|
import { Route } from "react-router-dom";
|
||||||
|
|
||||||
import { AdminLayout } from "./components/AdminLayout";
|
import AdminLayout from "./components/AdminLayout";
|
||||||
import { ImportFeature } from "./components/ImportFeature";
|
import UserImport from "./components/UserImport";
|
||||||
import germanMessages from "./i18n/de";
|
import germanMessages from "./i18n/de";
|
||||||
import englishMessages from "./i18n/en";
|
import englishMessages from "./i18n/en";
|
||||||
import frenchMessages from "./i18n/fr";
|
import frenchMessages from "./i18n/fr";
|
||||||
@ -23,6 +24,7 @@ import users from "./resources/users";
|
|||||||
import authProvider from "./synapse/authProvider";
|
import authProvider from "./synapse/authProvider";
|
||||||
import dataProvider from "./synapse/dataProvider";
|
import dataProvider from "./synapse/dataProvider";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { Config } from "./utils/config";
|
||||||
|
|
||||||
// TODO: Can we use lazy loading together with browser locale?
|
// TODO: Can we use lazy loading together with browser locale?
|
||||||
const messages = {
|
const messages = {
|
||||||
@ -49,7 +51,7 @@ const i18nProvider = polyglotI18nProvider(
|
|||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
const App = () => (
|
export const App = () => (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Admin
|
<Admin
|
||||||
disableTelemetry
|
disableTelemetry
|
||||||
@ -61,7 +63,7 @@ const App = () => (
|
|||||||
i18nProvider={i18nProvider}
|
i18nProvider={i18nProvider}
|
||||||
>
|
>
|
||||||
<CustomRoutes>
|
<CustomRoutes>
|
||||||
<Route path="/import_users" element={<ImportFeature />} />
|
<Route path="/import_users" element={<UserImport />} />
|
||||||
</CustomRoutes>
|
</CustomRoutes>
|
||||||
<Resource {...users} />
|
<Resource {...users} />
|
||||||
<Resource {...rooms} />
|
<Resource {...rooms} />
|
||||||
@ -84,4 +86,8 @@ const App = () => (
|
|||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const AppContext = createContext({});
|
||||||
|
|
||||||
|
export const useAppContext = () => useContext(AppContext) as Config;
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
import { createContext, useContext } from "react";
|
|
||||||
import { Config } from "./components/config";
|
|
||||||
|
|
||||||
export const AppContext = createContext({});
|
|
||||||
|
|
||||||
export const useAppContext = () => useContext(AppContext) as Config;
|
|
@ -1,8 +1,8 @@
|
|||||||
import { CheckForApplicationUpdate, AppBar, TitlePortal, InspectorButton, Confirm, Layout, Logout, Menu, useLogout, UserMenu } from "react-admin";
|
import { CheckForApplicationUpdate, AppBar, TitlePortal, InspectorButton, Confirm, Layout, Logout, Menu, useLogout, UserMenu } from "react-admin";
|
||||||
import { LoginMethod } from "../pages/LoginPage";
|
import { LoginMethod } from "../pages/LoginPage";
|
||||||
import { useEffect, useState, Suspense } from "react";
|
import { useEffect, useState, Suspense } from "react";
|
||||||
import { Icons, DefaultIcon } from "./icons";
|
import { Icons, DefaultIcon } from "../utils/icons";
|
||||||
import { MenuItem, GetConfig, ClearConfig } from "./config";
|
import { MenuItem, GetConfig, ClearConfig } from "../utils/config";
|
||||||
import Footer from "./Footer";
|
import Footer from "./Footer";
|
||||||
|
|
||||||
const AdminUserMenu = () => {
|
const AdminUserMenu = () => {
|
||||||
@ -96,3 +96,5 @@ export const AdminLayout = ({ children }) => {
|
|||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default AdminLayout;
|
||||||
|
@ -3,8 +3,7 @@ import { Avatar, AvatarProps, Badge, Tooltip } from "@mui/material";
|
|||||||
import { FieldProps, useRecordContext, useTranslate } from "react-admin";
|
import { FieldProps, useRecordContext, useTranslate } from "react-admin";
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
||||||
import { isMXID, isASManaged } from "./mxid";
|
import { isMXID, isASManaged } from "../utils/mxid";
|
||||||
import storage from "../storage";
|
|
||||||
|
|
||||||
const AvatarField = ({ source, ...rest }: AvatarProps & FieldProps) => {
|
const AvatarField = ({ source, ...rest }: AvatarProps & FieldProps) => {
|
||||||
const { alt, classes, sizes, sx, variant } = rest;
|
const { alt, classes, sizes, sx, variant } = rest;
|
||||||
@ -74,7 +73,7 @@ const AvatarField = ({ source, ...rest }: AvatarProps & FieldProps) => {
|
|||||||
badge = "🛡️";
|
badge = "🛡️";
|
||||||
tooltip = `${translate("resources.users.badge.system_managed")} (${tooltip})`;
|
tooltip = `${translate("resources.users.badge.system_managed")} (${tooltip})`;
|
||||||
}
|
}
|
||||||
if (storage.getItem("user_id") === record?.id) {
|
if (localStorage.getItem("user_id") === record?.id) {
|
||||||
badge = "🧙";
|
badge = "🧙";
|
||||||
tooltip = `${translate("resources.users.badge.you")} (${tooltip})`;
|
tooltip = `${translate("resources.users.badge.you")} (${tooltip})`;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DeleteWithConfirmButton, DeleteWithConfirmButtonProps, useRecordContext } from "react-admin";
|
import { DeleteWithConfirmButton, DeleteWithConfirmButtonProps, useRecordContext } from "react-admin";
|
||||||
import { isASManaged } from "./mxid";
|
import { isASManaged } from "../utils/mxid";
|
||||||
|
|
||||||
export const DeviceRemoveButton = (props: DeleteWithConfirmButtonProps) => {
|
export const DeviceRemoveButton = (props: DeleteWithConfirmButtonProps) => {
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
@ -26,3 +26,5 @@ export const DeviceRemoveButton = (props: DeleteWithConfirmButtonProps) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default DeviceRemoveButton;
|
@ -93,3 +93,5 @@ export const ExperimentalFeaturesList = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ExperimentalFeaturesList;
|
||||||
|
@ -15,7 +15,8 @@ import {
|
|||||||
import { DataProvider, useTranslate } from "ra-core";
|
import { DataProvider, useTranslate } from "ra-core";
|
||||||
import { useDataProvider, useNotify, RaRecord, Title } from "react-admin";
|
import { useDataProvider, useNotify, RaRecord, Title } from "react-admin";
|
||||||
|
|
||||||
import { generateRandomMxId, generateRandomPassword, returnMXID } from "../synapse/synapse";
|
import { generateRandomMXID, returnMXID } from "../utils/mxid";
|
||||||
|
import { generateRandomPassword } from "../utils/password";
|
||||||
|
|
||||||
const LOGGING = true;
|
const LOGGING = true;
|
||||||
|
|
||||||
@ -274,7 +275,7 @@ const FilePicker = () => {
|
|||||||
// No need to do a bunch of cryptographic random number getting if
|
// No need to do a bunch of cryptographic random number getting if
|
||||||
// we are using neither a generated password nor a generated user id.
|
// we are using neither a generated password nor a generated user id.
|
||||||
if (useridMode === "ignore" || userRecord.id === undefined || userRecord.id === "") {
|
if (useridMode === "ignore" || userRecord.id === undefined || userRecord.id === "") {
|
||||||
userRecord.id = generateRandomMxId();
|
userRecord.id = generateRandomMXID();
|
||||||
}
|
}
|
||||||
if (passwordMode === false || entry.password === undefined || entry.password === "") {
|
if (passwordMode === false || entry.password === undefined || entry.password === "") {
|
||||||
userRecord.password = generateRandomPassword();
|
userRecord.password = generateRandomPassword();
|
||||||
@ -325,7 +326,7 @@ const FilePicker = () => {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const newRecordData = Object.assign({}, recordData, {
|
const newRecordData = Object.assign({}, recordData, {
|
||||||
id: generateRandomMxId(),
|
id: generateRandomMXID(),
|
||||||
});
|
});
|
||||||
retries++;
|
retries++;
|
||||||
if (retries > 512) {
|
if (retries > 512) {
|
||||||
@ -570,4 +571,5 @@ const FilePicker = () => {
|
|||||||
return [<Title defaultTitle={translate("import_users.title")} />, cardContainer];
|
return [<Title defaultTitle={translate("import_users.title")} />, cardContainer];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImportFeature = FilePicker;
|
export const UserImport = FilePicker;
|
||||||
|
export default UserImport;
|
@ -46,7 +46,7 @@ const RateLimitRow = ({ limit, value, updateRateLimit }: { limit: string, value:
|
|||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserRateLimits = () => {
|
const UserRateLimits = () => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
@ -93,3 +93,5 @@ export const UserRateLimits = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default UserRateLimits;
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import { lazy } from "react";
|
|
||||||
|
|
||||||
export const Icons = {
|
|
||||||
Announcement: lazy(() => import('@mui/icons-material/Announcement')),
|
|
||||||
Engineering: lazy(() => import('@mui/icons-material/Engineering')),
|
|
||||||
HelpCenter: lazy(() => import('@mui/icons-material/HelpCenter')),
|
|
||||||
SupportAgent: lazy(() => import('@mui/icons-material/SupportAgent')),
|
|
||||||
Default: lazy(() => import('@mui/icons-material/OpenInNew')),
|
|
||||||
// Add more icons as needed
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DefaultIcon = Icons.Default;
|
|
@ -32,9 +32,8 @@ import {
|
|||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { dateParser } from "./date";
|
import { dateParser } from "../utils/date";
|
||||||
import { DeleteMediaParams, SynapseDataProvider } from "../synapse/dataProvider";
|
import { DeleteMediaParams, SynapseDataProvider } from "../synapse/dataProvider";
|
||||||
import storage from "../storage";
|
|
||||||
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
||||||
|
|
||||||
const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
|
const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
|
||||||
@ -385,7 +384,7 @@ export const MediaIDField = ({ source }) => {
|
|||||||
if (!record) {
|
if (!record) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const homeserver = storage.getItem("home_server");
|
const homeserver = localStorage.getItem("home_server");
|
||||||
|
|
||||||
const mediaID = get(record, source)?.toString();
|
const mediaID = get(record, source)?.toString();
|
||||||
if (!mediaID) {
|
if (!mediaID) {
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { Identifier } from "ra-core";
|
|
||||||
import { GetConfig } from "./config";
|
|
||||||
|
|
||||||
const mxidPattern = /^@[^@:]+:[^@:]+$/;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if id is a valid Matrix ID (user)
|
|
||||||
* @param id The ID to check
|
|
||||||
* @returns Whether the ID is a valid Matrix ID
|
|
||||||
*/
|
|
||||||
export const isMXID = (id: string | Identifier): boolean => mxidPattern.test(id as string);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a user is managed by an application service
|
|
||||||
* @param id The user ID to check
|
|
||||||
* @returns Whether the user is managed by an application service
|
|
||||||
*/
|
|
||||||
export const isASManaged = (id: string | Identifier): boolean => {
|
|
||||||
return GetConfig().asManagedUsers.some(regex => regex.test(id as string));
|
|
||||||
};
|
|
@ -2,10 +2,8 @@ import React from "react";
|
|||||||
|
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
import App from "./App";
|
import {App, AppContext } from "./App";
|
||||||
import { FetchConfig, GetConfig } from "./components/config";
|
import { FetchConfig, GetConfig } from "./utils/config";
|
||||||
import { AppContext } from "./AppContext";
|
|
||||||
import storage from "./storage";
|
|
||||||
|
|
||||||
await FetchConfig();
|
await FetchConfig();
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { render, screen } from "@testing-library/react";
|
|||||||
import { AdminContext } from "react-admin";
|
import { AdminContext } from "react-admin";
|
||||||
|
|
||||||
import LoginPage from "./LoginPage";
|
import LoginPage from "./LoginPage";
|
||||||
import { AppContext } from "../AppContext";
|
import { AppContext } from "../App";
|
||||||
import englishMessages from "../i18n/en";
|
import englishMessages from "../i18n/en";
|
||||||
|
|
||||||
const i18nProvider = polyglotI18nProvider(() => englishMessages, "en", [{ locale: "en", name: "English" }]);
|
const i18nProvider = polyglotI18nProvider(() => englishMessages, "en", [{ locale: "en", name: "English" }]);
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import LoginFormBox from "../components/LoginFormBox";
|
import LoginFormBox from "../components/LoginFormBox";
|
||||||
import { useAppContext } from "../AppContext";
|
import { useAppContext } from "../App";
|
||||||
import {
|
import {
|
||||||
getServerVersion,
|
getServerVersion,
|
||||||
getSupportedFeatures,
|
getSupportedFeatures,
|
||||||
@ -24,8 +24,7 @@ import {
|
|||||||
getWellKnownUrl,
|
getWellKnownUrl,
|
||||||
isValidBaseUrl,
|
isValidBaseUrl,
|
||||||
splitMxid,
|
splitMxid,
|
||||||
} from "../synapse/synapse";
|
} from "../synapse/matrix";
|
||||||
import storage from "../storage";
|
|
||||||
import Footer from "../components/Footer";
|
import Footer from "../components/Footer";
|
||||||
|
|
||||||
export type LoginMethod = "credentials" | "accessToken";
|
export type LoginMethod = "credentials" | "accessToken";
|
||||||
@ -46,7 +45,7 @@ const LoginPage = () => {
|
|||||||
const [locale, setLocale] = useLocaleState();
|
const [locale, setLocale] = useLocaleState();
|
||||||
const locales = useLocales();
|
const locales = useLocales();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const base_url = allowSingleBaseUrl ? restrictBaseUrl : storage.getItem("base_url");
|
const base_url = allowSingleBaseUrl ? restrictBaseUrl : localStorage.getItem("base_url");
|
||||||
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
||||||
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
|
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
|
||||||
const [loginMethod, setLoginMethod] = useState<LoginMethod>("credentials");
|
const [loginMethod, setLoginMethod] = useState<LoginMethod>("credentials");
|
||||||
@ -60,8 +59,8 @@ const LoginPage = () => {
|
|||||||
console.log("SSO token is", ssoToken);
|
console.log("SSO token is", ssoToken);
|
||||||
// Prevent further requests
|
// Prevent further requests
|
||||||
window.history.replaceState({}, "", window.location.href.replace(loginToken[0], "#").split("#")[0]);
|
window.history.replaceState({}, "", window.location.href.replace(loginToken[0], "#").split("#")[0]);
|
||||||
const baseUrl = storage.getItem("sso_base_url");
|
const baseUrl = localStorage.getItem("sso_base_url");
|
||||||
storage.removeItem("sso_base_url");
|
localStorage.removeItem("sso_base_url");
|
||||||
if (baseUrl) {
|
if (baseUrl) {
|
||||||
const auth = {
|
const auth = {
|
||||||
base_url: baseUrl,
|
base_url: baseUrl,
|
||||||
@ -114,7 +113,7 @@ const LoginPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSSO = () => {
|
const handleSSO = () => {
|
||||||
storage.setItem("sso_base_url", ssoBaseUrl);
|
localStorage.setItem("sso_base_url", ssoBaseUrl);
|
||||||
const ssoFullUrl = `${ssoBaseUrl}/_matrix/client/r0/login/sso/redirect?redirectUrl=${encodeURIComponent(
|
const ssoFullUrl = `${ssoBaseUrl}/_matrix/client/r0/login/sso/redirect?redirectUrl=${encodeURIComponent(
|
||||||
window.location.href
|
window.location.href
|
||||||
)}`;
|
)}`;
|
||||||
|
@ -33,7 +33,7 @@ import {
|
|||||||
DateFieldProps,
|
DateFieldProps,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
|
||||||
import { DATE_FORMAT } from "../components/date";
|
import { DATE_FORMAT } from "../utils/date";
|
||||||
import { get } from "lodash";
|
import { get } from "lodash";
|
||||||
|
|
||||||
const DestinationPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
const DestinationPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
Toolbar,
|
Toolbar,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
|
||||||
import { DATE_FORMAT, dateFormatter, dateParser } from "../components/date";
|
import { DATE_FORMAT, dateFormatter, dateParser } from "../utils/date";
|
||||||
|
|
||||||
const validateToken = [regex(/^[A-Za-z0-9._~-]{0,64}$/)];
|
const validateToken = [regex(/^[A-Za-z0-9._~-]{0,64}$/)];
|
||||||
const validateUsesAllowed = [number()];
|
const validateUsesAllowed = [number()];
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
|
||||||
import { DATE_FORMAT } from "../components/date";
|
import { DATE_FORMAT } from "../utils/date";
|
||||||
import { ReportMediaContent } from "../components/media";
|
import { ReportMediaContent } from "../components/media";
|
||||||
|
|
||||||
const ReportPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
const ReportPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
||||||
|
@ -47,7 +47,7 @@ import {
|
|||||||
RoomDirectoryUnpublishButton,
|
RoomDirectoryUnpublishButton,
|
||||||
RoomDirectoryPublishButton,
|
RoomDirectoryPublishButton,
|
||||||
} from "./room_directory";
|
} from "./room_directory";
|
||||||
import { DATE_FORMAT } from "../components/date";
|
import { DATE_FORMAT } from "../utils/date";
|
||||||
import DeleteRoomButton from "../components/DeleteRoomButton";
|
import DeleteRoomButton from "../components/DeleteRoomButton";
|
||||||
import AvatarField from "../components/AvatarField";
|
import AvatarField from "../components/AvatarField";
|
||||||
import { Room } from "../synapse/dataProvider";
|
import { Room } from "../synapse/dataProvider";
|
||||||
|
@ -69,15 +69,15 @@ import { Link } from "react-router-dom";
|
|||||||
|
|
||||||
import AvatarField from "../components/AvatarField";
|
import AvatarField from "../components/AvatarField";
|
||||||
import DeleteUserButton from "../components/DeleteUserButton";
|
import DeleteUserButton from "../components/DeleteUserButton";
|
||||||
import { isASManaged } from "../components/mxid";
|
import { isASManaged } from "../utils/mxid";
|
||||||
import { ServerNoticeButton, ServerNoticeBulkButton } from "../components/ServerNotices";
|
import { ServerNoticeButton, ServerNoticeBulkButton } from "../components/ServerNotices";
|
||||||
import { DATE_FORMAT } from "../components/date";
|
import { DATE_FORMAT } from "../utils/date";
|
||||||
import { DeviceRemoveButton } from "../components/devices";
|
import DeviceRemoveButton from "../components/DeviceRemoveButton";
|
||||||
import { MediaIDField, ProtectMediaButton, QuarantineMediaButton } from "../components/media";
|
import { MediaIDField, ProtectMediaButton, QuarantineMediaButton } from "../components/media";
|
||||||
import { generateRandomPassword } from "../synapse/synapse";
|
import { generateRandomPassword } from "../utils/password";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import { ExperimentalFeaturesList } from "../components/ExperimentalFeatures";
|
import ExperimentalFeaturesList from "../components/ExperimentalFeatures";
|
||||||
import { UserRateLimits } from "../components/UserRateLimits";
|
import UserRateLimits from "../components/UserRateLimits";
|
||||||
import { User, UsernameAvailabilityResult } from "../synapse/dataProvider";
|
import { User, UsernameAvailabilityResult } from "../synapse/dataProvider";
|
||||||
import { MakeAdminBtn } from "./rooms";
|
import { MakeAdminBtn } from "./rooms";
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
const storage = localStorage;
|
|
||||||
|
|
||||||
export default storage;
|
|
@ -1,7 +1,6 @@
|
|||||||
import fetchMock from "jest-fetch-mock";
|
import fetchMock from "jest-fetch-mock";
|
||||||
|
|
||||||
import authProvider from "./authProvider";
|
import authProvider from "./authProvider";
|
||||||
import storage from "../storage";
|
|
||||||
import { HttpError } from "ra-core";
|
import { HttpError } from "ra-core";
|
||||||
|
|
||||||
fetchMock.enableMocks();
|
fetchMock.enableMocks();
|
||||||
@ -9,7 +8,7 @@ fetchMock.enableMocks();
|
|||||||
describe("authProvider", () => {
|
describe("authProvider", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetchMock.resetMocks();
|
fetchMock.resetMocks();
|
||||||
storage.clear();
|
localStorage.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("login", () => {
|
describe("login", () => {
|
||||||
@ -38,10 +37,10 @@ describe("authProvider", () => {
|
|||||||
}),
|
}),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
expect(storage.getItem("base_url")).toEqual("http://example.com");
|
expect(localStorage.getItem("base_url")).toEqual("http://example.com");
|
||||||
expect(storage.getItem("user_id")).toEqual("@user:example.com");
|
expect(localStorage.getItem("user_id")).toEqual("@user:example.com");
|
||||||
expect(storage.getItem("access_token")).toEqual("foobar");
|
expect(localStorage.getItem("access_token")).toEqual("foobar");
|
||||||
expect(storage.getItem("device_id")).toEqual("some_device");
|
expect(localStorage.getItem("device_id")).toEqual("some_device");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,16 +68,16 @@ describe("authProvider", () => {
|
|||||||
}),
|
}),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
expect(storage.getItem("base_url")).toEqual("https://example.com");
|
expect(localStorage.getItem("base_url")).toEqual("https://example.com");
|
||||||
expect(storage.getItem("user_id")).toEqual("@user:example.com");
|
expect(localStorage.getItem("user_id")).toEqual("@user:example.com");
|
||||||
expect(storage.getItem("access_token")).toEqual("foobar");
|
expect(localStorage.getItem("access_token")).toEqual("foobar");
|
||||||
expect(storage.getItem("device_id")).toEqual("some_device");
|
expect(localStorage.getItem("device_id")).toEqual("some_device");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("logout", () => {
|
describe("logout", () => {
|
||||||
it("should remove the access_token from storage", async () => {
|
it("should remove the access_token from storage", async () => {
|
||||||
storage.setItem("base_url", "example.com");
|
localStorage.setItem("base_url", "example.com");
|
||||||
storage.setItem("access_token", "foo");
|
localStorage.setItem("access_token", "foo");
|
||||||
fetchMock.mockResponse(JSON.stringify({}));
|
fetchMock.mockResponse(JSON.stringify({}));
|
||||||
|
|
||||||
await authProvider.logout(null);
|
await authProvider.logout(null);
|
||||||
@ -91,7 +90,7 @@ describe("authProvider", () => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
user: { authenticated: true, token: "Bearer foo" },
|
user: { authenticated: true, token: "Bearer foo" },
|
||||||
});
|
});
|
||||||
expect(storage.getItem("access_token")).toBeNull();
|
expect(localStorage.getItem("access_token")).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -115,7 +114,7 @@ describe("authProvider", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should resolve when logged in", async () => {
|
it("should resolve when logged in", async () => {
|
||||||
storage.setItem("access_token", "foobar");
|
localStorage.setItem("access_token", "foobar");
|
||||||
|
|
||||||
await expect(authProvider.checkAuth({})).resolves.toBeUndefined();
|
await expect(authProvider.checkAuth({})).resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { AuthProvider, HttpError, Options, fetchUtils } from "react-admin";
|
import { AuthProvider, HttpError, Options, fetchUtils } from "react-admin";
|
||||||
|
|
||||||
import storage from "../storage";
|
import { MatrixError, displayError } from "../utils/error";
|
||||||
import { MatrixError, displayError } from "../components/error";
|
|
||||||
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
||||||
import { FetchConfig, ClearConfig } from "../components/config";
|
import { FetchConfig, ClearConfig } from "../utils/config";
|
||||||
|
|
||||||
const authProvider: AuthProvider = {
|
const authProvider: AuthProvider = {
|
||||||
// called when the user attempts to log in
|
// called when the user attempts to log in
|
||||||
@ -26,7 +25,7 @@ const authProvider: AuthProvider = {
|
|||||||
body: JSON.stringify(
|
body: JSON.stringify(
|
||||||
Object.assign(
|
Object.assign(
|
||||||
{
|
{
|
||||||
device_id: storage.getItem("device_id"),
|
device_id: localStorage.getItem("device_id"),
|
||||||
initial_device_display_name: "Synapse Admin",
|
initial_device_display_name: "Synapse Admin",
|
||||||
},
|
},
|
||||||
loginToken
|
loginToken
|
||||||
@ -52,11 +51,11 @@ const authProvider: AuthProvider = {
|
|||||||
if (!base_url) {
|
if (!base_url) {
|
||||||
// there is some kind of bug with base_url being present in the form, but not submitted
|
// there is some kind of bug with base_url being present in the form, but not submitted
|
||||||
// ref: https://github.com/etkecc/synapse-admin/issues/14
|
// ref: https://github.com/etkecc/synapse-admin/issues/14
|
||||||
storage.removeItem("base_url")
|
localStorage.removeItem("base_url")
|
||||||
throw new Error("Homeserver URL is required.");
|
throw new Error("Homeserver URL is required.");
|
||||||
}
|
}
|
||||||
base_url = base_url.replace(/\/+$/g, "");
|
base_url = base_url.replace(/\/+$/g, "");
|
||||||
storage.setItem("base_url", base_url);
|
localStorage.setItem("base_url", base_url);
|
||||||
|
|
||||||
const decoded_base_url = window.decodeURIComponent(base_url);
|
const decoded_base_url = window.decodeURIComponent(base_url);
|
||||||
let login_api_url = decoded_base_url + (accessToken ? "/_matrix/client/v3/account/whoami" : "/_matrix/client/v3/login");
|
let login_api_url = decoded_base_url + (accessToken ? "/_matrix/client/v3/account/whoami" : "/_matrix/client/v3/login");
|
||||||
@ -76,11 +75,11 @@ const authProvider: AuthProvider = {
|
|||||||
|
|
||||||
response = await fetchUtils.fetchJson(login_api_url, options);
|
response = await fetchUtils.fetchJson(login_api_url, options);
|
||||||
const json = response.json;
|
const json = response.json;
|
||||||
storage.setItem("home_server", accessToken ? json.user_id.split(":")[1] : json.home_server);
|
localStorage.setItem("home_server", accessToken ? json.user_id.split(":")[1] : json.home_server);
|
||||||
storage.setItem("user_id", json.user_id);
|
localStorage.setItem("user_id", json.user_id);
|
||||||
storage.setItem("access_token", accessToken ? accessToken : json.access_token);
|
localStorage.setItem("access_token", accessToken ? accessToken : json.access_token);
|
||||||
storage.setItem("device_id", json.device_id);
|
localStorage.setItem("device_id", json.device_id);
|
||||||
storage.setItem("login_type", accessToken ? "accessToken" : "credentials");
|
localStorage.setItem("login_type", accessToken ? "accessToken" : "credentials");
|
||||||
|
|
||||||
// when doing access token auth, config is not fetched, so we need to do it here
|
// when doing access token auth, config is not fetched, so we need to do it here
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
@ -103,9 +102,9 @@ const authProvider: AuthProvider = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getIdentity: async () => {
|
getIdentity: async () => {
|
||||||
const access_token = storage.getItem("access_token");
|
const access_token = localStorage.getItem("access_token");
|
||||||
const user_id = storage.getItem("user_id");
|
const user_id = localStorage.getItem("user_id");
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
|
|
||||||
if (typeof access_token !== "string" || typeof user_id !== "string" || typeof base_url !== "string") {
|
if (typeof access_token !== "string" || typeof user_id !== "string" || typeof base_url !== "string") {
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
@ -143,8 +142,8 @@ const authProvider: AuthProvider = {
|
|||||||
logout: async () => {
|
logout: async () => {
|
||||||
console.log("logout");
|
console.log("logout");
|
||||||
|
|
||||||
const logout_api_url = storage.getItem("base_url") + "/_matrix/client/v3/logout";
|
const logout_api_url = localStorage.getItem("base_url") + "/_matrix/client/v3/logout";
|
||||||
const access_token = storage.getItem("access_token");
|
const access_token = localStorage.getItem("access_token");
|
||||||
|
|
||||||
const options: Options = {
|
const options: Options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -176,7 +175,7 @@ const authProvider: AuthProvider = {
|
|||||||
},
|
},
|
||||||
// called when the user navigates to a new location, to check for authentication
|
// called when the user navigates to a new location, to check for authentication
|
||||||
checkAuth: () => {
|
checkAuth: () => {
|
||||||
const access_token = storage.getItem("access_token");
|
const access_token = localStorage.getItem("access_token");
|
||||||
return typeof access_token === "string" ? Promise.resolve() : Promise.reject();
|
return typeof access_token === "string" ? Promise.resolve() : Promise.reject();
|
||||||
},
|
},
|
||||||
// called when the user navigates to a new location, to check for permissions / roles
|
// called when the user navigates to a new location, to check for permissions / roles
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import fetchMock from "jest-fetch-mock";
|
import fetchMock from "jest-fetch-mock";
|
||||||
|
|
||||||
import dataProvider from "./dataProvider";
|
import dataProvider from "./dataProvider";
|
||||||
import storage from "../storage";
|
|
||||||
|
|
||||||
fetchMock.enableMocks();
|
fetchMock.enableMocks();
|
||||||
|
|
||||||
@ -10,8 +9,8 @@ beforeEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("dataProvider", () => {
|
describe("dataProvider", () => {
|
||||||
storage.setItem("base_url", "http://localhost");
|
localStorage.setItem("base_url", "http://localhost");
|
||||||
storage.setItem("access_token", "access_token");
|
localStorage.setItem("access_token", "access_token");
|
||||||
|
|
||||||
it("fetches all users", async () => {
|
it("fetches all users", async () => {
|
||||||
fetchMock.mockResponseOnce(
|
fetchMock.mockResponseOnce(
|
||||||
|
@ -13,13 +13,12 @@ import {
|
|||||||
withLifecycleCallbacks,
|
withLifecycleCallbacks,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
|
||||||
import storage from "../storage";
|
import { returnMXID } from "../utils/mxid";
|
||||||
import { returnMXID } from "./synapse";
|
import { MatrixError, displayError } from "../utils/error";
|
||||||
import { MatrixError, displayError } from "../components/error";
|
|
||||||
|
|
||||||
// Adds the access token to all requests
|
// Adds the access token to all requests
|
||||||
const jsonClient = async (url: string, options: Options = {}) => {
|
const jsonClient = async (url: string, options: Options = {}) => {
|
||||||
const token = storage.getItem("access_token");
|
const token = localStorage.getItem("access_token");
|
||||||
console.log("httpClient " + url);
|
console.log("httpClient " + url);
|
||||||
if (token !== null) {
|
if (token !== null) {
|
||||||
options.user = {
|
options.user = {
|
||||||
@ -401,7 +400,7 @@ const resourceMap = {
|
|||||||
data: "media",
|
data: "media",
|
||||||
total: json => json.total,
|
total: json => json.total,
|
||||||
delete: (params: DeleteParams) => ({
|
delete: (params: DeleteParams) => ({
|
||||||
endpoint: `/_synapse/admin/v1/media/${storage.getItem("home_server")}/${params.id}`,
|
endpoint: `/_synapse/admin/v1/media/${localStorage.getItem("home_server")}/${params.id}`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
protect_media: {
|
protect_media: {
|
||||||
@ -418,11 +417,11 @@ const resourceMap = {
|
|||||||
quarantine_media: {
|
quarantine_media: {
|
||||||
map: (qm: UserMedia) => ({ id: qm.media_id }),
|
map: (qm: UserMedia) => ({ id: qm.media_id }),
|
||||||
create: (params: UserMedia) => ({
|
create: (params: UserMedia) => ({
|
||||||
endpoint: `/_synapse/admin/v1/media/quarantine/${storage.getItem("home_server")}/${params.media_id}`,
|
endpoint: `/_synapse/admin/v1/media/quarantine/${localStorage.getItem("home_server")}/${params.media_id}`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
}),
|
}),
|
||||||
delete: (params: DeleteParams) => ({
|
delete: (params: DeleteParams) => ({
|
||||||
endpoint: `/_synapse/admin/v1/media/unquarantine/${storage.getItem("home_server")}/${params.id}`,
|
endpoint: `/_synapse/admin/v1/media/unquarantine/${localStorage.getItem("home_server")}/${params.id}`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -567,7 +566,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
order_by: field,
|
order_by: field,
|
||||||
dir: getSearchOrder(order),
|
dir: getSearchOrder(order),
|
||||||
};
|
};
|
||||||
const homeserver = storage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
||||||
|
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
@ -586,7 +585,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
|
|
||||||
getOne: async (resource, params) => {
|
getOne: async (resource, params) => {
|
||||||
console.log("getOne " + resource);
|
console.log("getOne " + resource);
|
||||||
const homeserver = storage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
||||||
|
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
@ -598,8 +597,8 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
|
|
||||||
getMany: async (resource, params) => {
|
getMany: async (resource, params) => {
|
||||||
console.log("getMany " + resource);
|
console.log("getMany " + resource);
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const homeserver = storage.getItem("home_server");
|
const homeserver = localStorage.getItem("home_server");
|
||||||
if (!base_url || !(resource in resourceMap)) throw Error("base_url not set");
|
if (!base_url || !(resource in resourceMap)) throw Error("base_url not set");
|
||||||
|
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
@ -638,7 +637,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
dir: getSearchOrder(order),
|
dir: getSearchOrder(order),
|
||||||
};
|
};
|
||||||
|
|
||||||
const homeserver = storage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
||||||
|
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
@ -655,7 +654,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
|
|
||||||
update: async (resource, params) => {
|
update: async (resource, params) => {
|
||||||
console.log("update " + resource);
|
console.log("update " + resource);
|
||||||
const homeserver = storage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
||||||
|
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
@ -670,7 +669,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
|
|
||||||
updateMany: async (resource, params) => {
|
updateMany: async (resource, params) => {
|
||||||
console.log("updateMany " + resource);
|
console.log("updateMany " + resource);
|
||||||
const homeserver = storage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
||||||
|
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
@ -687,7 +686,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
|
|
||||||
create: async (resource, params) => {
|
create: async (resource, params) => {
|
||||||
console.log("create " + resource);
|
console.log("create " + resource);
|
||||||
const homeserver = storage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
||||||
|
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
@ -704,7 +703,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
|
|
||||||
createMany: async (resource: string, params: { ids: Identifier[]; data: RaRecord }) => {
|
createMany: async (resource: string, params: { ids: Identifier[]; data: RaRecord }) => {
|
||||||
console.log("createMany " + resource);
|
console.log("createMany " + resource);
|
||||||
const homeserver = storage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
||||||
|
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
@ -726,7 +725,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
|
|
||||||
delete: async (resource, params) => {
|
delete: async (resource, params) => {
|
||||||
console.log("delete " + resource);
|
console.log("delete " + resource);
|
||||||
const homeserver = storage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
||||||
|
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
@ -751,7 +750,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
|
|
||||||
deleteMany: async (resource, params) => {
|
deleteMany: async (resource, params) => {
|
||||||
console.log("deleteMany " + resource, "params", params);
|
console.log("deleteMany " + resource, "params", params);
|
||||||
const homeserver = storage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
||||||
|
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
@ -798,17 +797,17 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
deleteMedia: async ({ before_ts, size_gt = 0, keep_profiles = true }) => {
|
deleteMedia: async ({ before_ts, size_gt = 0, keep_profiles = true }) => {
|
||||||
const homeserver = storage.getItem("home_server"); // TODO only required for synapse < 1.78.0
|
const homeserver = localStorage.getItem("home_server"); // TODO only required for synapse < 1.78.0
|
||||||
const endpoint = `/_synapse/admin/v1/media/${homeserver}/delete?before_ts=${before_ts}&size_gt=${size_gt}&keep_profiles=${keep_profiles}`;
|
const endpoint = `/_synapse/admin/v1/media/${homeserver}/delete?before_ts=${before_ts}&size_gt=${size_gt}&keep_profiles=${keep_profiles}`;
|
||||||
|
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const endpoint_url = base_url + endpoint;
|
const endpoint_url = base_url + endpoint;
|
||||||
const { json } = await jsonClient(endpoint_url, { method: "POST" });
|
const { json } = await jsonClient(endpoint_url, { method: "POST" });
|
||||||
return json as DeleteMediaResult;
|
return json as DeleteMediaResult;
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadMedia: async ({ file, filename, content_type }: UploadMediaParams) => {
|
uploadMedia: async ({ file, filename, content_type }: UploadMediaParams) => {
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const uploadMediaURL = `${base_url}/_matrix/media/v3/upload`;
|
const uploadMediaURL = `${base_url}/_matrix/media/v3/upload`;
|
||||||
|
|
||||||
const { json } = await jsonClient(`${uploadMediaURL}?filename=${filename}`, {
|
const { json } = await jsonClient(`${uploadMediaURL}?filename=${filename}`, {
|
||||||
@ -822,18 +821,18 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
return json as UploadMediaResult;
|
return json as UploadMediaResult;
|
||||||
},
|
},
|
||||||
getFeatures: async (id: Identifier) => {
|
getFeatures: async (id: Identifier) => {
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const endpoint_url = `${base_url}/_synapse/admin/v1/experimental_features/${encodeURIComponent(returnMXID(id))}`;
|
const endpoint_url = `${base_url}/_synapse/admin/v1/experimental_features/${encodeURIComponent(returnMXID(id))}`;
|
||||||
const { json } = await jsonClient(endpoint_url);
|
const { json } = await jsonClient(endpoint_url);
|
||||||
return json.features as ExperimentalFeaturesModel;
|
return json.features as ExperimentalFeaturesModel;
|
||||||
},
|
},
|
||||||
updateFeatures: async (id: Identifier, features: ExperimentalFeaturesModel) => {
|
updateFeatures: async (id: Identifier, features: ExperimentalFeaturesModel) => {
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const endpoint_url = `${base_url}/_synapse/admin/v1/experimental_features/${encodeURIComponent(returnMXID(id))}`;
|
const endpoint_url = `${base_url}/_synapse/admin/v1/experimental_features/${encodeURIComponent(returnMXID(id))}`;
|
||||||
await jsonClient(endpoint_url, { method: "PUT", body: JSON.stringify({ features }) });
|
await jsonClient(endpoint_url, { method: "PUT", body: JSON.stringify({ features }) });
|
||||||
},
|
},
|
||||||
getRateLimits: async (id: Identifier) => {
|
getRateLimits: async (id: Identifier) => {
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const endpoint_url = `${base_url}/_synapse/admin/v1/users/${encodeURIComponent(returnMXID(id))}/override_ratelimit`;
|
const endpoint_url = `${base_url}/_synapse/admin/v1/users/${encodeURIComponent(returnMXID(id))}/override_ratelimit`;
|
||||||
const { json } = await jsonClient(endpoint_url);
|
const { json } = await jsonClient(endpoint_url);
|
||||||
return json as RateLimitsModel;
|
return json as RateLimitsModel;
|
||||||
@ -846,7 +845,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const endpoint_url = `${base_url}/_synapse/admin/v1/users/${encodeURIComponent(returnMXID(id))}/override_ratelimit`;
|
const endpoint_url = `${base_url}/_synapse/admin/v1/users/${encodeURIComponent(returnMXID(id))}/override_ratelimit`;
|
||||||
if (Object.keys(filtered).length === 0) {
|
if (Object.keys(filtered).length === 0) {
|
||||||
await jsonClient(endpoint_url, { method: "DELETE" });
|
await jsonClient(endpoint_url, { method: "DELETE" });
|
||||||
@ -856,7 +855,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
await jsonClient(endpoint_url, { method: "POST", body: JSON.stringify(filtered) });
|
await jsonClient(endpoint_url, { method: "POST", body: JSON.stringify(filtered) });
|
||||||
},
|
},
|
||||||
checkUsernameAvailability: async (username: string) => {
|
checkUsernameAvailability: async (username: string) => {
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const endpoint_url = `${base_url}/_synapse/admin/v1/username_available?username=${encodeURIComponent(username)}`;
|
const endpoint_url = `${base_url}/_synapse/admin/v1/username_available?username=${encodeURIComponent(username)}`;
|
||||||
try {
|
try {
|
||||||
const { json } = await jsonClient(endpoint_url);
|
const { json } = await jsonClient(endpoint_url);
|
||||||
@ -869,7 +868,7 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
makeRoomAdmin: async (room_id: string, user_id: string) => {
|
makeRoomAdmin: async (room_id: string, user_id: string) => {
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
|
|
||||||
const endpoint_url = `${base_url}/_synapse/admin/v1/rooms/${encodeURIComponent(room_id)}/make_room_admin`;
|
const endpoint_url = `${base_url}/_synapse/admin/v1/rooms/${encodeURIComponent(room_id)}/make_room_admin`;
|
||||||
try {
|
try {
|
||||||
@ -914,13 +913,13 @@ const dataProvider = withLifecycleCallbacks(baseDataProvider, [
|
|||||||
},
|
},
|
||||||
beforeDelete: async (params: DeleteParams<any>, dataProvider: DataProvider) => {
|
beforeDelete: async (params: DeleteParams<any>, dataProvider: DataProvider) => {
|
||||||
if (params.meta?.deleteMedia) {
|
if (params.meta?.deleteMedia) {
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const endpoint_url = `${base_url}/_synapse/admin/v1/users/${encodeURIComponent(returnMXID(params.id))}/media`;
|
const endpoint_url = `${base_url}/_synapse/admin/v1/users/${encodeURIComponent(returnMXID(params.id))}/media`;
|
||||||
await jsonClient(endpoint_url, { method: "DELETE" });
|
await jsonClient(endpoint_url, { method: "DELETE" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.meta?.redactEvents) {
|
if (params.meta?.redactEvents) {
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const endpoint_url = `${base_url}/_synapse/admin/v1/user/${encodeURIComponent(returnMXID(params.id))}/redact`;
|
const endpoint_url = `${base_url}/_synapse/admin/v1/user/${encodeURIComponent(returnMXID(params.id))}/redact`;
|
||||||
await jsonClient(endpoint_url, { method: "POST", body: JSON.stringify({ rooms: [] }) });
|
await jsonClient(endpoint_url, { method: "POST", body: JSON.stringify({ rooms: [] }) });
|
||||||
}
|
}
|
||||||
@ -931,13 +930,13 @@ const dataProvider = withLifecycleCallbacks(baseDataProvider, [
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
params.ids.map(async id => {
|
params.ids.map(async id => {
|
||||||
if (params.meta?.deleteMedia) {
|
if (params.meta?.deleteMedia) {
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const endpoint_url = `${base_url}/_synapse/admin/v1/users/${encodeURIComponent(returnMXID(id))}/media`;
|
const endpoint_url = `${base_url}/_synapse/admin/v1/users/${encodeURIComponent(returnMXID(id))}/media`;
|
||||||
await jsonClient(endpoint_url, { method: "DELETE" });
|
await jsonClient(endpoint_url, { method: "DELETE" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.meta?.redactEvents) {
|
if (params.meta?.redactEvents) {
|
||||||
const base_url = storage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const endpoint_url = `${base_url}/_synapse/admin/v1/user/${encodeURIComponent(returnMXID(id))}/redact`;
|
const endpoint_url = `${base_url}/_synapse/admin/v1/user/${encodeURIComponent(returnMXID(id))}/redact`;
|
||||||
await jsonClient(endpoint_url, { method: "POST", body: JSON.stringify({ rooms: [] }) });
|
await jsonClient(endpoint_url, { method: "POST", body: JSON.stringify({ rooms: [] }) });
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isValidBaseUrl, splitMxid } from "./synapse";
|
import { isValidBaseUrl, splitMxid } from "./matrix";
|
||||||
|
|
||||||
describe("splitMxid", () => {
|
describe("splitMxid", () => {
|
||||||
it("splits valid MXIDs", () =>
|
it("splits valid MXIDs", () =>
|
@ -1,7 +1,6 @@
|
|||||||
import { Identifier, fetchUtils } from "react-admin";
|
import { Identifier, fetchUtils } from "react-admin";
|
||||||
|
|
||||||
import storage from "../storage";
|
import { isMXID } from "../utils/mxid";
|
||||||
import { isMXID } from "../components/mxid";
|
|
||||||
|
|
||||||
export const splitMxid = mxid => {
|
export const splitMxid = mxid => {
|
||||||
const re = /^@(?<name>[a-zA-Z0-9._=\-/]+):(?<domain>[a-zA-Z0-9\-.]+\.[a-zA-Z]+)$/;
|
const re = /^@(?<name>[a-zA-Z0-9._=\-/]+):(?<domain>[a-zA-Z0-9\-.]+\.[a-zA-Z]+)$/;
|
||||||
@ -50,51 +49,7 @@ export const getSupportedFeatures = async baseUrl => {
|
|||||||
* @returns array of supported login flows
|
* @returns array of supported login flows
|
||||||
*/
|
*/
|
||||||
export const getSupportedLoginFlows = async baseUrl => {
|
export const getSupportedLoginFlows = async baseUrl => {
|
||||||
const loginFlowsUrl = `${baseUrl}/_matrix/client/r0/login`;
|
const loginFlowsUrl = `${baseUrl}/_matrix/client/v3/login`;
|
||||||
const response = await fetchUtils.fetchJson(loginFlowsUrl, { method: "GET" });
|
const response = await fetchUtils.fetchJson(loginFlowsUrl, { method: "GET" });
|
||||||
return response.json.flows;
|
return response.json.flows;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random MXID for current homeserver
|
|
||||||
* @returns full MXID as string
|
|
||||||
*/
|
|
||||||
export function generateRandomMxId(): string {
|
|
||||||
const homeserver = storage.getItem("home_server");
|
|
||||||
const characters = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
||||||
const localpart = Array.from(crypto.getRandomValues(new Uint32Array(8)))
|
|
||||||
.map(x => characters[x % characters.length])
|
|
||||||
.join("");
|
|
||||||
return `@${localpart}:${homeserver}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the full MXID from an arbitrary input
|
|
||||||
* @param input the input string
|
|
||||||
* @returns full MXID as string
|
|
||||||
*/
|
|
||||||
export function returnMXID(input: string | Identifier): string {
|
|
||||||
const homeserver = storage.getItem("home_server");
|
|
||||||
|
|
||||||
// Check if the input already looks like a valid MXID (i.e., starts with "@" and contains ":")
|
|
||||||
const mxidPattern = /^@[^@:]+:[^@:]+$/;
|
|
||||||
if (isMXID(input)) {
|
|
||||||
return input as string; // Already a valid MXID
|
|
||||||
}
|
|
||||||
|
|
||||||
// If input is not a valid MXID, assume it's a localpart and construct the MXID
|
|
||||||
const localpart = typeof input === 'string' && input.startsWith('@') ? input.slice(1) : input;
|
|
||||||
return `@${localpart}:${homeserver}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random user password
|
|
||||||
* @returns a new random password as string
|
|
||||||
*/
|
|
||||||
export function generateRandomPassword(length = 64): string {
|
|
||||||
const characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~`!@#$%^&*()_-+={[}]|:;'.?/<>,";
|
|
||||||
return Array.from(crypto.getRandomValues(new Uint32Array(length)))
|
|
||||||
.map(x => characters[x % characters.length])
|
|
||||||
.join("");
|
|
||||||
}
|
|
@ -1,5 +1,3 @@
|
|||||||
import storage from "../storage";
|
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
restrictBaseUrl: string | string[];
|
restrictBaseUrl: string | string[];
|
||||||
asManagedUsers: RegExp[];
|
asManagedUsers: RegExp[];
|
||||||
@ -32,7 +30,7 @@ export const FetchConfig = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if home_server is set, try to load https://home_server/.well-known/matrix/client
|
// if home_server is set, try to load https://home_server/.well-known/matrix/client
|
||||||
const homeserver = storage.getItem("home_server");
|
const homeserver = localStorage.getItem("home_server");
|
||||||
if (homeserver) {
|
if (homeserver) {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`https://${homeserver}/.well-known/matrix/client`);
|
const resp = await fetch(`https://${homeserver}/.well-known/matrix/client`);
|
||||||
@ -80,5 +78,5 @@ export const ClearConfig = () => {
|
|||||||
// config.json
|
// config.json
|
||||||
config = {} as Config;
|
config = {} as Config;
|
||||||
// session
|
// session
|
||||||
storage.clear();
|
localStorage.clear();
|
||||||
}
|
}
|
@ -1,9 +1,6 @@
|
|||||||
import storage from "../storage";
|
|
||||||
|
|
||||||
export const getServerAndMediaIdFromMxcUrl = (mxcUrl: string): { serverName: string, mediaId: string } => {
|
export const getServerAndMediaIdFromMxcUrl = (mxcUrl: string): { serverName: string, mediaId: string } => {
|
||||||
const re = /^mxc:\/\/([^/]+)\/(\w+)/;
|
const re = /^mxc:\/\/([^/]+)\/(\w+)/;
|
||||||
const ret = re.exec(mxcUrl);
|
const ret = re.exec(mxcUrl);
|
||||||
console.log("mxcClient " + ret);
|
|
||||||
if (ret == null) {
|
if (ret == null) {
|
||||||
throw new Error("Invalid mxcUrl");
|
throw new Error("Invalid mxcUrl");
|
||||||
}
|
}
|
||||||
@ -15,8 +12,8 @@ export const getServerAndMediaIdFromMxcUrl = (mxcUrl: string): { serverName: str
|
|||||||
export type MediaType = "thumbnail" | "original";
|
export type MediaType = "thumbnail" | "original";
|
||||||
|
|
||||||
export const fetchAuthenticatedMedia = async (mxcUrl: string, type: MediaType): Promise<Response> => {
|
export const fetchAuthenticatedMedia = async (mxcUrl: string, type: MediaType): Promise<Response> => {
|
||||||
const homeserver = storage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
const accessToken = storage.getItem("access_token");
|
const accessToken = localStorage.getItem("access_token");
|
||||||
|
|
||||||
const { serverName, mediaId } = getServerAndMediaIdFromMxcUrl(mxcUrl);
|
const { serverName, mediaId } = getServerAndMediaIdFromMxcUrl(mxcUrl);
|
||||||
if (!serverName || !mediaId) {
|
if (!serverName || !mediaId) {
|
||||||
@ -40,4 +37,4 @@ export const fetchAuthenticatedMedia = async (mxcUrl: string, type: MediaType):
|
|||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
16
src/utils/icons.ts
Normal file
16
src/utils/icons.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import AnnouncementIcon from '@mui/icons-material/Announcement';
|
||||||
|
import EngineeringIcon from '@mui/icons-material/Engineering';
|
||||||
|
import HelpCenterIcon from '@mui/icons-material/HelpCenter';
|
||||||
|
import SupportAgentIcon from '@mui/icons-material/SupportAgent';
|
||||||
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||||
|
|
||||||
|
export const Icons = {
|
||||||
|
Announcement: AnnouncementIcon,
|
||||||
|
Engineering: EngineeringIcon,
|
||||||
|
HelpCenter: HelpCenterIcon,
|
||||||
|
SupportAgent: SupportAgentIcon,
|
||||||
|
Default: OpenInNewIcon,
|
||||||
|
// Add more icons as needed
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultIcon = Icons.Default;
|
52
src/utils/mxid.ts
Normal file
52
src/utils/mxid.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Identifier } from "ra-core";
|
||||||
|
import { GetConfig } from "../utils/config";
|
||||||
|
|
||||||
|
const mxidPattern = /^@[^@:]+:[^@:]+$/;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if id is a valid Matrix ID (user)
|
||||||
|
* @param id The ID to check
|
||||||
|
* @returns Whether the ID is a valid Matrix ID
|
||||||
|
*/
|
||||||
|
export const isMXID = (id: string | Identifier): boolean => mxidPattern.test(id as string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user is managed by an application service
|
||||||
|
* @param id The user ID to check
|
||||||
|
* @returns Whether the user is managed by an application service
|
||||||
|
*/
|
||||||
|
export const isASManaged = (id: string | Identifier): boolean => {
|
||||||
|
return GetConfig().asManagedUsers.some(regex => regex.test(id as string));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random MXID for current homeserver
|
||||||
|
* @returns full MXID as string
|
||||||
|
*/
|
||||||
|
export function generateRandomMXID(): string {
|
||||||
|
const homeserver = localStorage.getItem("home_server");
|
||||||
|
const characters = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||||
|
const localpart = Array.from(crypto.getRandomValues(new Uint32Array(8)))
|
||||||
|
.map(x => characters[x % characters.length])
|
||||||
|
.join("");
|
||||||
|
return `@${localpart}:${homeserver}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the full MXID from an arbitrary input
|
||||||
|
* @param input the input string
|
||||||
|
* @returns full MXID as string
|
||||||
|
*/
|
||||||
|
export function returnMXID(input: string | Identifier): string {
|
||||||
|
const homeserver = localStorage.getItem("home_server");
|
||||||
|
|
||||||
|
// Check if the input already looks like a valid MXID (i.e., starts with "@" and contains ":")
|
||||||
|
const mxidPattern = /^@[^@:]+:[^@:]+$/;
|
||||||
|
if (isMXID(input)) {
|
||||||
|
return input as string; // Already a valid MXID
|
||||||
|
}
|
||||||
|
|
||||||
|
// If input is not a valid MXID, assume it's a localpart and construct the MXID
|
||||||
|
const localpart = typeof input === 'string' && input.startsWith('@') ? input.slice(1) : input;
|
||||||
|
return `@${localpart}:${homeserver}`;
|
||||||
|
}
|
10
src/utils/password.ts
Normal file
10
src/utils/password.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Generate a random user password
|
||||||
|
* @returns a new random password as string
|
||||||
|
*/
|
||||||
|
export function generateRandomPassword(length = 64): string {
|
||||||
|
const characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~`!@#$%^&*()_-+={[}]|:;'.?/<>,";
|
||||||
|
return Array.from(crypto.getRandomValues(new Uint32Array(length)))
|
||||||
|
.map(x => characters[x % characters.length])
|
||||||
|
.join("");
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user