commit d6c587f5c12d2c4458502e9162926cf196870931
parent e9c6b3c409a9b4377202db77c6faa035be6d06c9
Author: MTRNord <mtrnord1@gmail.com>
Date: Fri, 5 May 2023 00:03:10 +0200
Initialize rust-crypto and add send button (missing actual function for now)
Diffstat:
5 files changed, 207 insertions(+), 46 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
@@ -7,11 +7,6 @@ import LoginPage from './pages/LoginPage';
import MainPage from './pages/MainPage';
import { ProtectedRoute } from './app/protectedRoute';
import { memo } from "react";
-import { initAsync, start } from "@matrix-org/matrix-sdk-crypto-js";
-
-initAsync().then(() => {
- start();
-})
function App() {
return <BrowserRouter basename={import.meta.env.BASE_URL}>
diff --git a/src/app/sdk/client.ts b/src/app/sdk/client.ts
@@ -27,6 +27,10 @@ import {
IDBPDatabase,
openDB
} from "idb";
+import { DeviceId, KeysBackupRequest, KeysUploadRequest, OlmMachine, RequestType, RoomMessageRequest, SignatureUploadRequest, UserId } from "@matrix-org/matrix-sdk-crypto-js";
+import { KeysQueryRequest } from "@matrix-org/matrix-sdk-crypto-js";
+import { KeysClaimRequest } from "@matrix-org/matrix-sdk-crypto-js";
+import { ToDeviceRequest } from "@matrix-org/matrix-sdk-crypto-js";
export interface MatrixClientEvents {
// Used to notify about changes to the room list
@@ -112,6 +116,7 @@ export class MatrixClient extends EventEmitter {
private profileInfo?: IProfileInfo;
private lastRanges?: { [key: string]: number[][] };
private lastTxnID?: string;
+ private olmMachine?: OlmMachine;
public get isLoggedIn(): boolean {
return this.access_token !== undefined;
@@ -139,6 +144,7 @@ export class MatrixClient extends EventEmitter {
avatar_url: loginInfo[0].avatarUrl,
displayname: loginInfo[0].displayName,
};
+ instance.olmMachine = await OlmMachine.initialize(new UserId(instance.mxid), new DeviceId(instance.device_id!), "cetirizine-crypto");
// Load sync info
const syncTx = instance.database?.transaction('syncInfo', 'readonly');
@@ -331,6 +337,145 @@ export class MatrixClient extends EventEmitter {
this.shiftRight(listKey, ranges, max + 1, index);
}
+ private async sendIdentifyAndOneTimeKeys() {
+ if (!this.isLoggedIn) {
+ throw Error("Not logged in");
+ }
+ if (!this.slidingSyncHostname) {
+ throw Error("Hostname must be set first");
+ }
+ if (!this.olmMachine) {
+ throw Error("Olm machine must be set first");
+ }
+
+ const outgoing_requests = await this.olmMachine.outgoingRequests();
+
+ for (const request of outgoing_requests) {
+ // Check which type the request is
+ if (request.type === RequestType.KeysUpload) {
+ // Send the key
+ const request_typed = request as KeysUploadRequest;
+ const response = await fetch(
+ `${this.hostname}/_matrix/client/v3/keys/upload`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${this.access_token}`
+ },
+ body: request_typed.body
+ }
+ )
+ if (!response.ok) {
+ console.error("Failed to upload keys", response);
+ }
+ this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text());
+ } else if (request.type === RequestType.KeysQuery) {
+ const request_typed = request as KeysQueryRequest;
+ const response = await fetch(
+ `${this.hostname}/_matrix/client/v3/keys/query`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${this.access_token}`
+ },
+ body: request_typed.body
+ }
+ )
+ if (!response.ok) {
+ console.error("Failed to query keys", response);
+ }
+ this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text());
+ } else if (request.type === RequestType.KeysClaim) {
+ const request_typed = request as KeysClaimRequest;
+ const response = await fetch(
+ `${this.hostname}/_matrix/client/v3/keys/claim`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${this.access_token}`
+ },
+ body: request_typed.body
+ }
+ )
+ if (!response.ok) {
+ console.error("Failed to claim keys", response);
+ }
+ this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text());
+ } else if (request.type === RequestType.ToDevice) {
+ const request_typed = request as ToDeviceRequest;
+ const response = await fetch(
+ `${this.hostname}/_matrix/client/v3/sendToDevice/${request_typed.event_type}/${request_typed.txn_id}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${this.access_token}`
+ },
+ body: request_typed.body
+ }
+ )
+ if (!response.ok) {
+ console.error("Failed to send to device", response);
+ }
+ this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text());
+ } else if (request.type === RequestType.SignatureUpload) {
+ const request_typed = request as SignatureUploadRequest;
+ const response = await fetch(
+ `${this.hostname}/_matrix/client/v3/keys/signatures/upload`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${this.access_token}`
+ },
+ body: request_typed.body
+ }
+ )
+ if (!response.ok) {
+ console.error("Failed to upload signatures", response);
+ }
+ this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text());
+ } else if (request.type === RequestType.RoomMessage) {
+ const request_typed = request as RoomMessageRequest;
+ const response = await fetch(
+ `${this.hostname}/_matrix/client/v3/rooms/${request_typed.room_id}/send/${request_typed.event_type}/${request_typed.txn_id}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${this.access_token}`
+ },
+ body: request_typed.body
+ }
+ )
+ if (!response.ok) {
+ console.error("Failed to send message", response);
+ }
+ this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text());
+ } else if (request.type === RequestType.KeysBackup) {
+ const request_typed = request as KeysBackupRequest;
+ const response = await fetch(
+ `${this.hostname}/_matrix/client/v3/room_keys/keys`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${this.access_token}`
+ },
+ body: request_typed.body
+ }
+ )
+ if (!response.ok) {
+ console.error("Failed to backup keys", response);
+ }
+ this.olmMachine.markRequestAsSent(request_typed.id!, request_typed.type, await response.text());
+ }
+ }
+ }
+
private async sync() {
if (!this.isLoggedIn) {
throw Error("Not logged in");
@@ -339,6 +484,8 @@ export class MatrixClient extends EventEmitter {
throw Error("Hostname must be set first");
}
+ await this.sendIdentifyAndOneTimeKeys();
+
// This is the initial sync case for each list
let lists_ranges: {
"overview": number[][];
@@ -491,9 +638,11 @@ export class MatrixClient extends EventEmitter {
},
bump_event_types: ["m.room.message", "m.room.encrypted"],
- // Room Subscriptions API
- //room_subscriptions: {},
- //unsubscribe_rooms: []
+ extensions: {
+ e2ee: {
+ enabled: true,
+ }
+ }
})
});
if (!resp.ok) {
@@ -969,6 +1118,8 @@ export class MatrixClient extends EventEmitter {
this.access_token = json.access_token;
this.device_id = json.device_id;
this.mxid = json.user_id;
+
+ this.olmMachine = await OlmMachine.initialize(new UserId(this.mxid), new DeviceId(this.device_id!), "cetirizine-crypto");
}
}
diff --git a/src/components/input/chat/input.tsx b/src/components/input/chat/input.tsx
@@ -63,7 +63,7 @@ const ChatInput: FC<ChatInputProps> = memo(({ namespace, onChange, onError }: Ch
}
return (
<LexicalComposer initialConfig={initialConfig}>
- <div className="editor-container">
+ <div className="editor-container flex-1">
<ToolbarPlugin />
<div className="editor-inner">
<RichTextPlugin
diff --git a/src/main.tsx b/src/main.tsx
@@ -1,17 +1,25 @@
/// <reference types="./@types/global.d.ts" />
+import { initAsync, start } from "@matrix-org/matrix-sdk-crypto-js";
import React from 'react'
import { createRoot } from 'react-dom/client';
-import App from './App';
import './index.scss';
-import { MatrixContext, defaultMatrixClient } from './app/sdk/client';
-const container = document.getElementById('root')!;
-const root = createRoot(container);
+initAsync().then(() => {
+ start();
-root.render(
- <React.StrictMode>
- <MatrixContext.Provider value={defaultMatrixClient}>
- <App />
- </MatrixContext.Provider>
- </React.StrictMode>
-);
-\ No newline at end of file
+ import('./app/sdk/client').then(({ MatrixContext, defaultMatrixClient }) => {
+ import('./App').then(({ default: App }) => {
+ const container = document.getElementById('root')!;
+ const root = createRoot(container);
+
+ root.render(
+ <React.StrictMode>
+ <MatrixContext.Provider value={defaultMatrixClient}>
+ <App />
+ </MatrixContext.Provider>
+ </React.StrictMode>
+ );
+ })
+
+ })
+})
diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx
@@ -1,4 +1,4 @@
-import { Settings } from 'lucide-react';
+import { Send, Settings } from 'lucide-react';
import Avatar from '../components/avatar/avatar';
import ChatInput from '../components/input/chat/input';
import RoomList, { Section } from '../components/roomList/roomList';
@@ -196,32 +196,40 @@ const MainPage = memo(() => {
</div>
<RoomList sections={sections} rooms={otherRooms} />
</div>
- {room && <div className='flex-1 flex flex-col'>
- <div className='pb-2 flex flex-row items-center border-b-2 mt-4 ml-2'>
- <Avatar displayname={room.getName()} avatarUrl={room.getAvatarURL()} dm={room.isDM()} online={room.isOnline()} />
- <div className='flex flex-row items-center'>
- <h1 className='text-black font-semibold text-lg flex-shrink-0'>{room.getName()}</h1>
- <p className='ml-4 text-slate-700 font-normal text-base'>{room.getTopic()}</p>
+ {
+ room && <div className='flex-1 flex flex-col'>
+ <div className='pb-2 flex flex-row items-center border-b-2 mt-4 ml-2'>
+ <Avatar displayname={room.getName()} avatarUrl={room.getAvatarURL()} dm={room.isDM()} online={room.isOnline()} />
+ <div className='flex flex-row items-center'>
+ <h1 className='text-black font-semibold text-lg flex-shrink-0'>{room.getName()}</h1>
+ <p className='ml-4 text-slate-700 font-normal text-base'>{room.getTopic()}</p>
+ </div>
+ </div>
+ <div ref={scrollRef} className='overflow-y-auto overflow-x-hidden scrollbarSmall mr-2 my-1 flex-1 w-full flex flex-col-reverse'>
+ <ChatView roomID={params.roomIdOrAlias} scrollRef={scrollRef} />
+ </div>
+ <div className='flex flex-row items-end'>
+ <ChatInput namespace='Editor' onChange={(editorState, editor) => {
+ // Convert editor state to both html and markdown.
+ // If there is no formatting then just use the plain text.
+ editorState.read(() => {
+ const html = $generateHtmlFromNodes(editor);
+ // TODO: Make sure that we strip any non matrix stuff
+ setHtmlMessage(html);
+ console.log(html);
+ const markdown = $convertToMarkdownString(TRANSFORMERS);
+ setPlainMessage(markdown);
+ console.log(markdown);
+ });
+ // TODO: we need some send button
+ }} onError={(e) => console.error(e)} />
+ <Send size={45} stroke='unset' className='stroke-slate-600 rounded m-4 hover:bg-slate-300 hover:stroke-slate-500 p-2 cursor-pointer' onClick={() => {
+ // TODO: Sanitize the html and send message to room
+ // TODO: encrypt if room is encrypted
+ }} />
</div>
</div>
- <div ref={scrollRef} className='overflow-y-auto overflow-x-hidden scrollbarSmall mr-2 my-1 flex-1 w-full flex flex-col-reverse'>
- <ChatView roomID={params.roomIdOrAlias} scrollRef={scrollRef} />
- </div>
- <ChatInput namespace='Editor' onChange={(editorState, editor) => {
- // Convert editor state to both html and markdown.
- // If there is no formatting then just use the plain text.
- editorState.read(() => {
- const html = $generateHtmlFromNodes(editor);
- // TODO: Make sure that we strip any non matrix stuff
- setHtmlMessage(html);
- console.log(html);
- const markdown = $convertToMarkdownString(TRANSFORMERS);
- setPlainMessage(markdown);
- console.log(markdown);
- });
- // TODO: we need some send button
- }} onError={(e) => console.error(e)} />
- </div>}
+ }
</div >
})