## Core system - [X] Base structure of JSON API code - [X] Implement the first methods for testing + the code that should later be generated by the proc macro - [X] Create the proc macro - [X] json api - [X] ts types - [X] arguments (no args, one argument, multiple args) - [X] return type - [X] custom types as type aliases that ts file looks prettier ## Pre - MVP - [X] Web socket server - [WIP] Web socket client (ts) - [X] backend connection state changed events - [X] Reconnect on connection loss / connection state - [ ] find a way to type the event emitter callback functions - [X] Events ## MVP - [X] mocha integration test for ts api - [X] basic tests - [X] advanced / "online tests" (mailadm for burner accounts) - [ ] coverage for a majority of the API - [ ] Blobs served - [ ] Blob upload (for attachments, setting profile-picture, importing backup and so on) - [ ] Web push API? At least some kind of notification hook closure this lib can accept. ## Other Ideas - [ ] make sure there can only be one connection at a time to the ws - why? , it could give problems if its commanded from multiple connections - [ ] encrypted connection? - [ ] authenticated connection? - [ ] Look into unit-testing for the proc macros? - [ ] proc macro taking over doc comments to generated typescript file - [X] GH action for tests (rust and typescript) - [X] rust test - [X] rust fmt - [X] rust clippy - [X] tsc check - [X] prettier - [X] mocha - [X] scripts to check&fix prettier formatting ## Apis replicate desktop api feature set: (this feature set is based on desktop version `1.20`, needs to be updated in the future) ```rs struct sendMessageParams { text: Option, filename: Option, // TODO we need to think about blobs some more location: Option<(u32,u32)>, quote_message_id: Option, } struct QrCodeResponse = { state: u32 // also enum in reality, for simlicity u32 here id: u32 text1: String } impl Api { // root --------------------------------------------------------------- // NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY async fn sc_set_profile_picture(&self, new_image: String) -> Result<()> {} // NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY // 'getProfilePicture' equals to `dc.getContact(C.DC_CONTACT_ID_SELF).getProfileImage()` or `dc.get_config("selfavatar")` async fn sc_join_secure_join(&self, qrCode: String) -> Result {} async fn sc_stop_ongoing_process(&self) -> Result {} async fn sc_check_qr_code(&self, qrCode: String) -> Result {} // login ---------------------------------------------------- // INFO: login functions need to call stop&start io where applicable // login.newLogin: // do instead in frontend: // 1. call `add_account` // 2. call `select_account` // 3. set credentials via set config // 4. call `sc_configure` // login.getLogins - is already implemented: `get_all_accounts` // login.loadAccount - Basically `select_account` // login.logout -> TODO: unselect account, which isn't implemented in the core yet // login.forgetAccount -> `remove_account` // login.getLastLoggedInAccount -> `get_selected_account_id` // login.updateCredentials -> do instead: set config then call `sc_configure` // backup ------------------------------------------------------------- // INFO: backup functions need to call stop&start io // NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY async fn sc_backup_export(&self, out_dir: String) -> Result<()> {} // NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY async fn sc_backup_import(&self, file: String) -> Result<()> {} // will not return the same as in desktop because this function imports backup to the current context unlike it was in desktop // chatList ----------------------------------------------------------- // chatList.selectChat - will be removed from desktop // chatList.getSelectedChatId - will be removed from desktop // chatList.onChatModified - will be removed from desktop async fn sc_chatlist_get_general_fresh_message_counter(&self) -> Result // this method might be used for a favicon badge counter // contacts ------------------------------------------------------------ async fn sc_contacts_change_nickname(&self, contact_id: u32, new_name: String) -> Result<()> // contacts.getChatIdByContactId - very similar to sc_contacts_create_chat_by_contact_id // contacts.getDMChatId - very similar to sc_contacts_create_chat_by_contact_id async fn sc_contacts_get_encryption_info(&self, contact_id: u32) -> Result async fn sc_contacts_lookup_contact_id_by_addr(&self, email: String) -> Result } ``` ```ts class DeltaRemote { // chat --------------------------------------------------------------- call( fnName: 'chat.getChatMedia', chatId: number, msgType1: number, msgType2: number ): Promise call(fnName: 'chat.getEncryptionInfo', chatId: number): Promise call(fnName: 'chat.getQrCode', chatId?: number): Promise call(fnName: 'chat.leaveGroup', chatId: number): Promise call(fnName: 'chat.setName', chatId: number, name: string): Promise call( fnName: 'chat.modifyGroup', chatId: number, name: string, image: string, remove: number[], add: number[] ): Promise call( fnName: 'chat.addContactToChat', chatId: number, contactId: number ): Promise call( fnName: 'chat.setProfileImage', chatId: number, newImage: string ): Promise call( fnName: 'chat.setMuteDuration', chatId: number, duration: MuteDuration ): Promise call( fnName: 'chat.createGroupChat', verified: boolean, name: string ): Promise call(fnName: 'chat.delete', chatId: number): Promise call( fnName: 'chat.setVisibility', chatId: number, visibility: | C.DC_CERTCK_AUTO | C.DC_CERTCK_STRICT | C.DC_CHAT_VISIBILITY_PINNED ): Promise call(fnName: 'chat.getChatContacts', chatId: number): Promise call(fnName: 'chat.markNoticedChat', chatId: number): Promise call(fnName: 'chat.getChatEphemeralTimer', chatId: number): Promise call( fnName: 'chat.setChatEphemeralTimer', chatId: number, ephemeralTimer: number ): Promise call(fnName: 'chat.sendVideoChatInvitation', chatId: number): Promise call( fnName: 'chat.decideOnContactRequest', messageId: number, decision: | C.DC_DECISION_START_CHAT | C.DC_DECISION_NOT_NOW | C.DC_DECISION_BLOCK ): Promise // locations ---------------------------------------------------------- call( fnName: 'locations.setLocation', latitude: number, longitude: number, accuracy: number ): Promise call( fnName: 'locations.getLocations', chatId: number, contactId: number, timestampFrom: number, timestampTo: number ): Promise // NOTHING HERE that is called directly from the frontend, yet // messageList -------------------------------------------------------- call( fnName: 'messageList.sendMessage', chatId: number, params: sendMessageParams ): Promise<[number, MessageType | null]> call( fnName: 'messageList.sendSticker', chatId: number, stickerPath: string ): Promise call(fnName: 'messageList.deleteMessage', id: number): Promise call(fnName: 'messageList.getMessageInfo', msgId: number): Promise call( fnName: 'messageList.getDraft', chatId: number ): Promise call( fnName: 'messageList.setDraft', chatId: number, { text, file, quotedMessageId, }: { text?: string; file?: string; quotedMessageId?: number } ): Promise call( fnName: 'messageList.messageIdToJson', id: number ): Promise<{ msg: null } | MessageType> call( fnName: 'messageList.forwardMessage', msgId: number, chatId: number ): Promise call( fnName: 'messageList.searchMessages', query: string, chatId?: number ): Promise call( fnName: 'messageList.msgIds2SearchResultItems', msgIds: number[] ): Promise<{ [id: number]: MessageSearchResult }> call( fnName: 'messageList.saveMessageHTML2Disk', messageId: number ): Promise // settings ----------------------------------------------------------- call(fnName: 'settings.keysImport', directory: string): Promise call(fnName: 'settings.keysExport', directory: string): Promise call( fnName: 'settings.serverFlags', { mail_security, send_security, }: { mail_security?: string send_security?: string } ): Promise call( fnName: 'settings.setDesktopSetting', key: keyof DesktopSettings, value: string | number | boolean ): Promise call(fnName: 'settings.getDesktopSettings'): Promise call( fnName: 'settings.saveBackgroundImage', file: string, isDefaultPicture: boolean ): Promise call( fnName: 'settings.estimateAutodeleteCount', fromServer: boolean, seconds: number ): Promise // stickers ----------------------------------------------------------- call( fnName: 'stickers.getStickers' ): Promise<{ [key: string]: string[] }> // todo move to extras? because its not directly elated to core // context ------------------------------------------------------------ call(fnName: 'context.maybeNetwork'): Promise // burner accounts ------------------------------------------------------------ call( fnName: 'burnerAccounts.create', url: string ): Promise<{ email: string; password: string }> // think about how to improve that api - probably use core api instead // extras ------------------------------------------------------------- call(fnName: 'extras.getLocaleData', locale: string): Promise call(fnName: 'extras.setLocale', locale: string): Promise call( fnName: 'extras.getActiveTheme' ): Promise<{ theme: Theme data: string } | null> call(fnName: 'extras.setThemeFilePath', address: string): void call(fnName: 'extras.getAvailableThemes'): Promise call(fnName: 'extras.setTheme', address: string): Promise // catchall: ---------------------------------------------------------- call(fnName: string): Promise call(fnName: string, ...args: any[]): Promise { return _callDcMethodAsync(fnName, ...args) } } export const DeltaBackend = new DeltaRemote() ``` after that, or while doing it adjust api to be more complete TODO different test to simulate two devices: to test autocrypt_initiate_key_transfer & autocrypt_continue_key_transfer