commit 49becbdb392509dff8ac10c9591f67e9d32f7c2f
parent 3081db8ce62670475ed897bc4bb9e5b7e034a29a
Author: MTRNord <mtrnord1@gmail.com>
Date: Tue, 19 Sep 2023 11:26:58 +0200
Add more errors and remove throws
Diffstat:
7 files changed, 141 insertions(+), 56 deletions(-)
diff --git a/src/app/sdk/client.ts b/src/app/sdk/client.ts
@@ -20,7 +20,7 @@ import {
} from "idb";
import { DeviceId, UserId } from "@mtrnord/matrix-sdk-crypto-js";
import { MatrixSlidingSync } from "./slidingSync";
-import { NotLogeedInError, SDKError } from './utils';
+import { AccessTokenMissingError, HostnameMissingError, NotLogeedInError, ProfileFetchError, SDKError } from './utils';
export interface MatrixClientEvents {
// Used to notify about changes to the room list
@@ -119,8 +119,11 @@ export class MatrixClient extends EventEmitter {
this.emit("rooms", rooms);
}
- public async passwordLogin(username: string, password: string) {
- await this.user.passwordLogin(username, password);
+ public async passwordLogin(username: string, password: string): Promise<SDKError | void> {
+ const loginResp = await this.user.passwordLogin(username, password);
+ if (loginResp instanceof SDKError) {
+ return loginResp
+ }
this.sync.on("rooms", (rooms) => this.onSyncRooms(rooms));
}
@@ -241,10 +244,13 @@ export class MatrixClient extends EventEmitter {
return decryptedEvent;
}
- public async logout() {
+ public async logout(): Promise<void | SDKError> {
this.sync.logout();
this.sync.off("rooms", this.onSyncRooms);
- await this.user.logout();
+ const error = await this.user.logout();
+ if (error instanceof SDKError) {
+ return error;
+ }
this.user.e2ee.logoutE2ee();
if (this.user.mxid) {
const syncInfoTX = this.database?.transaction('syncInfo', 'readwrite');
@@ -383,7 +389,7 @@ export class MatrixClient extends EventEmitter {
await this.sync.startSync();
}
- public async fetchProfileInfo(userId: string): Promise<IProfileInfo> {
+ public async fetchProfileInfo(userId: string): Promise<SDKError | IProfileInfo> {
// @ts-ignore
if (globalThis.IS_STORYBOOK) {
await new Promise(r => setTimeout(r, 5000))
@@ -392,13 +398,13 @@ export class MatrixClient extends EventEmitter {
return this.profileInfo;
}
if (!this.user.hostname) {
- throw Error("Hostname must be set first");
+ return new HostnameMissingError()
}
if (!this.database) {
await this.createDatabase();
}
if (!this.user.access_token) {
- throw Error("Access token must be set first");
+ return new AccessTokenMissingError();
}
const resp = await fetch(`${this.user.hostname}/_matrix/client/v3/profile/${userId}`, {
headers: {
@@ -409,8 +415,7 @@ export class MatrixClient extends EventEmitter {
if (resp.status === 404 || resp.status === 403) {
return {} as IProfileInfo;
}
- console.error(resp);
- throw Error("Error fetching profile info. See console for error.");
+ return new ProfileFetchError(resp);
}
const json = await resp.json() as IProfileInfo;
if (json.avatar_url) {
@@ -496,10 +501,12 @@ export function useProfile() {
useEffect(() => {
client.fetchProfileInfo(client.mxid!).then((profile) => {
- if (!profile.displayname) {
- profile.displayname = client.mxid || "Unknown";
+ if (!(profile instanceof SDKError)) {
+ if (!profile.displayname) {
+ profile.displayname = client.mxid || "Unknown";
+ }
+ setProfile(profile);
}
- setProfile(profile);
})
}, [])
return profile;
diff --git a/src/app/sdk/ownUser.ts b/src/app/sdk/ownUser.ts
@@ -2,7 +2,7 @@ import { DeviceId, UserId } from "@mtrnord/matrix-sdk-crypto-js";
import { IErrorResp, ILoginFlows, ILoginResponse, IWellKnown } from "./api/apiTypes";
import { MatrixClient, isRateLimitError } from "./client";
import { MatrixE2EE } from "./e2ee";
-import { HostnameMissingError, NotLogeedInError, SDKError } from "./utils";
+import { HostnameMissingError, HostnameMissingHTTPSError, LoginError, LoginFlowRequestError, LogoutError, NotLogeedInError, PasswordLoginNotSupportedError, PasswordMissingError, SDKError, SlidingSyncProxyNotFoundError, UsernameMissingError } from "./utils";
export class OwnUser {
public access_token?: string;
@@ -36,8 +36,7 @@ export class OwnUser {
},
});
if (!resp.ok) {
- console.error(resp);
- throw Error("Error logging out. See console for error.");
+ return new LogoutError(resp);
}
this.access_token = undefined;
@@ -47,9 +46,9 @@ export class OwnUser {
- public async setHostname(hostname: string) {
+ public async setHostname(hostname: string): Promise<SDKError | void> {
if (!hostname.startsWith("https://")) {
- throw Error("Hostname must start with 'https://'");
+ return new HostnameMissingHTTPSError();
}
if (!this.client.database) {
await this.client.createDatabase();
@@ -70,47 +69,48 @@ export class OwnUser {
this.hostname = hostname;
}
- private async getLoginFlows(): Promise<ILoginFlows> {
+ private async getLoginFlows(): Promise<ILoginFlows | SDKError> {
if (!this.hostname) {
- throw Error("Hostname must be set first");
+ return new HostnameMissingError();
}
const resp = await fetch(`${this.hostname}/_matrix/client/v3/login`);
if (!resp.ok) {
- console.error(resp);
- throw Error("Error requesting login flows. See console for error.");
+ return new LoginFlowRequestError(resp);
}
const json = await resp.json() as ILoginFlows;
return json;
}
- private async getWellKnown(): Promise<IWellKnown> {
+ private async getWellKnown(): Promise<IWellKnown | SDKError> {
if (!this.hostname) {
- throw Error("Hostname must be set first");
+ return new HostnameMissingError();
}
const resp = await fetch(`${this.hostname}/.well-known/matrix/client`);
if (!resp.ok) {
- console.error(resp);
- throw Error("Error requesting login flows. See console for error.");
+ return new LoginFlowRequestError(resp);
}
const json = await resp.json() as IWellKnown;
return json;
}
- public async passwordLogin(username: string, password: string, triesLeft = 5) {
+ public async passwordLogin(username: string, password: string, triesLeft = 5): Promise<SDKError | void> {
if (!this.client.database) {
await this.client.createDatabase();
}
if (!username) {
- throw Error("Username must be set");
+ return new UsernameMissingError();
}
if (!password) {
- throw Error("Password must be set");
+ return new PasswordMissingError();
}
this.mxid = username;
await this.setHostname(`https://${username.split(':')[1]}`);
try {
const well_known = await this.getWellKnown();
+ if (well_known instanceof SDKError) {
+ return well_known;
+ }
if (well_known["m.homeserver"]?.base_url) {
await this.setHostname(well_known["m.homeserver"].base_url);
}
@@ -129,15 +129,18 @@ export class OwnUser {
// Set the sliding sync proxy
this.slidingSyncHostname = well_known["org.matrix.msc3575.proxy"].url;
} else {
- throw Error("No sliding sync proxy found");
+ return new SlidingSyncProxyNotFoundError();
}
} catch (e: any) {
console.warn(`No well-known found for ${this.hostname}:\n${e}`);
}
const loginFlows = await this.getLoginFlows();
+ if (loginFlows instanceof SDKError) {
+ return loginFlows;
+ }
if ((loginFlows.flows.filter((flow) => flow.type === 'm.login.password')?.length || 0) == 0) {
- throw Error("Password login is not supported by this homeserver");
+ return new PasswordLoginNotSupportedError();
}
const resp = await fetch(`${this.hostname}/_matrix/client/v3/login`, {
@@ -156,12 +159,11 @@ export class OwnUser {
})
});
if (!resp.ok) {
- console.error(resp);
- throw Error("Error logging in. See console for error.");
+ return new LoginError(resp);
}
const json = await resp.json();
if (isErrorResp(json)) {
- throw Error(`Error logging in: ${json.errcode}: ${json.error}`);
+ return new LoginError(undefined, json);
}
if (isRateLimitError(json)) {
console.error(`Rate limited. Retrying in ${json.retry_after_ms}ms. ${triesLeft} tries left.`);
diff --git a/src/app/sdk/room.ts b/src/app/sdk/room.ts
@@ -15,6 +15,7 @@ import { useEffect, useState } from "react";
import { EncryptionAlgorithm, EncryptionSettings, RoomId } from "@mtrnord/matrix-sdk-crypto-js";
import { OnlineState } from "./api/otherEnums";
import { MatrixE2EE } from "./e2ee";
+import { AccessTokenMissingError, FailedSendingError, HostnameMissingError, ProfileFetchError, SDKError } from "./utils";
export interface RoomEvents {
// Used to notify about changes to the event list
@@ -251,13 +252,13 @@ export class Room extends EventEmitter {
return isBot;
}
- public async joinedMembers(): Promise<IRoomMemberEvent[]> {
+ public async joinedMembers(): Promise<IRoomMemberEvent[] | SDKError> {
if (!this.has_all_users) {
if (!this.client.hostname) {
- throw Error("Hostname must be set first");
+ return new HostnameMissingError();
}
if (!this.client.accessToken) {
- throw Error("Access token must be set first");
+ return new AccessTokenMissingError();
}
// We dont have all members so we need to fetch them
const resp = await fetch(`${this.client.hostname}/_matrix/client/v3/rooms/${this.roomID}/members`, {
@@ -273,8 +274,7 @@ export class Room extends EventEmitter {
}
});
}
- console.error(resp);
- throw Error("Error fetching profile info. See console for error.");
+ return new ProfileFetchError(resp);
}
const json = await resp.json() as { chunk: IRoomMemberEvent[] };
this.stateEvents = [...this.stateEvents, ...json.chunk];
@@ -305,7 +305,7 @@ export class Room extends EventEmitter {
this.emit("events", this.getEvents());
}
- public async sendHtmlMessage(html: string, plainText: string, callbackLocalEcho: () => void): Promise<string> {
+ public async sendHtmlMessage(html: string, plainText: string, callbackLocalEcho: () => void): Promise<string | SDKError> {
const txn_id = Date.now().toString();
// @ts-ignore: Intentionally incomplete
const event = {
@@ -338,7 +338,7 @@ export class Room extends EventEmitter {
});
if (!resp.ok) {
this.deletePendingByEventID(event.event_id);
- throw new Error(`Failed to send message: ${resp.status} ${resp.statusText}`);
+ return new FailedSendingError(resp);
}
const json = await resp.json();
this.deletePendingByEventID(event.event_id);
@@ -362,7 +362,7 @@ export class Room extends EventEmitter {
});
if (!resp.ok) {
this.deletePendingByEventID(event.event_id);
- throw new Error(`Failed to send message: ${resp.status} ${resp.statusText}`);
+ return new FailedSendingError(resp);
}
const json = await resp.json();
this.deletePendingByEventID(event.event_id);
@@ -370,7 +370,7 @@ export class Room extends EventEmitter {
}
}
- public async sendTextMessage(text: string, callbackLocalEcho: () => void): Promise<string> {
+ public async sendTextMessage(text: string, callbackLocalEcho: () => void): Promise<string | SDKError> {
const txn_id = Date.now().toString();
// @ts-ignore: Intentionally incomplete
const event = {
@@ -401,7 +401,7 @@ export class Room extends EventEmitter {
});
if (!resp.ok) {
this.deletePendingByEventID(event.event_id);
- throw new Error(`Failed to send message: ${resp.status} ${resp.statusText}`);
+ return new FailedSendingError(resp);
}
const json = await resp.json();
this.deletePendingByEventID(event.event_id);
@@ -425,7 +425,7 @@ export class Room extends EventEmitter {
});
if (!resp.ok) {
this.deletePendingByEventID(event.event_id);
- throw new Error(`Failed to send message: ${resp.status} ${resp.statusText}`);
+ return new FailedSendingError(resp);
}
const json = await resp.json();
this.deletePendingByEventID(event.event_id);
diff --git a/src/app/sdk/slidingSync.ts b/src/app/sdk/slidingSync.ts
@@ -5,7 +5,7 @@ import { Room } from "./room";
import { OwnUser } from "./ownUser";
import { DeviceLists, UserId } from "@mtrnord/matrix-sdk-crypto-js";
import { IRoomEvent, IRoomStateEvent, isRoomStateEvent } from "./api/events";
-import { HostnameMissingError, NotLogeedInError, SDKError } from "./utils";
+import { HostnameMissingError, NotLogeedInError, SDKError, SyncError } from "./utils";
export interface MatrixSlidingSyncEvents {
// Used to notify about changes to the room list
@@ -462,10 +462,9 @@ export class MatrixSlidingSync extends EventEmitter {
return;
} else if (resp.status === 401) {
await this.logout();
- console.error(resp);
- console.error("Error syncing. See console for error.");
+ return new SyncError(resp);
} else {
- throw new Error(`Error syncing. See console for error:\n\n${await resp.text()}`);
+ return new SyncError(resp);
}
}
const json = await resp.json() as ISlidingSyncResp;
diff --git a/src/app/sdk/utils.ts b/src/app/sdk/utils.ts
@@ -1,3 +1,5 @@
+import { IErrorResp } from "./api/apiTypes"
+
export class SDKError extends Error {
protected constructor(msg?: string) {
if (msg) {
@@ -20,8 +22,80 @@ export class HostnameMissingError extends SDKError {
}
}
-export class OlmMachineNotSetup extends SDKError {
+export class HostnameMissingHTTPSError extends SDKError {
+ constructor() {
+ super("Hostname must start with 'https://'")
+ }
+}
+
+export class OlmMachineNotSetupError extends SDKError {
constructor() {
super("Olm machine must be set first")
}
+}
+
+export class AccessTokenMissingError extends SDKError {
+ constructor() {
+ super("Access token must be set first")
+ }
+}
+
+export class ProfileFetchError extends SDKError {
+ constructor(public readonly response: Response) {
+ super("Error fetching profile info.")
+ }
+}
+
+export class LogoutError extends SDKError {
+ constructor(public readonly response: Response) {
+ super("Error logging out.")
+ }
+}
+
+export class LoginFlowRequestError extends SDKError {
+ constructor(public readonly response: Response) {
+ super("Error requesting login flows.")
+ }
+}
+
+export class UsernameMissingError extends SDKError {
+ constructor() {
+ super("Username must be set")
+ }
+}
+
+export class PasswordMissingError extends SDKError {
+ constructor() {
+ super("Password must be set")
+ }
+}
+
+export class SlidingSyncProxyNotFoundError extends SDKError {
+ constructor() {
+ super("No sliding sync proxy found")
+ }
+}
+
+export class LoginError extends SDKError {
+ constructor(public readonly response?: Response, public readonly matrix_error?: IErrorResp) {
+ super("Error logging in.")
+ }
+}
+
+export class PasswordLoginNotSupportedError extends SDKError {
+ constructor() {
+ super("Password login is not supported by this homeserver")
+ }
+}
+
+export class FailedSendingError extends SDKError {
+ constructor(public readonly response: Response) {
+ super(`Failed to send message: ${response.status} ${response.statusText}`)
+ }
+}
+
+export class SyncError extends SDKError {
+ constructor(public readonly response: Response) {
+ super(`Error syncing`)
+ }
}
\ No newline at end of file
diff --git a/src/components/login/login.tsx b/src/components/login/login.tsx
@@ -18,11 +18,10 @@ const Login = memo(() => {
}
}
const startLogin = async () => {
- try {
- setLoginPending(true);
- await matrixClient.passwordLogin(username, password);
- } catch (e: any) {
- setLoginError(e.toString());
+ setLoginPending(true);
+ const error = await matrixClient.passwordLogin(username, password);
+ if (error) {
+ setLoginError(error.toString());
}
setLoginPending(false);
}
diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx
@@ -2,6 +2,7 @@ import './LoginPage.scss';
import Login from '../components/login/login';
import { memo, useContext, useEffect, useState } from 'react';
import { MatrixContext } from '../app/sdk/client';
+import { SDKError } from '../app/sdk/utils';
const LoginPage = memo(() => {
const matrixClient = useContext(MatrixContext);
@@ -10,7 +11,10 @@ const LoginPage = memo(() => {
useEffect(() => {
if (loading) {
// Ensure logout worked
- matrixClient.logout().then(() => {
+ matrixClient.logout().then((error) => {
+ if (error instanceof SDKError) {
+ console.error(error)
+ }
setLoading(false)
});
}