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.
|
||||
Each `menu` item can contain the following fields:
|
||||
* `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.
|
||||
[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:
|
||||
|
||||
* `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.
|
||||
* `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 { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin";
|
||||
import { createContext, useContext } from "react";
|
||||
import { Route } from "react-router-dom";
|
||||
|
||||
import { AdminLayout } from "./components/AdminLayout";
|
||||
import { ImportFeature } from "./components/ImportFeature";
|
||||
import AdminLayout from "./components/AdminLayout";
|
||||
import UserImport from "./components/UserImport";
|
||||
import germanMessages from "./i18n/de";
|
||||
import englishMessages from "./i18n/en";
|
||||
import frenchMessages from "./i18n/fr";
|
||||
@ -23,6 +24,7 @@ import users from "./resources/users";
|
||||
import authProvider from "./synapse/authProvider";
|
||||
import dataProvider from "./synapse/dataProvider";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { Config } from "./utils/config";
|
||||
|
||||
// TODO: Can we use lazy loading together with browser locale?
|
||||
const messages = {
|
||||
@ -49,7 +51,7 @@ const i18nProvider = polyglotI18nProvider(
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const App = () => (
|
||||
export const App = () => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Admin
|
||||
disableTelemetry
|
||||
@ -61,7 +63,7 @@ const App = () => (
|
||||
i18nProvider={i18nProvider}
|
||||
>
|
||||
<CustomRoutes>
|
||||
<Route path="/import_users" element={<ImportFeature />} />
|
||||
<Route path="/import_users" element={<UserImport />} />
|
||||
</CustomRoutes>
|
||||
<Resource {...users} />
|
||||
<Resource {...rooms} />
|
||||
@ -84,4 +86,8 @@ const App = () => (
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
export const AppContext = createContext({});
|
||||
|
||||
export const useAppContext = () => useContext(AppContext) as Config;
|
||||
|
||||
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 { LoginMethod } from "../pages/LoginPage";
|
||||
import { useEffect, useState, Suspense } from "react";
|
||||
import { Icons, DefaultIcon } from "./icons";
|
||||
import { MenuItem, GetConfig, ClearConfig } from "./config";
|
||||
import { Icons, DefaultIcon } from "../utils/icons";
|
||||
import { MenuItem, GetConfig, ClearConfig } from "../utils/config";
|
||||
import Footer from "./Footer";
|
||||
|
||||
const AdminUserMenu = () => {
|
||||
@ -96,3 +96,5 @@ export const AdminLayout = ({ children }) => {
|
||||
<Footer />
|
||||
</>
|
||||
};
|
||||
|
||||
export default AdminLayout;
|
||||
|
@ -3,8 +3,7 @@ import { Avatar, AvatarProps, Badge, Tooltip } from "@mui/material";
|
||||
import { FieldProps, useRecordContext, useTranslate } from "react-admin";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
||||
import { isMXID, isASManaged } from "./mxid";
|
||||
import storage from "../storage";
|
||||
import { isMXID, isASManaged } from "../utils/mxid";
|
||||
|
||||
const AvatarField = ({ source, ...rest }: AvatarProps & FieldProps) => {
|
||||
const { alt, classes, sizes, sx, variant } = rest;
|
||||
@ -74,7 +73,7 @@ const AvatarField = ({ source, ...rest }: AvatarProps & FieldProps) => {
|
||||
badge = "🛡️";
|
||||
tooltip = `${translate("resources.users.badge.system_managed")} (${tooltip})`;
|
||||
}
|
||||
if (storage.getItem("user_id") === record?.id) {
|
||||
if (localStorage.getItem("user_id") === record?.id) {
|
||||
badge = "🧙";
|
||||
tooltip = `${translate("resources.users.badge.you")} (${tooltip})`;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DeleteWithConfirmButton, DeleteWithConfirmButtonProps, useRecordContext } from "react-admin";
|
||||
import { isASManaged } from "./mxid";
|
||||
import { isASManaged } from "../utils/mxid";
|
||||
|
||||
export const DeviceRemoveButton = (props: DeleteWithConfirmButtonProps) => {
|
||||
const record = useRecordContext();
|
||||
@ -26,3 +26,5 @@ export const DeviceRemoveButton = (props: DeleteWithConfirmButtonProps) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeviceRemoveButton;
|
@ -93,3 +93,5 @@ export const ExperimentalFeaturesList = () => {
|
||||
</Stack>
|
||||
</>
|
||||
}
|
||||
|
||||
export default ExperimentalFeaturesList;
|
||||
|
@ -15,7 +15,8 @@ import {
|
||||
import { DataProvider, useTranslate } from "ra-core";
|
||||
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;
|
||||
|
||||
@ -274,7 +275,7 @@ const FilePicker = () => {
|
||||
// No need to do a bunch of cryptographic random number getting if
|
||||
// we are using neither a generated password nor a generated user id.
|
||||
if (useridMode === "ignore" || userRecord.id === undefined || userRecord.id === "") {
|
||||
userRecord.id = generateRandomMxId();
|
||||
userRecord.id = generateRandomMXID();
|
||||
}
|
||||
if (passwordMode === false || entry.password === undefined || entry.password === "") {
|
||||
userRecord.password = generateRandomPassword();
|
||||
@ -325,7 +326,7 @@ const FilePicker = () => {
|
||||
);
|
||||
} else {
|
||||
const newRecordData = Object.assign({}, recordData, {
|
||||
id: generateRandomMxId(),
|
||||
id: generateRandomMXID(),
|
||||
});
|
||||
retries++;
|
||||
if (retries > 512) {
|
||||
@ -570,4 +571,5 @@ const FilePicker = () => {
|
||||
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>
|
||||
}
|
||||
|
||||
export const UserRateLimits = () => {
|
||||
const UserRateLimits = () => {
|
||||
const translate = useTranslate();
|
||||
const notify = useNotify();
|
||||
const record = useRecordContext();
|
||||
@ -93,3 +93,5 @@ export const UserRateLimits = () => {
|
||||
</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";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
|
||||
import { dateParser } from "./date";
|
||||
import { dateParser } from "../utils/date";
|
||||
import { DeleteMediaParams, SynapseDataProvider } from "../synapse/dataProvider";
|
||||
import storage from "../storage";
|
||||
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
||||
|
||||
const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
|
||||
@ -385,7 +384,7 @@ export const MediaIDField = ({ source }) => {
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
const homeserver = storage.getItem("home_server");
|
||||
const homeserver = localStorage.getItem("home_server");
|
||||
|
||||
const mediaID = get(record, source)?.toString();
|
||||
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 App from "./App";
|
||||
import { FetchConfig, GetConfig } from "./components/config";
|
||||
import { AppContext } from "./AppContext";
|
||||
import storage from "./storage";
|
||||
import {App, AppContext } from "./App";
|
||||
import { FetchConfig, GetConfig } from "./utils/config";
|
||||
|
||||
await FetchConfig();
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { render, screen } from "@testing-library/react";
|
||||
import { AdminContext } from "react-admin";
|
||||
|
||||
import LoginPage from "./LoginPage";
|
||||
import { AppContext } from "../AppContext";
|
||||
import { AppContext } from "../App";
|
||||
import englishMessages from "../i18n/en";
|
||||
|
||||
const i18nProvider = polyglotI18nProvider(() => englishMessages, "en", [{ locale: "en", name: "English" }]);
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
} from "react-admin";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import LoginFormBox from "../components/LoginFormBox";
|
||||
import { useAppContext } from "../AppContext";
|
||||
import { useAppContext } from "../App";
|
||||
import {
|
||||
getServerVersion,
|
||||
getSupportedFeatures,
|
||||
@ -24,8 +24,7 @@ import {
|
||||
getWellKnownUrl,
|
||||
isValidBaseUrl,
|
||||
splitMxid,
|
||||
} from "../synapse/synapse";
|
||||
import storage from "../storage";
|
||||
} from "../synapse/matrix";
|
||||
import Footer from "../components/Footer";
|
||||
|
||||
export type LoginMethod = "credentials" | "accessToken";
|
||||
@ -46,7 +45,7 @@ const LoginPage = () => {
|
||||
const [locale, setLocale] = useLocaleState();
|
||||
const locales = useLocales();
|
||||
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 loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
|
||||
const [loginMethod, setLoginMethod] = useState<LoginMethod>("credentials");
|
||||
@ -60,8 +59,8 @@ const LoginPage = () => {
|
||||
console.log("SSO token is", ssoToken);
|
||||
// Prevent further requests
|
||||
window.history.replaceState({}, "", window.location.href.replace(loginToken[0], "#").split("#")[0]);
|
||||
const baseUrl = storage.getItem("sso_base_url");
|
||||
storage.removeItem("sso_base_url");
|
||||
const baseUrl = localStorage.getItem("sso_base_url");
|
||||
localStorage.removeItem("sso_base_url");
|
||||
if (baseUrl) {
|
||||
const auth = {
|
||||
base_url: baseUrl,
|
||||
@ -114,7 +113,7 @@ const LoginPage = () => {
|
||||
};
|
||||
|
||||
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(
|
||||
window.location.href
|
||||
)}`;
|
||||
|
@ -33,7 +33,7 @@ import {
|
||||
DateFieldProps,
|
||||
} from "react-admin";
|
||||
|
||||
import { DATE_FORMAT } from "../components/date";
|
||||
import { DATE_FORMAT } from "../utils/date";
|
||||
import { get } from "lodash";
|
||||
|
||||
const DestinationPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
Toolbar,
|
||||
} 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 validateUsesAllowed = [number()];
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
useTranslate,
|
||||
} from "react-admin";
|
||||
|
||||
import { DATE_FORMAT } from "../components/date";
|
||||
import { DATE_FORMAT } from "../utils/date";
|
||||
import { ReportMediaContent } from "../components/media";
|
||||
|
||||
const ReportPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
||||
|
@ -47,7 +47,7 @@ import {
|
||||
RoomDirectoryUnpublishButton,
|
||||
RoomDirectoryPublishButton,
|
||||
} from "./room_directory";
|
||||
import { DATE_FORMAT } from "../components/date";
|
||||
import { DATE_FORMAT } from "../utils/date";
|
||||
import DeleteRoomButton from "../components/DeleteRoomButton";
|
||||
import AvatarField from "../components/AvatarField";
|
||||
import { Room } from "../synapse/dataProvider";
|
||||
|
@ -69,15 +69,15 @@ import { Link } from "react-router-dom";
|
||||
|
||||
import AvatarField from "../components/AvatarField";
|
||||
import DeleteUserButton from "../components/DeleteUserButton";
|
||||
import { isASManaged } from "../components/mxid";
|
||||
import { isASManaged } from "../utils/mxid";
|
||||
import { ServerNoticeButton, ServerNoticeBulkButton } from "../components/ServerNotices";
|
||||
import { DATE_FORMAT } from "../components/date";
|
||||
import { DeviceRemoveButton } from "../components/devices";
|
||||
import { DATE_FORMAT } from "../utils/date";
|
||||
import DeviceRemoveButton from "../components/DeviceRemoveButton";
|
||||
import { MediaIDField, ProtectMediaButton, QuarantineMediaButton } from "../components/media";
|
||||
import { generateRandomPassword } from "../synapse/synapse";
|
||||
import { generateRandomPassword } from "../utils/password";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { ExperimentalFeaturesList } from "../components/ExperimentalFeatures";
|
||||
import { UserRateLimits } from "../components/UserRateLimits";
|
||||
import ExperimentalFeaturesList from "../components/ExperimentalFeatures";
|
||||
import UserRateLimits from "../components/UserRateLimits";
|
||||
import { User, UsernameAvailabilityResult } from "../synapse/dataProvider";
|
||||
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 authProvider from "./authProvider";
|
||||
import storage from "../storage";
|
||||
import { HttpError } from "ra-core";
|
||||
|
||||
fetchMock.enableMocks();
|
||||
@ -9,7 +8,7 @@ fetchMock.enableMocks();
|
||||
describe("authProvider", () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.resetMocks();
|
||||
storage.clear();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe("login", () => {
|
||||
@ -38,10 +37,10 @@ describe("authProvider", () => {
|
||||
}),
|
||||
method: "POST",
|
||||
});
|
||||
expect(storage.getItem("base_url")).toEqual("http://example.com");
|
||||
expect(storage.getItem("user_id")).toEqual("@user:example.com");
|
||||
expect(storage.getItem("access_token")).toEqual("foobar");
|
||||
expect(storage.getItem("device_id")).toEqual("some_device");
|
||||
expect(localStorage.getItem("base_url")).toEqual("http://example.com");
|
||||
expect(localStorage.getItem("user_id")).toEqual("@user:example.com");
|
||||
expect(localStorage.getItem("access_token")).toEqual("foobar");
|
||||
expect(localStorage.getItem("device_id")).toEqual("some_device");
|
||||
});
|
||||
});
|
||||
|
||||
@ -69,16 +68,16 @@ describe("authProvider", () => {
|
||||
}),
|
||||
method: "POST",
|
||||
});
|
||||
expect(storage.getItem("base_url")).toEqual("https://example.com");
|
||||
expect(storage.getItem("user_id")).toEqual("@user:example.com");
|
||||
expect(storage.getItem("access_token")).toEqual("foobar");
|
||||
expect(storage.getItem("device_id")).toEqual("some_device");
|
||||
expect(localStorage.getItem("base_url")).toEqual("https://example.com");
|
||||
expect(localStorage.getItem("user_id")).toEqual("@user:example.com");
|
||||
expect(localStorage.getItem("access_token")).toEqual("foobar");
|
||||
expect(localStorage.getItem("device_id")).toEqual("some_device");
|
||||
});
|
||||
|
||||
describe("logout", () => {
|
||||
it("should remove the access_token from storage", async () => {
|
||||
storage.setItem("base_url", "example.com");
|
||||
storage.setItem("access_token", "foo");
|
||||
localStorage.setItem("base_url", "example.com");
|
||||
localStorage.setItem("access_token", "foo");
|
||||
fetchMock.mockResponse(JSON.stringify({}));
|
||||
|
||||
await authProvider.logout(null);
|
||||
@ -91,7 +90,7 @@ describe("authProvider", () => {
|
||||
method: "POST",
|
||||
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 () => {
|
||||
storage.setItem("access_token", "foobar");
|
||||
localStorage.setItem("access_token", "foobar");
|
||||
|
||||
await expect(authProvider.checkAuth({})).resolves.toBeUndefined();
|
||||
});
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { AuthProvider, HttpError, Options, fetchUtils } from "react-admin";
|
||||
|
||||
import storage from "../storage";
|
||||
import { MatrixError, displayError } from "../components/error";
|
||||
import { MatrixError, displayError } from "../utils/error";
|
||||
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
||||
import { FetchConfig, ClearConfig } from "../components/config";
|
||||
import { FetchConfig, ClearConfig } from "../utils/config";
|
||||
|
||||
const authProvider: AuthProvider = {
|
||||
// called when the user attempts to log in
|
||||
@ -26,7 +25,7 @@ const authProvider: AuthProvider = {
|
||||
body: JSON.stringify(
|
||||
Object.assign(
|
||||
{
|
||||
device_id: storage.getItem("device_id"),
|
||||
device_id: localStorage.getItem("device_id"),
|
||||
initial_device_display_name: "Synapse Admin",
|
||||
},
|
||||
loginToken
|
||||
@ -52,11 +51,11 @@ const authProvider: AuthProvider = {
|
||||
if (!base_url) {
|
||||
// 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
|
||||
storage.removeItem("base_url")
|
||||
localStorage.removeItem("base_url")
|
||||
throw new Error("Homeserver URL is required.");
|
||||
}
|
||||
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);
|
||||
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);
|
||||
const json = response.json;
|
||||
storage.setItem("home_server", accessToken ? json.user_id.split(":")[1] : json.home_server);
|
||||
storage.setItem("user_id", json.user_id);
|
||||
storage.setItem("access_token", accessToken ? accessToken : json.access_token);
|
||||
storage.setItem("device_id", json.device_id);
|
||||
storage.setItem("login_type", accessToken ? "accessToken" : "credentials");
|
||||
localStorage.setItem("home_server", accessToken ? json.user_id.split(":")[1] : json.home_server);
|
||||
localStorage.setItem("user_id", json.user_id);
|
||||
localStorage.setItem("access_token", accessToken ? accessToken : json.access_token);
|
||||
localStorage.setItem("device_id", json.device_id);
|
||||
localStorage.setItem("login_type", accessToken ? "accessToken" : "credentials");
|
||||
|
||||
// when doing access token auth, config is not fetched, so we need to do it here
|
||||
if (accessToken) {
|
||||
@ -103,9 +102,9 @@ const authProvider: AuthProvider = {
|
||||
}
|
||||
},
|
||||
getIdentity: async () => {
|
||||
const access_token = storage.getItem("access_token");
|
||||
const user_id = storage.getItem("user_id");
|
||||
const base_url = storage.getItem("base_url");
|
||||
const access_token = localStorage.getItem("access_token");
|
||||
const user_id = localStorage.getItem("user_id");
|
||||
const base_url = localStorage.getItem("base_url");
|
||||
|
||||
if (typeof access_token !== "string" || typeof user_id !== "string" || typeof base_url !== "string") {
|
||||
return Promise.reject();
|
||||
@ -143,8 +142,8 @@ const authProvider: AuthProvider = {
|
||||
logout: async () => {
|
||||
console.log("logout");
|
||||
|
||||
const logout_api_url = storage.getItem("base_url") + "/_matrix/client/v3/logout";
|
||||
const access_token = storage.getItem("access_token");
|
||||
const logout_api_url = localStorage.getItem("base_url") + "/_matrix/client/v3/logout";
|
||||
const access_token = localStorage.getItem("access_token");
|
||||
|
||||
const options: Options = {
|
||||
method: "POST",
|
||||
@ -176,7 +175,7 @@ const authProvider: AuthProvider = {
|
||||
},
|
||||
// called when the user navigates to a new location, to check for authentication
|
||||
checkAuth: () => {
|
||||
const access_token = storage.getItem("access_token");
|
||||
const access_token = localStorage.getItem("access_token");
|
||||
return typeof access_token === "string" ? Promise.resolve() : Promise.reject();
|
||||
},
|
||||
// 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 dataProvider from "./dataProvider";
|
||||
import storage from "../storage";
|
||||
|
||||
fetchMock.enableMocks();
|
||||
|
||||
@ -10,8 +9,8 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe("dataProvider", () => {
|
||||
storage.setItem("base_url", "http://localhost");
|
||||
storage.setItem("access_token", "access_token");
|
||||
localStorage.setItem("base_url", "http://localhost");
|
||||
localStorage.setItem("access_token", "access_token");
|
||||
|
||||
it("fetches all users", async () => {
|
||||
fetchMock.mockResponseOnce(
|
||||
|
@ -13,13 +13,12 @@ import {
|
||||
withLifecycleCallbacks,
|
||||
} from "react-admin";
|
||||
|
||||
import storage from "../storage";
|
||||
import { returnMXID } from "./synapse";
|
||||
import { MatrixError, displayError } from "../components/error";
|
||||
import { returnMXID } from "../utils/mxid";
|
||||
import { MatrixError, displayError } from "../utils/error";
|
||||
|
||||
// Adds the access token to all requests
|
||||
const jsonClient = async (url: string, options: Options = {}) => {
|
||||
const token = storage.getItem("access_token");
|
||||
const token = localStorage.getItem("access_token");
|
||||
console.log("httpClient " + url);
|
||||
if (token !== null) {
|
||||
options.user = {
|
||||
@ -401,7 +400,7 @@ const resourceMap = {
|
||||
data: "media",
|
||||
total: json => json.total,
|
||||
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: {
|
||||
@ -418,11 +417,11 @@ const resourceMap = {
|
||||
quarantine_media: {
|
||||
map: (qm: UserMedia) => ({ id: qm.media_id }),
|
||||
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",
|
||||
}),
|
||||
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",
|
||||
}),
|
||||
},
|
||||
@ -567,7 +566,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
order_by: field,
|
||||
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");
|
||||
|
||||
const res = resourceMap[resource];
|
||||
@ -586,7 +585,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
|
||||
getOne: async (resource, params) => {
|
||||
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");
|
||||
|
||||
const res = resourceMap[resource];
|
||||
@ -598,8 +597,8 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
|
||||
getMany: async (resource, params) => {
|
||||
console.log("getMany " + resource);
|
||||
const base_url = storage.getItem("base_url");
|
||||
const homeserver = storage.getItem("home_server");
|
||||
const base_url = localStorage.getItem("base_url");
|
||||
const homeserver = localStorage.getItem("home_server");
|
||||
if (!base_url || !(resource in resourceMap)) throw Error("base_url not set");
|
||||
|
||||
const res = resourceMap[resource];
|
||||
@ -638,7 +637,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
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");
|
||||
|
||||
const res = resourceMap[resource];
|
||||
@ -655,7 +654,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
|
||||
update: async (resource, params) => {
|
||||
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");
|
||||
|
||||
const res = resourceMap[resource];
|
||||
@ -670,7 +669,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
|
||||
updateMany: async (resource, params) => {
|
||||
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");
|
||||
|
||||
const res = resourceMap[resource];
|
||||
@ -687,7 +686,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
|
||||
create: async (resource, params) => {
|
||||
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");
|
||||
|
||||
const res = resourceMap[resource];
|
||||
@ -704,7 +703,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
|
||||
createMany: async (resource: string, params: { ids: Identifier[]; data: RaRecord }) => {
|
||||
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");
|
||||
|
||||
const res = resourceMap[resource];
|
||||
@ -726,7 +725,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
|
||||
delete: async (resource, params) => {
|
||||
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");
|
||||
|
||||
const res = resourceMap[resource];
|
||||
@ -751,7 +750,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
|
||||
deleteMany: async (resource, 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");
|
||||
|
||||
const res = resourceMap[resource];
|
||||
@ -798,17 +797,17 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
* @returns
|
||||
*/
|
||||
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 base_url = storage.getItem("base_url");
|
||||
const base_url = localStorage.getItem("base_url");
|
||||
const endpoint_url = base_url + endpoint;
|
||||
const { json } = await jsonClient(endpoint_url, { method: "POST" });
|
||||
return json as DeleteMediaResult;
|
||||
},
|
||||
|
||||
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 { json } = await jsonClient(`${uploadMediaURL}?filename=${filename}`, {
|
||||
@ -822,18 +821,18 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
return json as UploadMediaResult;
|
||||
},
|
||||
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 { json } = await jsonClient(endpoint_url);
|
||||
return json.features as 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))}`;
|
||||
await jsonClient(endpoint_url, { method: "PUT", body: JSON.stringify({ features }) });
|
||||
},
|
||||
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 { json } = await jsonClient(endpoint_url);
|
||||
return json as RateLimitsModel;
|
||||
@ -846,7 +845,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
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`;
|
||||
if (Object.keys(filtered).length === 0) {
|
||||
await jsonClient(endpoint_url, { method: "DELETE" });
|
||||
@ -856,7 +855,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
await jsonClient(endpoint_url, { method: "POST", body: JSON.stringify(filtered) });
|
||||
},
|
||||
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)}`;
|
||||
try {
|
||||
const { json } = await jsonClient(endpoint_url);
|
||||
@ -869,7 +868,7 @@ const baseDataProvider: SynapseDataProvider = {
|
||||
}
|
||||
},
|
||||
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`;
|
||||
try {
|
||||
@ -914,13 +913,13 @@ const dataProvider = withLifecycleCallbacks(baseDataProvider, [
|
||||
},
|
||||
beforeDelete: async (params: DeleteParams<any>, dataProvider: DataProvider) => {
|
||||
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`;
|
||||
await jsonClient(endpoint_url, { method: "DELETE" });
|
||||
}
|
||||
|
||||
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`;
|
||||
await jsonClient(endpoint_url, { method: "POST", body: JSON.stringify({ rooms: [] }) });
|
||||
}
|
||||
@ -931,13 +930,13 @@ const dataProvider = withLifecycleCallbacks(baseDataProvider, [
|
||||
await Promise.all(
|
||||
params.ids.map(async id => {
|
||||
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`;
|
||||
await jsonClient(endpoint_url, { method: "DELETE" });
|
||||
}
|
||||
|
||||
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`;
|
||||
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", () => {
|
||||
it("splits valid MXIDs", () =>
|
@ -1,7 +1,6 @@
|
||||
import { Identifier, fetchUtils } from "react-admin";
|
||||
|
||||
import storage from "../storage";
|
||||
import { isMXID } from "../components/mxid";
|
||||
import { isMXID } from "../utils/mxid";
|
||||
|
||||
export const splitMxid = mxid => {
|
||||
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
|
||||
*/
|
||||
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" });
|
||||
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 {
|
||||
restrictBaseUrl: string | string[];
|
||||
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
|
||||
const homeserver = storage.getItem("home_server");
|
||||
const homeserver = localStorage.getItem("home_server");
|
||||
if (homeserver) {
|
||||
try {
|
||||
const resp = await fetch(`https://${homeserver}/.well-known/matrix/client`);
|
||||
@ -80,5 +78,5 @@ export const ClearConfig = () => {
|
||||
// config.json
|
||||
config = {} as Config;
|
||||
// session
|
||||
storage.clear();
|
||||
localStorage.clear();
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
import storage from "../storage";
|
||||
|
||||
export const getServerAndMediaIdFromMxcUrl = (mxcUrl: string): { serverName: string, mediaId: string } => {
|
||||
const re = /^mxc:\/\/([^/]+)\/(\w+)/;
|
||||
const ret = re.exec(mxcUrl);
|
||||
console.log("mxcClient " + ret);
|
||||
if (ret == null) {
|
||||
throw new Error("Invalid mxcUrl");
|
||||
}
|
||||
@ -15,8 +12,8 @@ export const getServerAndMediaIdFromMxcUrl = (mxcUrl: string): { serverName: str
|
||||
export type MediaType = "thumbnail" | "original";
|
||||
|
||||
export const fetchAuthenticatedMedia = async (mxcUrl: string, type: MediaType): Promise<Response> => {
|
||||
const homeserver = storage.getItem("base_url");
|
||||
const accessToken = storage.getItem("access_token");
|
||||
const homeserver = localStorage.getItem("base_url");
|
||||
const accessToken = localStorage.getItem("access_token");
|
||||
|
||||
const { serverName, mediaId } = getServerAndMediaIdFromMxcUrl(mxcUrl);
|
||||
if (!serverName || !mediaId) {
|
||||
@ -40,4 +37,4 @@ export const fetchAuthenticatedMedia = async (mxcUrl: string, type: MediaType):
|
||||
});
|
||||
|
||||
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