diff --git a/package.json b/package.json
index 81354d2a..69de231c 100644
--- a/package.json
+++ b/package.json
@@ -49,10 +49,12 @@
"@emotion/styled": "^11.3.0",
"@haleos/ra-language-german": "^1.0.0",
"@haxqer/ra-language-chinese": "^4.16.2",
+ "@matrix-org/spec": "^1.10.1",
"@mui/icons-material": "^5.15.16",
"@mui/material": "^5.16.0",
"history": "^5.1.0",
"lodash": "^4.17.21",
+ "openapi-fetch": "^0.9.5",
"papaparse": "^5.4.1",
"query-string": "^7.1.1",
"ra-core": "^4.16.17",
@@ -91,8 +93,8 @@
],
"extends": [
"eslint:recommended",
- "plugin:@typescript-eslint/recommended",
- "plugin:@typescript-eslint/stylistic",
+ "plugin:@typescript-eslint/recommended-type-checked",
+ "plugin:@typescript-eslint/stylistic-type-checked",
"plugin:import/typescript",
"plugin:yaml/recommended"
],
diff --git a/src/App.tsx b/src/App.tsx
index 698c66f7..e3c2f40b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,6 +5,7 @@ import { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin
import { Route } from "react-router-dom";
import { ImportFeature } from "./components/ImportFeature";
+import { SynapseTranslationMessages } from "./i18n";
import germanMessages from "./i18n/de";
import englishMessages from "./i18n/en";
import frenchMessages from "./i18n/fr";
@@ -30,7 +31,7 @@ const messages = {
zh: chineseMessages,
};
const i18nProvider = polyglotI18nProvider(
- locale => (messages[locale] ? merge({}, messages.en, messages[locale]) : messages.en),
+ locale => (messages[locale] ? (merge({}, messages.en, messages[locale]) as SynapseTranslationMessages) : messages.en),
resolveBrowserLocale(),
[
{ locale: "en", name: "English" },
diff --git a/src/components/AvatarField.tsx b/src/components/AvatarField.tsx
index 0a8e2328..e4edbdd2 100644
--- a/src/components/AvatarField.tsx
+++ b/src/components/AvatarField.tsx
@@ -1,11 +1,11 @@
import { get } from "lodash";
-import { Avatar } from "@mui/material";
+import { Avatar, AvatarProps } from "@mui/material";
import { useRecordContext } from "react-admin";
-const AvatarField = ({ source, ...rest }) => {
+const AvatarField = ({ source, ...rest }: AvatarProps & { source: string }) => {
const record = useRecordContext(rest);
- const src = get(record, source)?.toString();
+ const src = get(record, source, "") as string;
const { alt, classes, sizes, sx, variant } = rest;
return ;
};
diff --git a/src/components/ImportFeature.tsx b/src/components/ImportFeature.tsx
index 062af3da..13552db3 100644
--- a/src/components/ImportFeature.tsx
+++ b/src/components/ImportFeature.tsx
@@ -21,7 +21,7 @@ const LOGGING = true;
const expectedFields = ["id", "displayname"].sort();
-function TranslatableOption({ value, text }) {
+function TranslatableOption({ value, text }: { value: string; text: string }) {
const translate = useTranslate();
return ;
}
@@ -81,7 +81,7 @@ const FilePicker = () => {
const dataProvider = useDataProvider();
- const onFileChange = async (e: ChangeEvent) => {
+ const onFileChange = (e: ChangeEvent) => {
if (progress !== null) return;
setValues([]);
@@ -106,11 +106,11 @@ const FilePicker = () => {
skipEmptyLines: true /* especially for a final EOL in the csv file */,
complete: result => {
if (result.errors) {
- setError(result.errors.map(e => e.toString()));
+ setError(result.errors.map(e => String(e)));
}
/* Papaparse is very lenient, we may be able to salvage
* the data in the file. */
- verifyCsv(result, { setValues, setStats, setError });
+ verifyCsv(result, setValues, setStats, setError);
},
});
} catch {
@@ -119,7 +119,12 @@ const FilePicker = () => {
}
};
- const verifyCsv = ({ data, meta, errors }: ParseResult, { setValues, setStats, setError }) => {
+ const verifyCsv = (
+ { data, meta, errors }: ParseResult,
+ setValues: (values: ImportLine[]) => void,
+ setStats: (stats: ChangeStats | null) => void,
+ setError: (error: string | string[] | null) => void
+ ) => {
/* First, verify the presence of required fields */
const missingFields = expectedFields.filter(eF => meta.fields?.find(mF => eF === mF));
@@ -206,29 +211,23 @@ const FilePicker = () => {
return true;
};
- const runImport = async () => {
+ const runImport = () => {
if (progress !== null) {
notify("import_users.errors.already_in_progress");
return;
}
- const results = await doImport(
- dataProvider,
- values,
- conflictMode,
- passwordMode,
- useridMode,
- dryRun,
- setProgress,
- setError
+ void doImport(dataProvider, values, conflictMode, passwordMode, useridMode, dryRun, setProgress, setError).then(
+ results => {
+ setImportResults(results);
+ // offer CSV download of skipped or errored records
+ // (so that the user doesn't have to filter out successful
+ // records manually when fixing stuff in the CSV)
+ setSkippedRecords(unparseCsv(results.skippedRecords));
+ if (LOGGING) console.log("Skipped records:");
+ if (LOGGING) console.log(skippedRecords);
+ }
);
- setImportResults(results);
- // offer CSV download of skipped or errored records
- // (so that the user doesn't have to filter out successful
- // records manually when fixing stuff in the CSV)
- setSkippedRecords(unparseCsv(results.skippedRecords));
- if (LOGGING) console.log("Skipped records:");
- if (LOGGING) console.log(skippedRecords);
};
// XXX every single one of the requests will restart the activity indicator
@@ -370,7 +369,7 @@ const FilePicker = () => {
element.click();
};
- const onConflictModeChanged = async (e: ChangeEvent) => {
+ const onConflictModeChanged = (e: ChangeEvent) => {
if (progress !== null) {
return;
}
@@ -387,7 +386,7 @@ const FilePicker = () => {
setPasswordMode(e.target.checked);
};
- const onUseridModeChanged = async (e: ChangeEvent) => {
+ const onUseridModeChanged = (e: ChangeEvent) => {
if (progress !== null) {
return;
}
diff --git a/src/components/ServerNotices.tsx b/src/components/ServerNotices.tsx
index f269a7fb..5fcd87d9 100644
--- a/src/components/ServerNotices.tsx
+++ b/src/components/ServerNotices.tsx
@@ -40,6 +40,7 @@ const ServerNoticeDialog = ({ open, onClose, onSubmit }) => {
{translate("resources.servernotices.helper.send")}
} onSubmit={onSubmit}>
+ {/* TODO: Use MUI form (does not require a record) */}
{
redirect={false}
translateOptions={{
id: record.id,
- name: record.display_name ? record.display_name : record.id,
+ name: String(record.display_name ?? record.id),
}}
/>
);
diff --git a/src/components/media.tsx b/src/components/media.tsx
index 893b1af7..39c4299c 100644
--- a/src/components/media.tsx
+++ b/src/components/media.tsx
@@ -35,7 +35,13 @@ import { dateParser } from "./date";
import { DeleteMediaParams, SynapseDataProvider } from "../synapse/dataProvider";
import { getMediaUrl } from "../synapse/synapse";
-const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
+interface DeleteMediaDialogProps {
+ open: boolean;
+ onClose: () => void;
+ onSubmit: (params: DeleteMediaParams) => void;
+}
+
+const DeleteMediaDialog = ({ open, onClose, onSubmit }: DeleteMediaDialogProps) => {
const translate = useTranslate();
const DeleteMediaToolbar = (props: ToolbarProps) => (
@@ -53,6 +59,7 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
{translate("delete_media.helper.send")}
} onSubmit={onSubmit}>
+ {/* TODO: Use MUI form (does not require a record) */}
({
display: "flex",
@@ -115,20 +114,14 @@ const LoginPage = () => {
console.log("Base URL is:", baseUrl);
console.log("SSO Token is:", ssoToken);
console.log("Let's try token login...");
- login(auth).catch(error => {
- alert(
- typeof error === "string"
- ? error
- : typeof error === "undefined" || !error.message
- ? "ra.auth.sign_in_error"
- : error.message
- );
+ login(auth).catch((error: Error | string | undefined) => {
+ alert(typeof error === "string" ? error : !error?.message ? "ra.auth.sign_in_error" : error.message);
console.error(error);
});
}
}
- const validateBaseUrl = value => {
+ const validateBaseUrl = (value: string) => {
if (!value.match(/^(http|https):\/\//)) {
return translate("synapseadmin.auth.protocol_error");
} else if (!value.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/)) {
@@ -140,16 +133,11 @@ const LoginPage = () => {
const handleSubmit = auth => {
setLoading(true);
- login(auth).catch(error => {
+ login(auth).catch((error: Error | string | undefined) => {
setLoading(false);
- notify(
- typeof error === "string"
- ? error
- : typeof error === "undefined" || !error.message
- ? "ra.auth.sign_in_error"
- : error.message,
- { type: "warning" }
- );
+ notify(typeof error === "string" ? error : !error?.message ? "ra.auth.sign_in_error" : error.message, {
+ type: "warning",
+ });
});
};
@@ -161,7 +149,7 @@ const LoginPage = () => {
window.location.href = ssoFullUrl;
};
- const UserData = ({ formData }) => {
+ const UserData = ({ formData }: { formData: UserDataFields }) => {
const form = useFormContext();
const [serverVersion, setServerVersion] = useState("");
const [matrixVersions, setMatrixVersions] = useState("");
@@ -171,7 +159,7 @@ const LoginPage = () => {
// check if username is a full qualified userId then set base_url accordingly
const domain = splitMxid(formData.username)?.domain;
if (domain) {
- getWellKnownUrl(domain).then(url => {
+ void getWellKnownUrl(domain).then(url => {
if (allowAnyBaseUrl || (allowMultipleBaseUrls && restrictBaseUrl.includes(url)))
form.setValue("base_url", url);
});
@@ -183,22 +171,25 @@ const LoginPage = () => {
form.setValue("base_url", restrictBaseUrl[0]);
}
if (!isValidBaseUrl(formData.base_url)) return;
+ const synapseClient = useSynapse(formData.base_url);
getServerVersion(formData.base_url)
.then(serverVersion => setServerVersion(`${translate("synapseadmin.auth.server_version")} ${serverVersion}`))
.catch(() => setServerVersion(""));
- getSupportedFeatures(formData.base_url)
+ synapseClient
+ .getSupportedFeatures()
.then(features =>
- setMatrixVersions(`${translate("synapseadmin.auth.supports_specs")} ${features.versions.join(", ")}`)
+ setMatrixVersions(`${translate("synapseadmin.auth.supports_specs")} ${features?.versions.join(", ")}`)
)
.catch(() => setMatrixVersions(""));
// Set SSO Url
- getSupportedLoginFlows(formData.base_url)
+ synapseClient
+ .getSupportedLoginFlows()
.then(loginFlows => {
- const supportPass = loginFlows.find(f => f.type === "m.login.password") !== undefined;
- const supportSSO = loginFlows.find(f => f.type === "m.login.sso") !== undefined;
+ const supportPass = loginFlows?.find(f => f.type === "m.login.password") !== undefined;
+ const supportSSO = loginFlows?.find(f => f.type === "m.login.sso") !== undefined;
setSupportPassAuth(supportPass);
setSSOBaseUrl(supportSSO ? formData.base_url : "");
})
@@ -286,7 +277,7 @@ const LoginPage = () => {
))}
- {formDataProps => }
+ >{formDataProps => }