cetirizine

An experimental matrix client written in reactjs utilizing tailwind and storybook
git clone git://archive.git.mtrnord.blog/MTRNord/cetirizine.git
Log | Files | Refs | README | LICENSE

ownUser.ts (7068B)


      1 import { DeviceId, UserId } from "@mtrnord/matrix-sdk-crypto-js";
      2 import { IErrorResp, ILoginFlows, ILoginResponse, IWellKnown } from "./api/apiTypes";
      3 import { MatrixClient, isRateLimitError } from "./client";
      4 import { MatrixE2EE } from "./e2ee";
      5 import { HostnameMissingError, HostnameMissingHTTPSError, LoginError, LoginFlowRequestError, LogoutError, NotLogeedInError, PasswordLoginNotSupportedError, PasswordMissingError, SDKError, SlidingSyncProxyNotFoundError, UsernameMissingError } from "./utils";
      6 
      7 export class OwnUser {
      8     public access_token?: string;
      9     public device_id?: string;
     10     public mxid?: string;
     11     // Hostname including "https://"
     12     public hostname?: string;
     13     public slidingSyncHostname?: string;
     14     public e2ee: MatrixE2EE;
     15 
     16     constructor(private client: MatrixClient) {
     17         this.e2ee = new MatrixE2EE(this.client, this);
     18     }
     19 
     20     // TODO: call logout endpoint on logout
     21     public async logout(): Promise<void | SDKError> {
     22         if (!this.mxid) {
     23             return new NotLogeedInError();
     24         }
     25         if (!this.access_token) {
     26             return new NotLogeedInError();
     27         }
     28         if (!this.hostname) {
     29             return new HostnameMissingError();
     30         }
     31         const resp = await fetch(`${this.hostname}/_matrix/client/v3/logout`, {
     32             method: "POST",
     33             headers: {
     34                 "Content-Type": "application/json",
     35                 "Authorization": `Bearer ${this.access_token}`
     36             },
     37         });
     38         if (!resp.ok) {
     39             return new LogoutError(resp);
     40         }
     41 
     42         this.access_token = undefined;
     43         this.device_id = undefined;
     44         this.slidingSyncHostname = undefined;
     45     }
     46 
     47 
     48 
     49     public async setHostname(hostname: string): Promise<SDKError | void> {
     50         if (!hostname.startsWith("https://")) {
     51             return new HostnameMissingHTTPSError();
     52         }
     53         if (!this.client.database) {
     54             await this.client.createDatabase();
     55         }
     56 
     57         // Write to database
     58         const tx = this.client.database?.transaction('loginInfo', 'readwrite');
     59         await tx?.store.put({
     60             userId: this.mxid!,
     61             hostname: hostname,
     62             slidingSyncHostname: this.slidingSyncHostname,
     63             access_token: this.access_token,
     64             device_id: this.device_id,
     65         });
     66         await tx?.done
     67 
     68         // Set in memory
     69         this.hostname = hostname;
     70     }
     71 
     72     private async getLoginFlows(): Promise<ILoginFlows | SDKError> {
     73         if (!this.hostname) {
     74             return new HostnameMissingError();
     75         }
     76         const resp = await fetch(`${this.hostname}/_matrix/client/v3/login`);
     77         if (!resp.ok) {
     78             return new LoginFlowRequestError(resp);
     79         }
     80         const json = await resp.json() as ILoginFlows;
     81         return json;
     82     }
     83 
     84     private async getWellKnown(): Promise<IWellKnown | SDKError> {
     85         if (!this.hostname) {
     86             return new HostnameMissingError();
     87         }
     88         const resp = await fetch(`${this.hostname}/.well-known/matrix/client`);
     89         if (!resp.ok) {
     90             return new LoginFlowRequestError(resp);
     91         }
     92         const json = await resp.json() as IWellKnown;
     93         return json;
     94     }
     95 
     96     public async passwordLogin(username: string, password: string, triesLeft = 5): Promise<SDKError | void> {
     97         if (!this.client.database) {
     98             await this.client.createDatabase();
     99         }
    100         if (!username) {
    101             return new UsernameMissingError();
    102         }
    103         if (!password) {
    104             return new PasswordMissingError();
    105         }
    106         this.mxid = username;
    107         await this.setHostname(`https://${username.split(':')[1]}`);
    108 
    109         try {
    110             const well_known = await this.getWellKnown();
    111             if (well_known instanceof SDKError) {
    112                 return well_known;
    113             }
    114             if (well_known["m.homeserver"]?.base_url) {
    115                 await this.setHostname(well_known["m.homeserver"].base_url);
    116             }
    117             if (well_known["org.matrix.msc3575.proxy"]?.url) {
    118                 // Write to database
    119                 const tx = this.client.database?.transaction('loginInfo', 'readwrite');
    120                 await tx?.store.put({
    121                     userId: this.mxid!,
    122                     hostname: this.hostname,
    123                     slidingSyncHostname: well_known["org.matrix.msc3575.proxy"].url,
    124                     access_token: this.access_token,
    125                     device_id: this.device_id,
    126                 });
    127                 await tx?.done
    128 
    129                 // Set the sliding sync proxy
    130                 this.slidingSyncHostname = well_known["org.matrix.msc3575.proxy"].url;
    131             } else {
    132                 return new SlidingSyncProxyNotFoundError();
    133             }
    134         } catch (e: any) {
    135             console.warn(`No well-known found for ${this.hostname}:\n${e}`);
    136         }
    137 
    138         const loginFlows = await this.getLoginFlows();
    139         if (loginFlows instanceof SDKError) {
    140             return loginFlows;
    141         }
    142         if ((loginFlows.flows.filter((flow) => flow.type === 'm.login.password')?.length || 0) == 0) {
    143             return new PasswordLoginNotSupportedError();
    144         }
    145 
    146         const resp = await fetch(`${this.hostname}/_matrix/client/v3/login`, {
    147             method: "POST",
    148             headers: {
    149                 "Content-Type": "application/json"
    150             },
    151             body: JSON.stringify({
    152                 type: "m.login.password",
    153                 identifier: {
    154                     type: 'm.id.user',
    155                     user: username,
    156                 },
    157                 user: username,
    158                 password: password
    159             })
    160         });
    161         if (!resp.ok) {
    162             return new LoginError(resp);
    163         }
    164         const json = await resp.json();
    165         if (isErrorResp(json)) {
    166             return new LoginError(undefined, json);
    167         }
    168         if (isRateLimitError(json)) {
    169             console.error(`Rate limited. Retrying in ${json.retry_after_ms}ms. ${triesLeft} tries left.`);
    170             await this.passwordLogin(username, password, triesLeft - 1);
    171         }
    172         if (isLoginResponse(json)) {
    173             // Write to database
    174             const tx = this.client.database?.transaction('loginInfo', 'readwrite');
    175             await tx?.store.put({
    176                 userId: json.user_id!,
    177                 hostname: this.hostname,
    178                 slidingSyncHostname: this.slidingSyncHostname,
    179                 access_token: json.access_token,
    180                 device_id: json.device_id,
    181             });
    182             await tx?.done
    183             this.access_token = json.access_token;
    184             this.device_id = json.device_id;
    185             this.mxid = json.user_id;
    186 
    187             await this.e2ee.initOlmMachine(new UserId(this.mxid), new DeviceId(this.device_id));
    188         }
    189     }
    190 }
    191 
    192 function isLoginResponse(arg: any): arg is ILoginResponse {
    193     return arg.access_token !== undefined;
    194 }
    195 
    196 function isErrorResp(arg: any): arg is IErrorResp {
    197     return arg.errcode !== undefined;
    198 }