commit 9c583642b50b3fe1ecec6f6ce1ac80e5f70ff31d
parent b027d11fa8ce0b69c22f3fd5ffabc2d9f007ed2d
Author: Marcel <mtrnord1@gmail.com>
Date: Mon, 1 Jun 2020 11:16:41 +0200
Use sdk structs instead of wrapper types and fix some issues along the way
Took 7 hours 9 minutes
Diffstat:
13 files changed, 311 insertions(+), 551 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -1,7 +1,7 @@
[package]
-name = "yew-wasm-pack-template"
-version = "0.2.0"
-authors = ["Yew Maintainers <maintainers@yew.rs"]
+name = "daydream"
+version = "0.1.0"
+authors = ["MTRNord <info@nordgedanken.de>"]
edition = "2018"
license = "AGPL-v3.0"
repository = "https://github.com/MTRNord/Daydream"
@@ -11,8 +11,7 @@ description = "A Matrix Web client written in Rust"
crate-type = ["cdylib", "rlib"]
[dependencies]
-cfg-if = "0.1"
-console_error_panic_hook = { version = "0.1", optional = true }
+console_error_panic_hook = { version = "0.1" }
log = "0.4"
serde = { version = "1.0", features = ["rc", "derive"] }
serde_json = "1.0.53"
diff --git a/package.json b/package.json
@@ -1,7 +1,8 @@
{
"private": true,
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
+ "test": "wasm-pack test --firefox --headless",
+ "tarpaulin-test": "cargo tarpaulin -v",
"dev": "webpack --mode development",
"build": "webpack --mode production",
"start:dev": "webpack-dev-server --mode development",
@@ -10,6 +11,7 @@
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "^1.3.1",
"copy-webpack-plugin": "^6.0.1",
+ "cross-env": "^7.0.2",
"css-loader": "^3.5.3",
"html-webpack-plugin": "^4.3.0",
"mini-css-extract-plugin": "^0.9.0",
diff --git a/src/app/components/event_list.rs b/src/app/components/event_list.rs
@@ -1,10 +1,13 @@
-use linked_hash_set::LinkedHashSet;
+use std::collections::HashMap;
+
use log::*;
-use matrix_sdk::identifiers::{EventId, RoomId};
-use serde::{Deserialize, Serialize};
+use matrix_sdk::{
+ events::room::message::{MessageEvent, MessageEventContent},
+ identifiers::{EventId, RoomId},
+ Room,
+};
use yew::prelude::*;
-use crate::app::matrix::types::MessageWrapper;
use crate::app::matrix::{MatrixAgent, Request, Response};
pub struct EventList {
@@ -14,11 +17,10 @@ pub struct EventList {
props: Props,
}
-#[derive(Serialize, Deserialize, Default)]
+#[derive(Default)]
pub struct State {
// TODO handle all events
- // TODO use roomId based hashmap
- pub events: LinkedHashSet<MessageWrapper>,
+ pub events: HashMap<RoomId, Vec<MessageEvent>>,
pub message: Option<String>,
}
@@ -32,9 +34,7 @@ pub enum Msg {
#[derive(Clone, PartialEq, Properties, Debug)]
pub struct Props {
#[prop_or_default]
- pub current_room: Option<RoomId>,
- #[prop_or_default]
- pub displayname: String,
+ pub current_room: Option<Room>,
}
impl Component for EventList {
@@ -50,15 +50,9 @@ impl Component for EventList {
message: None,
};
- if props.clone().current_room.is_some() {
- let room_id = props.clone().current_room.clone().unwrap();
- if state
- .events
- .iter()
- .filter(|x| x.room_id.clone().unwrap() == room_id)
- .collect::<LinkedHashSet<&MessageWrapper>>()
- .is_empty()
- {
+ if props.current_room.is_some() {
+ let room_id = props.current_room.clone().unwrap().room_id;
+ if !state.events.contains_key(&room_id) {
matrix_agent.send(Request::GetOldMessages((room_id.clone(), None)));
}
}
@@ -75,40 +69,30 @@ impl Component for EventList {
match msg {
Msg::NewMessage(response) => {
match response {
- Response::Sync(msg) => {
+ Response::Sync((room_id, msg)) => {
// TODO handle all events
- if !(self
- .state
- .events
+ if !(self.state.events[&room_id]
.iter()
.map(|x| x.event_id.clone())
.collect::<Vec<EventId>>()
- .contains(&msg.event_id.clone()))
+ .contains(&msg.event_id))
{
- self.state.events.insert(msg);
+ self.state.events.get_mut(&room_id).unwrap().push(msg);
}
true
}
- Response::OldMessages(messages) => {
- // TODO this doesn't seem smart
- // let mut new_events_map = LinkedHashSet::new();
- info!("{}", self.state.events.len());
- self.state.events = self
- .state
- .events
- .clone()
- .into_iter()
- .chain(messages)
- .collect();
- info!("{}", self.state.events.len());
- /*for event in self.state.events.clone().into_iter() {
- new_events_map.insert(event);
+ Response::OldMessages((room_id, mut messages)) => {
+ if self.state.events.contains_key(&room_id) {
+ self.state
+ .events
+ .get_mut(&room_id)
+ .unwrap()
+ .append(messages.as_mut());
+ true
+ } else {
+ self.state.events.insert(room_id, messages);
+ true
}
- for event in messages.clone().into_iter() {
- new_events_map.insert(event);
- }
- self.state.events = new_events_map.clone();*/
- true
}
_ => false,
@@ -122,7 +106,7 @@ impl Component for EventList {
info!("Sending Message");
if self.state.message.is_some() {
self.matrix_agent.send(Request::SendMessage((
- self.props.current_room.clone().unwrap(),
+ self.props.current_room.clone().unwrap().room_id,
self.state.message.clone().unwrap(),
)));
self.state.message = None;
@@ -136,17 +120,9 @@ impl Component for EventList {
fn change(&mut self, props: Self::Properties) -> bool {
if self.props != props {
- info!("{:#?}", props);
if props.clone().current_room.is_some() {
- let room_id = props.clone().current_room.clone().unwrap();
- if self
- .state
- .events
- .iter()
- .filter(|x| x.room_id.clone().unwrap() == room_id)
- .collect::<LinkedHashSet<&MessageWrapper>>()
- .is_empty()
- {
+ let room_id = props.clone().current_room.unwrap().room_id;
+ if !self.state.events.contains_key(&room_id) {
self.matrix_agent
.send(Request::GetOldMessages((room_id.clone(), None)));
}
@@ -161,23 +137,28 @@ impl Component for EventList {
fn view(&self) -> Html {
return html! {
<div class="container uk-flex uk-flex-column uk-width-5-6 uk-padding uk-padding-remove-bottom" style="height: 100%">
- <h1>{ self.props.displayname.clone() }</h1>
+ <h1>{ self.props.current_room.as_ref().unwrap().display_name() }</h1>
<div class="scrollable" style="height: 100%">
- { self.state.events.iter().filter(|x| x.room_id.clone().unwrap() == self.props.current_room.clone().unwrap()).map(|event| self.get_event(event.clone())).collect::<Html>() }
+ {
+ if self.state.events.contains_key(&self.props.current_room.as_ref().unwrap().room_id) {
+ self.state.events[&self.props.current_room.as_ref().unwrap().room_id].iter().map(|event| self.get_event(event)).collect::<Html>()
+ } else {
+ html!{}
+ }
+ }
<div id="anchor"></div>
</div>
- <form class="uk-margin"
- onsubmit=self.link.callback(|e: FocusEvent| {e.prevent_default(); Msg::Nope})
- onkeypress=self.link.callback(|e: KeyboardEvent| {
- if e.key() == "Enter" { Msg::SendMessage } else { Msg::Nope }
- })>
+ <form class="uk-margin"
+ onsubmit=self.link.callback(|e: FocusEvent| {e.prevent_default(); Msg::Nope})
+ onkeypress=self.link.callback(|e: KeyboardEvent| {
+ if e.key() == "Enter" { Msg::SendMessage } else { Msg::Nope }
+ })>
<div>
<div class="uk-inline" style="display: block !important;">
<span class="uk-form-icon" uk-icon="icon: pencil"></span>
<input class="uk-input" type="text"
- value=&self.state.message.clone().unwrap_or("".to_string())
+ value=&self.state.message.as_ref().unwrap_or(&"".to_string())
oninput=self.link.callback(|e: InputData| Msg::SetMessage(e.value))
-
/>
</div>
</div>
@@ -190,37 +171,46 @@ impl Component for EventList {
impl EventList {
// Typeinspection of IDEA breaks with this :D
//noinspection RsTypeCheck
- fn get_event(&self, event: MessageWrapper) -> Html {
- if event.event_type == "m.text" {
- html! {
- <p>{event.sender_displayname.unwrap_or(event.sender.to_string()).clone()}{": "}{event.content.clone()}</p>
+ fn get_event(&self, event: &MessageEvent) -> Html {
+ let sender_displayname = {
+ let room = self.props.current_room.as_ref().unwrap();
+ let member = room.members.get(&event.sender).unwrap();
+ member
+ .display_name
+ .as_ref()
+ .map(ToString::to_string)
+ .unwrap_or(event.sender.to_string())
+ };
+ match &event.content {
+ MessageEventContent::Text(text_event) => {
+ html! {
+ <p>{sender_displayname}{": "}{text_event.body.clone()}</p>
+ }
+ }
+ MessageEventContent::Notice(notice_event) => {
+ html! {
+ <p style="opacity: .6;">{sender_displayname}{": "}{notice_event.body.clone()}</p>
+ }
}
- } else if event.event_type == "m.notice" {
- html! {
- <p style="opacity: .6;">{event.sender_displayname.unwrap_or(event.sender.to_string()).clone()}{": "}{event.content.clone()}</p>
+ MessageEventContent::Image(image_event) => {
+ let caption = format!("{}: {}", sender_displayname, image_event.body);
+
+ let image_url = image_event.url.clone().unwrap();
+ let thumbnail = match image_event.info.clone().unwrap().thumbnail_url {
+ None => image_url.clone(),
+ Some(v) => v,
+ };
+ html! {
+ <div uk-lightbox="">
+ <a class="uk-inline" href=image_url data-caption=caption >
+ <img src=thumbnail alt=caption />
+ </a>
+ </div>
+ }
}
- } else if event.event_type == "m.image" {
- let caption = format!(
- "{}: {}",
- event
- .sender_displayname
- .unwrap_or(event.sender.to_string())
- .clone(),
- event.content.clone()
- );
- let thumbnail = match event.info.clone().unwrap().thumbnail_url {
- None => event.info.clone().unwrap().url.clone().unwrap(),
- Some(v) => v,
- };
- html! {
- <div uk-lightbox="">
- <a class="uk-inline" href=event.info.clone().unwrap().url.clone().unwrap() data-caption=caption.clone() >
- <img src=thumbnail.clone() alt=caption.clone() />
- </a>
- </div>
+ _ => {
+ html! {}
}
- } else {
- html! {}
}
}
}
diff --git a/src/app/components/room_list.rs b/src/app/components/room_list.rs
@@ -1,17 +1,16 @@
use std::collections::HashMap;
use log::*;
-use matrix_sdk::{identifiers::RoomId, js_int::UInt};
+use matrix_sdk::{identifiers::RoomId, js_int::UInt, Room};
use serde::{Deserialize, Serialize};
use wasm_bindgen::JsCast;
use web_sys::HtmlElement;
use yew::prelude::*;
+use yew::utils::document;
use yew::{Bridge, Bridged, Component, ComponentLink, Html};
use yewtil::NeqAssign;
-use crate::app::matrix::types::SmallRoom;
use crate::app::matrix::{MatrixAgent, Request, Response};
-use yew::utils::document;
pub struct RoomList {
link: ComponentLink<Self>,
@@ -22,24 +21,25 @@ pub struct RoomList {
pub enum Msg {
NewMessage(Response),
- ChangeRoom(String),
+ ChangeRoom(Room),
SetFilter(String),
ToggleTheme,
}
#[derive(Serialize, Deserialize, Default)]
pub struct State {
- rooms: HashMap<RoomId, SmallRoom>,
- current_room: Option<String>,
+ rooms: HashMap<RoomId, Room>,
+ current_room: Option<Room>,
loading: bool,
search_query: Option<String>,
dark_theme: bool,
+ did_fetch: bool,
}
#[derive(Clone, PartialEq, Properties)]
pub struct Props {
#[prop_or_default]
- pub change_room_callback: Callback<(String, String)>,
+ pub change_room_callback: Callback<Room>,
}
impl Component for RoomList {
@@ -55,6 +55,7 @@ impl Component for RoomList {
loading: true,
search_query: None,
dark_theme: false,
+ did_fetch: false,
};
RoomList {
@@ -71,30 +72,23 @@ impl Component for RoomList {
Response::JoinedRoomList(rooms) => {
self.state.rooms = rooms;
self.state.loading = false;
+ self.state.did_fetch = false;
true
}
// Better Initial Sync Detection
Response::SyncPing => {
- if self.state.rooms.is_empty() {
+ if self.state.rooms.is_empty() && !self.state.did_fetch {
self.matrix_agent.send(Request::GetJoinedRooms);
- return true;
+ self.state.did_fetch = true;
}
false
}
// Handle new rooms from sync
- Response::Sync(msg) => {
- if !(self
- .state
- .rooms
- .keys()
- .map(|x| x.clone())
- .collect::<Vec<RoomId>>()
- .contains(&msg.room_id.clone().unwrap()))
- {
- self.matrix_agent
- .send(Request::GetJoinedRoom(msg.room_id.clone().unwrap()));
+ Response::Sync((room_id, _msg)) => {
+ if !(self.state.rooms.contains_key(&room_id)) && !self.state.did_fetch {
+ self.matrix_agent.send(Request::GetJoinedRoom(room_id));
}
- true
+ false
}
Response::JoinedRoom((room_id, room)) => {
self.state.rooms.insert(room_id, room);
@@ -103,16 +97,7 @@ impl Component for RoomList {
_ => false,
},
Msg::ChangeRoom(room) => {
- let displayname = self
- .state
- .rooms
- .iter()
- .filter(|(id, _)| **id == room)
- .map(|(_, room)| room.name.clone())
- .collect::<String>();
- self.props
- .change_room_callback
- .emit((displayname, room.clone()));
+ self.props.change_room_callback.emit(room.clone());
self.state.current_room = Some(room);
true
}
@@ -144,17 +129,18 @@ impl Component for RoomList {
self.props.neq_assign(props)
}
+ //noinspection RsTypeCheck
fn view(&self) -> Html {
- if self.state.loading {
- return html! {
+ return if self.state.loading {
+ html! {
<div class="container">
<div class="uk-position-center uk-padding">
<span uk-spinner="ratio: 4.5"></span>
</div>
</div>
- };
+ }
} else {
- return html! {
+ html! {
<div class="container uk-flex uk-flex-column uk-width-1-6" style="height: 100%">
<div class="uk-padding uk-padding-remove-bottom" style="height: 50px">
<form class="uk-search uk-search-default">
@@ -163,17 +149,17 @@ impl Component for RoomList {
class="uk-search-input"
type="search"
placeholder="Filter Rooms..."
- value=&self.state.search_query.clone().unwrap_or("".to_string())
+ value=&self.state.search_query.as_ref().unwrap_or(&"".to_string())
oninput=self.link.callback(|e: InputData| Msg::SetFilter(e.value)) />
</form>
</div>
<ul class="scrollable uk-flex uk-flex-column uk-padding uk-nav-default uk-nav-parent-icon uk-padding-remove-bottom" uk-nav="">
<li class="uk-nav-header">{"Rooms"}</li>
{
- if self.state.search_query.is_none() || (self.state.search_query.clone().unwrap_or("".to_string()) == "".to_string()) {
- self.state.rooms.iter().map(|(_, room)| self.get_room(room.clone())).collect::<Html>()
+ if self.state.search_query.is_none() || (self.state.search_query.as_ref().unwrap_or(&"".to_string()) == &"".to_string()) {
+ self.state.rooms.iter().map(|(_, room)| self.get_room(room)).collect::<Html>()
} else {
- self.state.rooms.iter().filter(|(_, room)| room.name.to_lowercase().contains(&self.state.search_query.clone().unwrap().to_lowercase())).map(|(_, room)| self.get_room(room.clone())).collect::<Html>()
+ self.state.rooms.iter().filter(|(_, room)| room.display_name().to_lowercase().contains(&self.state.search_query.as_ref().unwrap().to_lowercase())).map(|(_, room)| self.get_room(room)).collect::<Html>()
}
}
</ul>
@@ -194,17 +180,15 @@ impl Component for RoomList {
</label>
</div>
</div>
- };
- }
+ }
+ };
}
}
impl RoomList {
- fn get_room(&self, room: SmallRoom) -> Html {
- // TODO better linking than onlclick (yew limitation?)
-
- let classes = if self.state.current_room.clone().is_some() {
- if self.state.current_room.clone().unwrap() == room.id.to_string() {
+ fn get_room(&self, matrix_room: &Room) -> Html {
+ let classes = if self.state.current_room.is_some() {
+ if self.state.current_room.as_ref().unwrap().room_id == matrix_room.room_id {
"uk-active"
} else {
""
@@ -213,21 +197,21 @@ impl RoomList {
""
};
- let room_id = room.clone().id.to_string();
+ let room = matrix_room.clone();
html! {
<li class=classes>
- <a onclick=self.link.callback(move |e: MouseEvent| Msg::ChangeRoom(room_id.clone()))>
- {room.name.clone()}
+ <a onclick=self.link.callback(move |e: MouseEvent| Msg::ChangeRoom(room.clone()))>
+ {matrix_room.display_name()}
{
- if room.unread_notifications.is_some() && room.unread_notifications.unwrap() != UInt::from(0u32) {
- html! { <span class="uk-badge uk-margin-small-left">{room.unread_notifications.unwrap()}</span> }
+ if matrix_room.unread_notifications.is_some() && matrix_room.unread_notifications.unwrap() != UInt::from(0u32) {
+ html! { <span class="uk-badge uk-margin-small-left">{matrix_room.unread_notifications.unwrap()}</span> }
} else {
html! {}
}
}
{
- if room.unread_highlight.is_some() && room.unread_highlight.unwrap() != UInt::from(0u32) {
- html! { <span class="uk-badge red uk-margin-small-left">{room.unread_highlight.unwrap()}</span> }
+ if matrix_room.unread_highlight.is_some() && matrix_room.unread_highlight.unwrap() != UInt::from(0u32) {
+ html! { <span class="uk-badge red uk-margin-small-left">{matrix_room.unread_highlight.unwrap()}</span> }
} else {
html! {}
}
diff --git a/src/app/matrix.rs b/src/app/matrix.rs
@@ -1,14 +1,17 @@
use std::collections::{HashMap, HashSet};
-use std::convert::{TryFrom, TryInto};
+use std::convert::TryFrom;
use std::sync::{Arc, Mutex};
+use comrak::{format_html, parse_document, Arena, ComrakOptions};
use futures_locks::RwLock;
-use linked_hash_set::LinkedHashSet;
use log::*;
use matrix_sdk::{
api::r0::{filter::RoomEventFilter, message::get_message_events::Direction},
- events::collections::all::RoomEvent,
- events::room::message::{MessageEventContent, TextMessageEventContent},
+ events::{
+ collections::all::RoomEvent,
+ room::message::{MessageEvent, MessageEventContent, TextMessageEventContent},
+ EventJson,
+ },
identifiers::RoomId,
js_int::UInt,
Client, ClientConfig, MessagesRequestBuilder, Room, Session,
@@ -20,10 +23,9 @@ use yew::format::Json;
use yew::services::{storage::Area, StorageService};
use yew::worker::*;
-use crate::app::matrix::types::{ImageInfoWrapper, MessageWrapper, SmallRoom};
+use crate::app::matrix::types::get_media_download_url;
use crate::constants::AUTH_KEY;
use crate::errors::MatrixError;
-use comrak::{format_html, parse_document, Arena, ComrakOptions};
mod sync;
pub mod types;
@@ -69,21 +71,20 @@ pub enum Request {
SendMessage((RoomId, String)),
}
-#[derive(Serialize, Deserialize, Debug, Clone)]
+#[derive(Debug, Clone)]
pub enum Response {
Error(MatrixError),
LoggedIn(bool),
// TODO properly handle sync events
- Sync(MessageWrapper),
+ Sync((RoomId, MessageEvent)),
SyncPing,
- FinishedFirstSync,
- JoinedRoomList(HashMap<RoomId, SmallRoom>),
+ JoinedRoomList(HashMap<RoomId, Room>),
Userdata(),
- OldMessages(LinkedHashSet<MessageWrapper>),
- JoinedRoom((RoomId, SmallRoom)),
+ OldMessages((RoomId, Vec<MessageEvent>)),
+ JoinedRoom((RoomId, Room)),
}
-#[derive(Serialize, Deserialize, Debug, Clone)]
+#[derive(Debug, Clone)]
pub enum Msg {
OnSyncResponse(Response),
}
@@ -131,13 +132,13 @@ impl Agent for MatrixAgent {
fn handle_input(&mut self, msg: Self::Input, _: HandlerId) {
match msg {
Request::SetHomeserver(homeserver) => {
- self.matrix_state.homeserver = Some(homeserver.clone());
+ self.matrix_state.homeserver = Some(homeserver);
}
Request::SetUsername(username) => {
- self.matrix_state.username = Some(username.clone());
+ self.matrix_state.username = Some(username);
}
Request::SetPassword(password) => {
- self.matrix_state.password = Some(password.clone());
+ self.matrix_state.password = Some(password);
}
Request::Login() => {
let login_client = self.login();
@@ -148,18 +149,15 @@ impl Agent for MatrixAgent {
}
return;
}
- let client = login_client.clone().unwrap();
+ let client = login_client.unwrap();
let username = self.matrix_state.username.clone().unwrap();
let password = self.matrix_state.password.clone().unwrap();
- let username = username.clone();
- let password = password.clone();
- let client = client.clone();
let subscribers = self.subscribers.clone();
let agent = self.clone();
spawn_local(async move {
// TODO handle login error
if agent.session.is_some() {
- let stored_session = agent.session.clone().unwrap();
+ let stored_session = agent.session.unwrap();
let session = Session {
access_token: stored_session.access_token,
user_id: matrix_sdk::identifiers::UserId::try_from(
@@ -171,19 +169,14 @@ impl Agent for MatrixAgent {
client.restore_login(session).await;
} else {
let login_response: matrix_sdk::api::r0::session::login::Response = client
- .login(
- username.clone(),
- password.clone(),
- None,
- Some("Daydream".to_string()),
- )
+ .login(username, password, None, Some("Daydream".to_string()))
.await
.unwrap();
let session_store = SessionStore {
access_token: login_response.access_token,
user_id: login_response.user_id.to_string(),
device_id: login_response.device_id,
- homeserver_url: client.homeserver().clone().into_string(),
+ homeserver_url: client.homeserver().to_string(),
};
let mut storage = agent.storage.lock().unwrap();
storage.store(AUTH_KEY, Json(&session_store));
@@ -196,7 +189,6 @@ impl Agent for MatrixAgent {
});
}
Request::GetLoggedIn => {
- let subscribers = self.subscribers.clone();
let login_client = self.login();
if login_client.is_none() {
for sub in self.subscribers.iter() {
@@ -215,8 +207,8 @@ impl Agent for MatrixAgent {
if !logged_in && agent.session.is_some() {
error!("Not logged in but got session");
} else {
- for sub in subscribers.iter() {
- let resp = Response::LoggedIn(logged_in.clone());
+ for sub in agent.subscribers.iter() {
+ let resp = Response::LoggedIn(logged_in);
agent.link.respond(*sub, resp);
}
}
@@ -231,27 +223,18 @@ impl Agent for MatrixAgent {
}
Request::GetJoinedRooms => {
let agent = self.clone();
- let client = agent.matrix_client.clone().unwrap();
spawn_local(async move {
+ let rooms: Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> =
+ agent.matrix_client.unwrap().joined_rooms();
+
+ let readable_rooms = rooms.read().await;
+ let mut rooms_unarced: HashMap<RoomId, Room> = HashMap::new();
+ for (id, room) in readable_rooms.iter() {
+ let unarced_room = (*room.write().await).clone();
+ rooms_unarced.insert(id.clone(), unarced_room);
+ }
for sub in agent.subscribers.iter() {
- let rooms: Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> =
- client.clone().joined_rooms();
- let mut rooms_list_hack = HashMap::new();
- for (id, room) in rooms.read().await.iter() {
- let small_room = SmallRoom {
- name: room.read().await.display_name(),
- unread_notifications: room
- .read()
- .await
- .unread_notifications
- .clone(),
- unread_highlight: room.read().await.unread_highlight.clone(),
- id: id.clone(),
- };
- rooms_list_hack.insert(id.clone(), small_room);
- }
-
- let resp = Response::JoinedRoomList(rooms_list_hack);
+ let resp = Response::JoinedRoomList(rooms_unarced.clone());
agent.link.respond(*sub, resp);
}
});
@@ -261,14 +244,21 @@ impl Agent for MatrixAgent {
}
Request::GetOldMessages((room_id, from)) => {
let agent = self.clone();
- let client = self.matrix_client.clone().unwrap();
spawn_local(async move {
let mut builder = &mut MessagesRequestBuilder::new();
builder = builder.room_id(room_id.clone());
if from.is_some() {
builder = builder.from(from.unwrap());
} else {
- builder = builder.from(client.clone().sync_token().await.unwrap());
+ builder = builder.from(
+ agent
+ .matrix_client
+ .clone()
+ .unwrap()
+ .sync_token()
+ .await
+ .unwrap(),
+ );
}
let filter = RoomEventFilter {
types: Some(vec!["m.room.message".to_string()]),
@@ -279,93 +269,73 @@ impl Agent for MatrixAgent {
.direction(Direction::Backward)
.limit(UInt::new(30).unwrap());
- let messsages = client.room_messages(builder.clone()).await.unwrap();
+ // TODO handle error gracefully
+ let messsages = agent
+ .matrix_client
+ .clone()
+ .unwrap()
+ .room_messages(builder.clone())
+ .await
+ .unwrap();
// TODO save end point for future loading
- let mut wrapped_messages: LinkedHashSet<MessageWrapper> = LinkedHashSet::new();
- for event in messsages.chunk.iter().rev() {
- if let Ok(event) = event.deserialize() {
- match event {
- RoomEvent::RoomMessage(event) => {
- let wrapped_event_result: Result<MessageWrapper, MatrixError> =
- event.try_into();
- match wrapped_event_result {
- Ok(mut wrapped_event) => {
- if wrapped_event.room_id.is_none() {
- wrapped_event.room_id = Some(room_id.clone());
- }
-
- wrapped_event.sender_displayname = Some(
- wrapped_event.get_displayname(client.clone()).await,
- );
-
- // Convert mxc URLs
- if wrapped_event.info.is_some() {
- let mxc_url = wrapped_event
- .info
- .clone()
- .unwrap()
- .url
- .clone()
- .unwrap();
- let download_url = wrapped_event
- .get_media_download_url(
- client.clone(),
- mxc_url,
- );
- let mxc_thumbnail_url = wrapped_event
- .info
- .clone()
- .unwrap()
- .thumbnail_url
- .clone()
- .unwrap();
- let thumbnail_download_url = wrapped_event
- .get_media_download_url(
- client.clone(),
- mxc_thumbnail_url,
- );
- wrapped_event.info = Some(ImageInfoWrapper {
- url: Some(download_url),
- thumbnail_url: Some(thumbnail_download_url),
- });
- }
-
- wrapped_messages.insert(wrapped_event);
- }
- Err(_) => {
- // Ignore events we cant parse
- continue;
- }
- }
+ let mut wrapped_messages: Vec<MessageEvent> = Vec::new();
+ let chunk_iter: Vec<EventJson<RoomEvent>> = messsages.chunk;
+ let (oks, _): (Vec<_>, Vec<_>) = chunk_iter
+ .iter()
+ .map(|event| event.deserialize())
+ .partition(Result::is_ok);
+
+ let deserialized_events: Vec<RoomEvent> =
+ oks.into_iter().map(Result::unwrap).collect();
+
+ for event in deserialized_events.into_iter().rev() {
+ if let RoomEvent::RoomMessage(mut event) = event {
+ if let MessageEventContent::Image(mut image_event) = event.content {
+ if image_event.url.is_some() {
+ let new_url = Some(get_media_download_url(
+ agent.matrix_client.clone().unwrap(),
+ image_event.url.unwrap(),
+ ));
+ image_event.url = new_url;
}
- _ => {
- continue;
+ if image_event.info.is_some() {
+ let mut info = image_event.info.unwrap();
+ if info.thumbnail_url.is_some() {
+ let new_url = Some(get_media_download_url(
+ agent.matrix_client.clone().unwrap(),
+ info.thumbnail_url.unwrap(),
+ ));
+ info.thumbnail_url = new_url;
+ }
+ image_event.info = Some(info);
}
+ event.content = MessageEventContent::Image(image_event);
}
+ wrapped_messages.push(event.clone());
}
}
for sub in agent.subscribers.iter() {
- let resp = Response::OldMessages(wrapped_messages.clone());
+ let resp =
+ Response::OldMessages((room_id.clone(), wrapped_messages.clone()));
agent.link.respond(*sub, resp);
}
});
}
Request::GetJoinedRoom(room_id) => {
let agent = self.clone();
- let client = self.matrix_client.clone().unwrap();
spawn_local(async move {
- let room: Arc<RwLock<Room>> =
- client.get_joined_room(&room_id.clone()).await.unwrap();
- let small_room = SmallRoom {
- name: room.read().await.display_name(),
- unread_notifications: room.read().await.unread_notifications.clone(),
- unread_highlight: room.read().await.unread_highlight.clone(),
- id: room_id.clone(),
- };
+ let room: Arc<RwLock<Room>> = agent
+ .matrix_client
+ .unwrap()
+ .get_joined_room(&room_id)
+ .await
+ .unwrap();
+ let read_clone = room.clone().read().await;
+ let clean_room = (*read_clone).clone();
for sub in agent.subscribers.iter() {
- let resp = Response::JoinedRoom((room_id.clone(), small_room.clone()));
+ let resp = Response::JoinedRoom((room_id.clone(), clean_room.clone()));
agent.link.respond(*sub, resp);
}
});
@@ -375,8 +345,7 @@ impl Agent for MatrixAgent {
spawn_local(async move {
let arena = Arena::new();
- let root =
- parse_document(&arena, message.clone().as_str(), &ComrakOptions::default());
+ let root = parse_document(&arena, message.as_str(), &ComrakOptions::default());
let mut html = vec![];
format_html(root, &ComrakOptions::default(), &mut html).unwrap();
@@ -386,9 +355,8 @@ impl Agent for MatrixAgent {
let content;
if formatted_message == message {
- content = MessageEventContent::Text(TextMessageEventContent::new_plain(
- message.clone(),
- ));
+ content =
+ MessageEventContent::Text(TextMessageEventContent::new_plain(message));
} else {
content = MessageEventContent::Text(TextMessageEventContent {
body: message.into(),
@@ -425,7 +393,7 @@ impl MatrixAgent {
if self.matrix_client.is_none() {
return false;
}
- self.matrix_client.clone().unwrap().logged_in().await
+ self.matrix_client.as_ref().unwrap().logged_in().await
}
fn login(&mut self) -> Option<Client> {
diff --git a/src/app/matrix/sync.rs b/src/app/matrix/sync.rs
@@ -1,16 +1,13 @@
-use std::convert::TryInto;
+use std::time::Duration;
use log::*;
use matrix_sdk::{
api::r0::sync::sync_events::Response as SyncResponse, events::collections::all::RoomEvent,
identifiers::RoomId, Client, SyncSettings,
};
+use yew::Callback;
-use crate::app::matrix::types::{MessageWrapper, ImageInfoWrapper};
use crate::app::matrix::Response;
-use crate::errors::MatrixError;
-use yew::Callback;
-use std::time::Duration;
pub struct Sync {
pub(crate) matrix_client: Client,
@@ -20,31 +17,16 @@ pub struct Sync {
impl Sync {
pub async fn start_sync(&self) {
let client = self.matrix_client.clone();
- let resp = client.clone().sync(SyncSettings::default()).await;
- match resp {
- _ => {
- let resp = Response::FinishedFirstSync;
- self.callback.emit(resp);
- }
- }
- let settings = match client.clone().sync_token().await {
- None => {
- SyncSettings::default().timeout(Duration::from_secs(30)).full_state(true)
- },
- Some(token) => {
- SyncSettings::default().token(token).timeout(Duration::from_secs(30)).full_state(true)
- },
- };
+ let settings = SyncSettings::default().timeout(Duration::from_secs(30));
client
- .clone()
.sync_forever(settings, |response| self.on_sync_response(response))
.await;
}
async fn on_sync_response(&self, response: SyncResponse) {
for (room_id, room) in response.rooms.join {
- // Is there a smarter way?
+ // FIXME: Is there a smarter way?
let resp = Response::SyncPing;
self.callback.emit(resp);
for event in room.timeline.events {
@@ -60,58 +42,8 @@ impl Sync {
match event {
RoomEvent::RoomMessage(event) => {
- let wrapped_event_result: Result<MessageWrapper, MatrixError> = event.try_into();
- match wrapped_event_result {
- Ok(mut wrapped_event) => {
- if wrapped_event.room_id.is_none() {
- wrapped_event.room_id = Some(room_id.clone());
- }
-
- wrapped_event.sender_displayname = Some(
- wrapped_event
- .get_displayname(self.matrix_client.clone())
- .await,
- );
-
- // Convert mxc URLs
- if wrapped_event.info.is_some() {
- let mxc_url = wrapped_event
- .info
- .clone()
- .unwrap()
- .url
- .clone()
- .unwrap();
- let download_url = wrapped_event
- .get_media_download_url(
- self.matrix_client.clone(),
- mxc_url,
- );
- let mxc_thumbnail_url = wrapped_event
- .info
- .clone()
- .unwrap()
- .thumbnail_url
- .clone()
- .unwrap();
- let thumbnail_download_url = wrapped_event
- .get_media_download_url(
- self.matrix_client.clone(),
- mxc_thumbnail_url,
- );
- wrapped_event.info = Some(ImageInfoWrapper {
- url: Some(download_url),
- thumbnail_url: Some(thumbnail_download_url),
- });
- }
- let resp = Response::Sync(wrapped_event);
- self.callback.emit(resp);
- }
- Err(_) => {
- // Ignore events we cant parse
- return;
- }
- }
+ let resp = Response::Sync((room_id.clone(), event));
+ self.callback.emit(resp);
}
_ => {
return;
diff --git a/src/app/matrix/types.rs b/src/app/matrix/types.rs
@@ -1,126 +1,15 @@
-use std::convert::TryFrom;
-use std::sync::Arc;
-
-use futures_locks::RwLock;
-use matrix_sdk::{
- events::room::message::{MessageEvent, MessageEventContent},
- identifiers::{EventId, RoomId, UserId},
- js_int::UInt,
- Client, Room,
-};
-use serde::{Deserialize, Serialize};
-
-use crate::errors::MatrixError;
-
-// TODO: Add Into trait
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct SmallRoom {
- pub(crate) name: String,
- pub(crate) unread_notifications: Option<UInt>,
- pub(crate) unread_highlight: Option<UInt>,
- pub(crate) id: RoomId,
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
-pub struct ImageInfoWrapper {
- pub(crate) url: Option<String>,
- pub(crate) thumbnail_url: Option<String>,
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
-pub struct MessageWrapper {
- pub(crate) sender_displayname: Option<String>,
- pub(crate) room_id: Option<RoomId>,
- pub(crate) event_id: EventId,
- pub(crate) event_type: String,
- pub(crate) sender: UserId,
- // TODO use ruma structs
- pub(crate) content: String,
- pub(crate) info: Option<ImageInfoWrapper>,
-}
-
-impl MessageWrapper {
- pub async fn get_displayname(&self, client: Client) -> String {
- let room: Arc<RwLock<Room>> = client
- .get_joined_room(&self.room_id.clone().unwrap())
- .await
- .unwrap();
- let room = room.read().await;
- let member = match room.members.get(&self.sender.clone()) {
- None => {
- return self.sender.clone().to_string();
- }
- Some(v) => v,
- };
- member
- .display_name
- .as_ref()
- .map(ToString::to_string)
- .unwrap_or(self.sender.clone().to_string())
- }
-
- pub fn get_media_download_url(&self, client: Client, mxc_url: String) -> String {
- let url_parts_raw = mxc_url.clone().replace("mxc://", "");
- let url_parts: Vec<&str> = url_parts_raw.split("/").collect();
- let server_name = url_parts.first().unwrap().to_string();
- let media_id = url_parts.last().unwrap().to_string();
- let new_path = format!(
- "_matrix/media/r0/download/{}/{}/fix.jpg",
- server_name.clone(),
- media_id.clone()
- );
- let mut new_url = client.clone().homeserver().clone();
- new_url.set_path(new_path.as_str());
- new_url.to_string()
- }
-}
-
-impl TryFrom<MessageEvent> for MessageWrapper {
- type Error = MatrixError;
-
- fn try_from(event: MessageEvent) -> Result<Self, Self::Error> {
- return match event.content {
- MessageEventContent::Text(text_event) => {
- Ok(MessageWrapper {
- sender_displayname: None, // We cant get it without a Client and therefor cant calculate it here
- room_id: event.room_id,
- event_id: event.event_id,
- event_type: "m.text".to_string(),
- sender: event.sender,
- content: text_event.body,
- info: None,
- })
- }
- MessageEventContent::Image(image_event) => {
- let thumbnail_url = match image_event.info {
- None => None,
- Some(i) => i.thumbnail_url,
- };
- Ok(MessageWrapper {
- sender_displayname: None, // We cant get it without a Client and therefor cant calculate it here
- room_id: event.room_id,
- event_id: event.event_id,
- event_type: "m.image".to_string(),
- sender: event.sender,
- content: image_event.body,
- info: Some(ImageInfoWrapper {
- url: image_event.url,
- thumbnail_url,
- }),
- })
- }
- MessageEventContent::Notice(notice_event) => {
- Ok(MessageWrapper {
- sender_displayname: None, // We cant get it without a Client and therefor cant calculate it here
- room_id: event.room_id,
- event_id: event.event_id,
- event_type: "m.notice".to_string(),
- sender: event.sender,
- content: notice_event.body,
- info: None,
- })
- }
- _ => Err(MatrixError::UnsupportedEvent),
- };
- }
+use matrix_sdk::Client;
+
+pub fn get_media_download_url(client: Client, mxc_url: String) -> String {
+ let url_parts_raw = mxc_url.replace("mxc://", "");
+ let url_parts: Vec<&str> = url_parts_raw.split("/").collect();
+ let server_name = url_parts.first().unwrap().to_string();
+ let media_id = url_parts.last().unwrap().to_string();
+ let new_path = format!(
+ "_matrix/media/r0/download/{}/{}/fix.jpg",
+ server_name, media_id,
+ );
+ let mut new_url = client.homeserver().clone();
+ new_url.set_path(new_path.as_str());
+ new_url.to_string()
}
diff --git a/src/app/views/login.rs b/src/app/views/login.rs
@@ -17,7 +17,6 @@ pub enum Msg {
SetUsername(String),
SetPassword(String),
Login,
- Nope,
}
impl Component for Login {
@@ -40,27 +39,23 @@ impl Component for Login {
match msg {
Msg::SetHomeserver(homeserver) => {
self.homeserver = homeserver.clone();
- self.matrix_agent
- .send(Request::SetHomeserver(homeserver.clone()));
+ self.matrix_agent.send(Request::SetHomeserver(homeserver));
true
}
Msg::SetUsername(username) => {
self.username = username.clone();
- self.matrix_agent
- .send(Request::SetUsername(username.clone()));
+ self.matrix_agent.send(Request::SetUsername(username));
true
}
Msg::SetPassword(password) => {
self.password = password.clone();
- self.matrix_agent
- .send(Request::SetPassword(password.clone()));
+ self.matrix_agent.send(Request::SetPassword(password));
true
}
Msg::Login => {
self.matrix_agent.send(Request::Login());
false
}
- Msg::Nope => false,
}
}
diff --git a/src/app/views/main_view.rs b/src/app/views/main_view.rs
@@ -1,32 +1,24 @@
-use std::convert::TryFrom;
-
-use linked_hash_set::LinkedHashSet;
use log::*;
-use matrix_sdk::identifiers::RoomId;
+use matrix_sdk::Room;
use serde::{Deserialize, Serialize};
use yew::prelude::*;
use yew::ComponentLink;
use crate::app::components::{event_list::EventList, room_list::RoomList};
-use crate::app::matrix::types::MessageWrapper;
-use crate::app::matrix::{MatrixAgent, Request, Response};
+use crate::app::matrix::{MatrixAgent, Request};
pub struct MainView {
link: ComponentLink<Self>,
state: State,
- matrix_agent: Box<dyn Bridge<MatrixAgent>>,
}
pub enum Msg {
- NewMessage(Response),
- ChangeRoom((String, String)),
+ ChangeRoom(Room),
}
#[derive(Serialize, Deserialize, Default)]
pub struct State {
- // TODO handle all events
- pub events: LinkedHashSet<MessageWrapper>,
- pub current_room: Option<RoomId>,
+ pub current_room: Option<Room>,
pub current_room_displayname: String,
}
@@ -35,35 +27,20 @@ impl Component for MainView {
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
- let matrix_callback = link.callback(Msg::NewMessage);
- let mut matrix_agent = MatrixAgent::bridge(matrix_callback);
+ let mut matrix_agent = MatrixAgent::dispatcher();
matrix_agent.send(Request::StartSync);
let state = State {
- events: Default::default(),
current_room: None,
current_room_displayname: Default::default(),
};
- MainView {
- link,
- matrix_agent,
- state,
- }
+ MainView { link, state }
}
fn update(&mut self, msg: Self::Message) -> bool {
match msg {
- Msg::NewMessage(response) => match response {
- Response::FinishedFirstSync => {
- //self.matrix_agent.send(Request::GetJoinedRooms);
- }
- _ => {}
- },
- Msg::ChangeRoom((displayname, room)) => {
- let room_id = RoomId::try_from(room).unwrap();
-
- self.state.current_room = Some(room_id.clone());
- self.state.current_room_displayname = displayname;
+ Msg::ChangeRoom(room) => {
+ self.state.current_room = Some(room);
}
}
true
@@ -73,9 +50,10 @@ impl Component for MainView {
false
}
+ //noinspection RsTypeCheck
fn view(&self) -> Html {
- if self.state.current_room.is_none() {
- return html! {
+ return if self.state.current_room.is_none() {
+ html! {
<div class="uk-flex auto-scrollable-container uk-background-default" style="height: 100%">
<RoomList change_room_callback=self.link.callback(Msg::ChangeRoom)/>
@@ -85,14 +63,14 @@ impl Component for MainView {
</div>
</div>
</div>
- };
+ }
} else {
- return html! {
+ html! {
<div class="uk-flex auto-scrollable-container" style="height: 100%">
<RoomList change_room_callback=self.link.callback(Msg::ChangeRoom)/>
- <EventList current_room=self.state.current_room.clone() displayname=self.state.current_room_displayname.clone() />
+ <EventList current_room=self.state.current_room.as_ref().unwrap() />
</div>
- };
- }
+ }
+ };
}
}
diff --git a/src/errors.rs b/src/errors.rs
@@ -7,6 +7,4 @@ pub enum MatrixError {
MissingClient,
#[error("Missing required Data")]
MissingFields,
- #[error("This Matrix Event is not yet supported by Daydream")]
- UnsupportedEvent,
}
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,34 +1,16 @@
#![recursion_limit = "512"]
-#[macro_use]
-extern crate cfg_if;
+extern crate console_error_panic_hook;
+extern crate wee_alloc;
+
+use console_error_panic_hook::set_once as set_panic_hook;
+use wasm_bindgen::prelude::*;
mod app;
mod constants;
mod errors;
-use wasm_bindgen::prelude::*;
-
-cfg_if! {
- // When the `console_error_panic_hook` feature is enabled, we can call the
- // `set_panic_hook` function to get better error messages if we ever panic.
- if #[cfg(feature = "console_error_panic_hook")] {
- extern crate console_error_panic_hook;
- use console_error_panic_hook::set_once as set_panic_hook;
- } else {
- #[inline]
- fn set_panic_hook() {}
- }
-}
-
-cfg_if! {
- // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
- // allocator.
- if #[cfg(feature = "wee_alloc")] {
- extern crate wee_alloc;
- #[global_allocator]
- static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
- }
-}
+#[global_allocator]
+static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
// This is the entry point for the web app
#[wasm_bindgen]
diff --git a/tests/web.rs b/tests/web.rs
@@ -11,3 +11,6 @@ wasm_bindgen_test_configure!(run_in_browser);
fn pass() {
assert_eq!(1 + 1, 2);
}
+
+#[wasm_bindgen_test]
+fn login() {}
diff --git a/yarn.lock b/yarn.lock
@@ -1062,6 +1062,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
+cross-env@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9"
+ integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==
+ dependencies:
+ cross-spawn "^7.0.1"
+
cross-spawn@6.0.5, cross-spawn@^6.0.0:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -1073,6 +1080,15 @@ cross-spawn@6.0.5, cross-spawn@^6.0.0:
shebang-command "^1.2.0"
which "^1.2.9"
+cross-spawn@^7.0.1:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
crypto-browserify@^3.11.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@@ -3366,6 +3382,11 @@ path-key@^2.0.0, path-key@^2.0.1:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
@@ -4006,11 +4027,23 @@ shebang-command@^1.2.0:
dependencies:
shebang-regex "^1.0.0"
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
signal-exit@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
@@ -4801,6 +4834,13 @@ which@^1.2.14, which@^1.2.9, which@^1.3.1:
dependencies:
isexe "^2.0.0"
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
worker-farm@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"