Open karalabe opened 4 years ago
First draft at an (almost full) UX mock up for Corona Network: https://www.figma.com/proto/grE1s3BT52wNz1iB1hKJ4i/Corona-Network?node-id=3%3A277&viewport=262%2C662%2C0.25607821345329285&scaling=min-zoom
Removed some features in favor of a simple and familiar UX for MVP.
Thank you so so much in putting all that work in :)
I may have to shoot a few features down though due to the way the tech I envisioned would work, but would be happy to be proven wrong.
The direction I'd like to push this project towards is to be completely decentralized (no cloud, no server). Technically/practically, that would mean that it's based on Tor circuits and Onion services (I don't want to either reinvent the wheel, nor start inventing a new P2P protocol). This has a few implications however.
User identity in this setup would simply be a private key. We in theory could attach some extra information to it (social logins), but I'm unsure what the value would be in the first incarnation. That's why I originally only added a name (avatar is a nice touch, virus status and status message are also probably needed, but not sure if we need anything else). Your mobile device would generate you your key the first time you "create an account" and they you just run with it. In the first incarnation I wouldn't even support logging out or porting your user to a different device, just simply "nuke my account" if you don't want to use it any more.
Moving your account and data to another device or supporting login/logout is a fairly large can of worms. I have a prototype that does something like that (it scatters your data among your friends, who can feed it back to you on a different session/device). It's a very nice challenge, but finalizing that would be a lot of work with very little immediate gain for an MVP. As such, I think user handling should mostly boil down to "Create new user" (which under the hood generates a private key), and "Nuke my user" (which simply deletes all your local data).
The second nut I'm unsure how we could crack is GPS and maps. The problem at short is that map access requires a map provider. That's super expensive. Google maps gives you some 1K or 10K free loads per day, but if you exceed that, they charge you crazy money. Since this app would not be monetized, there's no place to cover such things from.
The decentralized approach also kind of rules out hosting a tile server ourselves, since I would really like this app to not depend on 3rd party infra (ok, it will depend on Tor, can't work around that any time soon). The only kind of possible path I could see if we either bake in some maps into the app itself (I don't think we can cram a meaningful resolution into it), or if we can download it from a place that guaranteed keeps it available forever (openstreemap has torrents, but not on demand regions afaik).
Curious if you know of a solution that we could use here?
Regarding MVP UI/UX features, my original idea was for the app to be able to tell me if I run the risk of being infected. For this, we need to be able to define some notion of "contact between people". Since we can't do geographical contact detection without some centralized tracking (and we don't want to share our location with the world), I was trying to figure out an alternative definition of "shared location".
The idea I came up with is "an event" (e.g. "movie night at someone's place", "ethcc conference", "a theater show"). Such events would always have an organizer (e.g. me, gnosis, the state theater). The same way each person is represented by a private key, each event would be essentially the same, a private key running a Tor onion service (hosted by the organizer's device). Everyone could "check in" to these events on arrival via a QR code, similar to how they can "friend" each other. This checking-in creates a virtual contact and timestamp between all the participants, and it also establishes a communication channel (the event onion service) between them. After X weeks, the event is deleted (onion torn down).
An future extension of the "event" could be a "location" (e.g. theater, park, venue). Again, an operator would need to "run" these locations, hosting the onion service. This would allow anyone visiting to "check in" (scan qr code), and report back infections. Since the "event" or "location" maintains a communication channel between all visitors, the infection report can immediately be propagated to all relevant visitors.
What the above "event" and "location" would allow us to do is to redefine the notion of "contact between people" from a "contiguous coordinate in the real world that's super sensitive to share + a global all-powerful operator with access to all data" to an "abstract private key that's un-guessable and will be irreversibly destroyed in 2 weeks + local operator (event organizer) with access to 2 weeks of data from a limited subset of people".
Essentially, just by shipping support for the "events", we can already create an almost fully untrusted temporal and spacial link between people, that allows them to communicate even if they don't all know each other; and it also allows algorithms to automatically provide infection risk guesses based on the announces.
This was my original dream for an MVP: Let me responsibly track the people I care about and events I attend; and let the program do the dirty work of linking everything together to warn me about dangers :)
Thank you for clearing up a few things have a better picture of what you want to get done.
Made changes to minimize data/UI, got rid of the map dependency in favor of a practical but less precise heat-map.
To take care of privacy concern, we can blur the GPS resolution by rounding long/lat to the minutes resolution (around 1 mile blocks effectively), the events paradigm I think is over engineering the solution, endangers who ever the host is, and sends the wrong message. I propose:
Each key/phone broadcasts all it's signed virus statuses and truncated GPS coordinates from 1st degree contacts (no personal information from contacts however) within 15 miles of person requesting along with their own profile data. Phones store the data they receive if it's within a 15 mile radius locally in something like a CRDT to give us consistency over the data regardless of how it propagates.
This allows for an untrusted temporal and spacial link between people even if they don't all know each other and covers a picture of what your city looks like (< 90% of cities are under 30 mi^2). and a little more color for the people that you do know.
Each key/phone broadcasts all it's signed virus statuses and truncated GPS coordinates from 1st degree contacts (no personal information from contacts however) within 15 miles of person requesting along with their own profile data. Phones store the data they receive if it's within a 15 mile radius locally in something like a CRDT to give us consistency over the data regardless of how it propagates.
I guess the hard technical question is how? Without a central operator that collects, filters and feeds the data back to you, how can phones nearby ind each other? This is what I haven't figured out. If there would be an easy way to for example use bluetooth without pairing or some such thing, there's a lot of nice things that could be done.
Otherwise I agree that this generic solution would be much nicer than the manual checkin.
The way I was thinking this would work is everyone can create some kind of link that allows anyone with the app that clicks it to request data from the creator (they could post on FB, Twitter, etc which I guess kind of act like central operator). Consider each user's graph G(v,e) where each node v represents an app user and each edge e represents links that node has clicked directly to add another vertex to G. WLOG, each link you collect gives you atleast one new vertex of the graph and all the edges/vertices that connect to it directly.
The question then becomes how many vertices of the graph do you need to collect links from before getting a majority of the edges in a 15 mile radius. If the 6 degrees of separation is accurate (probably moreso within cities) it's actually not that many edges that you need. If the number of links you need to complete the graph is too high then nodes can share vertices/edges one height deeper.
All this said, perhaps it'd be worthwhile to have a DB if only for the non-personal data. If the app hits critical mass might have better national risk estimates than CDC lagged testing.
On Thu, Mar 19, 2020, 9:06 AM Péter Szilágyi notifications@github.com wrote:
Each key/phone broadcasts all it's signed virus statuses and truncated GPS coordinates from 1st degree contacts (no personal information from contacts however) within 15 miles of person requesting along with their own profile data. Phones store the data they receive if it's within a 15 mile radius locally in something like a CRDT to give us consistency over the data regardless of how it propagates.
I guess the hard technical question is how? Without a central operator that collects, filters and feeds the data back to you, how can phones nearby ind each other? This is what I haven't figured out. If there would be an easy way to for example use bluetooth without pairing or some such thing, there's a lot of nice things that could be done.
Otherwise I agree that this generic solution would be much nicer than the manual checkin.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/coronanet/rn-coronanet/issues/1#issuecomment-601166828, or unsubscribe https://github.com/notifications/unsubscribe-auth/AN4UBHMVZOUMXFWSYYFY6CDRIIKF5ANCNFSM4LNQ7AGQ .
All this said, perhaps it'd be worthwhile to have a DB if only for the non-personal data. If the app hits critical mass might have better national risk estimates than CDC lagged testing.
I can't go down that path:
Now that that's out of the way :D, a few notes on the graph approach:
All in all I do agree that some automatic proximity detection would be desirable over manual checkins, just not sure how that could work out in practice. I would either way want to forcefully bind it to the physical world (bluetooth, direct connection, scan qr code locally), otherwise we'd run the risk of someone willing the system with fake data.
Tiny update:
This looks really promising. I need to finish up the Tor integrations into the backend first (pulled in my Tor P2P code yesterday) and need to create an easy way to pair nodes, but hopefully I can do that today and maybe I can dig into the bluetooth tomorrow.
Ok made UI to match your spec more closely. At this point I think perfection is the enemy of progress I want to ship something in the next two weeks, I am happy to commit to implementing this with your backend if we can agree. Let me know what you think of the mocks think we're at a last round of feedback before adding details and starting to implement the rn app.
Sorry if I've been a bit unresponsive on this thread. I was trying to push through a PoC backend that can be used to serve at least some meaningful data across the entire stack so that we can verify that the base idea itself works before putting too much time into it. It's a non-trivial amount of work to push the entire stack through though:
There are still a couple steps missing until there's an entire workable flow crossing over from one phone to another:
Working on this as fast as I can :)
Ok. So I took this weekend to take a step back and read through all of github.com/ipsn/go-ghostbridge, github.com/ipsn/go-libtor, and github.com/coronanet/go-coronanet. It was a spiritual experience. HOLY SHIT. I mean just go-ghostbridge alone blows my mind, brilliant.
You are too humble, even with a fair probability that the project will flop you are closer to a mobile first, actually secure/private/decentralized social network than any project I've ever seen; you've essentially done so by your bloody self, and the code quality brings a tear to my eye- the love and creativity that's gone into it is palpable.
I have enough to go off to start an implementation/contribute more meaningfully now that I understand ghostbridge/go-libtor. I can contribute more meaningfully on the protocol side if you (want to?) make some of the things you mentioned above into issues to delegate (you clearly don't need to). I completely understand leaving out the open source license and honestly might advise you to just make the entire repo for corona-net private for the time being- you only really need to expose the gomobile outputs and swagger spec.
In any case happy to contribute in any way to make this a reality, even outside of Corona Network a lot of this is ground breaking on its own, will start developing the front end so that it's REST plug-able and ping back here in a week to see where we're at with the wire protocol.
:blush:
I've tried a few approaches before the current one. I originally wanted to go with IPFS + IPNS (hence where the InterPlanetary Social Network name comes from). I had an implementation for account management + mini things on top of them, but the whole thing just didn't work as I expected. IPNS is not reliable, IPFS is a resource monster. I've tried to patch is certain things, fix IPNS, but at the end of the day it was a dead end. That took months of my time.
Not sure how I stumbled upon Tor as a possible next approach. I wanted to see how far I could push the envelope. I wanted to use Go as I contributed to gomobile
(yay my Ethereum Android client) a while back and knew it would solve running anything on Android. The question was whether it's possible to run Tor from Go, natively. Turned out that it's super perverted, but possible (yay my Ethereum USB library) so I ended up doing a "feasibility" study: https://medium.com/interplanetary-social-network/a-pinch-of-privacy-tor-from-within-go-fc4b09986120 I dropped the ball for quite a long time. It took way too much time to wrap Tor, it kind of worked, but also had issues. Just was exhausted.
A bit later I started experimenting with my libtor again, patched it up so it's more production-y, built some toys on top (https://medium.com/interplanetary-social-network/torfluxdb-anonymous-metrics-from-go-ce4f443e01fc). Got the motivation and started creating a mobile UI in React Native + a backend library. The whole thing just blew up. Crossing from RN to native modules to java to c to go was insane. I've implemented quite a bit of bridge code and then I just gave up. It was such a horrible resulting code that I called it a dead end. Then came the epiphany with ghostbridge (https://medium.com/interplanetary-social-network/ghost-bridge-react-native-to-go-19a69473f8e). I was super happy, but after publishing yet again a whole open source project, I felt burned out of it, so I closed the lid on the effort.
Half a year later I've played with at least 3 variations of a file sharing app on Android, some directly via Tor, some via IPFS, some via IPFS + Tor + some JavaScript bridge. All ended up in the bin. Good learning experience, horrible waste of time. I realized I still don't have enough building blocks for my decentralized system. I had raw networking via Tor and I had "UI" via REST, but in between was too much to fill in one project. After again a lot of time I figured I could build more puzzle pieces, that's how I made tornet
(included in go-coronanet). It was only a WIP PoC, was quite happy with the security and crypto (lost count how many "crpyto identities" I've implemented over 2 years for this, 5 at least), but it was a lot of effort and I couldn't motivate myself to dive into the "socail" network part (feeds, posts, profiles, etc).
Fast forward again half a year and we're here. I'm super enthusiastic about this project because I can write a few more puzzle pieces (profiles, contacts, ui), and this super limited social network would already be valuable; without having to go all out and compete with facebook. Also being super limited, there's a much much higher chance that this can work out (even if my long term dream not, or not yet). Last but not least it's a perfect feasibility study for continuing with my social network eventually or not.
As for keeping it closed. I've tried that. Originally the IPFS/IPNS work was closed, I had a forum with maybe 5 people on it, but I realized nobody will really help out if it's closed, so by keeping it away from the world I'm just leaving it to bit rot. I've never really published all my experiments, failed projects, etc, but every time I think I have a legit puzzle piece that is valuable, publishing it forces me to make it high enough quality. If I get a few contribs out of it too, it's already more valuable to me too than it was closed.
I'm also so so very happy that you're spending your time helping build this. It's such a huge boost to motivation that I'm not alone with my toys. So even if things don't work out thank you.
Anyway, enough of the philosophy :)
I've finished pairing today, the entire end-to-end flow, so now contacts can "friend" each other. Yesterday I've fixed some issues in Tor and now the whole system startup/restart thing works, and I think all REST APIs wrt gateway handling, profile handling and pairing is good to go (most certainly there's room to improve, but hey, I've started this project 10 days ago :D (+2 years thinking about it)).
I've ran out of time today to finish the contact integration so that after pairing, the nodes actually start to chatter (they do connect not, just crash because I have a nil
handler :P). Will try to fill in the missing things tomorrow, and hopefully by evening I can make a v0.0.3 .aar
that will have full contact management and profile/avatar sharing working. That would be a huge cornerstone for me because that's the first version where the entire system / tech stack as a whole ticks, together. The rest is just adding bells and whistles.
My only concern now is what will the bandwidth / battery cost of the system be. This is the true test that the PoC will answer. If it's decent, we're golden.
It works :) Decentralized social network is online :) Will make some polishes tonight, and push a new release on the Go repo.
Let me know how to hook up the UI with backend.
Writing reusable react hooks to all the ghostbridge endpoints is probably a good first
step. Want to do our best to turn it into a context/provider API you can import as a package to fully obscure the networking implementation details from RN UI. Will shoot to put up an example later today that should be easy to go off.
Setting up Ubuntu atm to build Corona Net and stay on the latest .aar
, what distro/version are you using @karalabe?
Just published v0.0.3 of the backend https://github.com/coronanet/go-coronanet/releases/tag/v0.0.3, I've also spent quite a bit of time to write up a short summary of the endpoints (details in the REST spec) and the pairing workflow. Hope this makes things clearer.
@daodesigner The Ubuntu version shouldn't really matter (I think I'm on latest released). Building it will require a stock gcc
(whatever, we don't care) and a fairly recent Go (if you're unfamiliar with Go flows, installing via snap
may be the easiest).
EDIT: There's also a slight chance that Ghost Bridge won't work as is. I've never used it from an infinitely running background service before. It most definitely should support the model, but the I might need to add some Java code to correctly reinit the bridge on app restart. Will do it tomorrow, need some rest now.
SO, got everything up and running and made a quick and dirty dash to test Ghost Bridge. https://gph.is/g/EGgW5xd Quick takeaways of what immediately stands out:
will finish writing the react hooks today and push something up, at which point the UI is pretty much decoupled from the internal networking and can breakup the UX to divide and conquer, shooting to get another Android device today to continue with the pairing flows and hopefully have some of the UI cranked out by the end of the week.
Sweet :) Just made a small demo for @0mkara, but I guess that might be late :D
diff --git a/components/WelcomeScreen.js b/components/WelcomeScreen.js
index 98ce7bc..ad757fc 100644
--- a/components/WelcomeScreen.js
+++ b/components/WelcomeScreen.js
@@ -3,6 +3,17 @@ import { Layout, Text, Button } from '@ui-kitten/components';
import { Dimensions, View, Image } from 'react-native';
export const WelcomeScreen = ({ navigation }) => {
+ // Nuke any previous user, create one from scratch, start the gateway
+ fetch('https://corona-network/profile', {method: 'DELETE'})
+ .then(() => fetch('https://corona-network/profile', {method: 'POST'})
+ .then(() => fetch('https://corona-network/gateway', {method: 'PUT'})
+ .then(() => fetch('https://corona-network/gateway')
+ .then((response) => response.json())
+ .then((response) => alert(JSON.stringify(response)))
+ )
+ )
+ );
+
const navigateSignup = () => {
navigation.navigate('Signup');
};
@daodesigner
/pairing GET,POST seems to time out sometimes
This might be due to the way it works internally (we can change the APIs to make it more meaningful/helpful to the UI). When you do POST /pairing
, it generates a new temporary crypto identity (should be super fast) and opens a new onion service through Tor to listen on for incoming pairings. The issue you are probably seeing is that the call blocks until the onion becomes operational.
The idea behind blocking was to only give you back the shared secret
after the onion service is online, otherwise you might end up with scanning a pairing QR code before there's anyone listening on the other side. I agree that this might be less than ideal. We could make it async so it gives you back the secret immediately and continues to build the channel in the background, but then we need a way to notify if it fails for whatever reason.
GET
Are you sure it's GET
that is slow and not PUT
? PUT does the same thing on the other side: it needs to do a network connection to your original onion. Until that is done, your local GET cannot return either, because it's waiting for the pairer to join.
/gateway DELETE fails (will try to pin down when)
This is an odd one as it should just tell Tor to disconnect and not even wait for it. Either way, I do propagate any error back in the response body, so that might help see what's odd.
Unit tests on RN side will be a B****
Oh yes. I'm trying to make a testable binary Go side so that we can do all the scenario testing there (perhaps even the REST endpoint tests) and minimize the crossover tests that's needed in RN.
There is non-negligible lag (~800ms) for most endpoints but that might just be my emulator
It's hard to pinpoint hat might be off in the general case. Some endpoints wait for networking (understandably slow), some do crypto (shouldn't be too slow). If you have specific problematic endpoints, I can def take a look. The baseline should be GET /profile
or avatar or contact. Those just do a tiny db read and return it. If those are slow too, there might be something odd going on.
Dived into reworking my old tornet
PoC package. The old one worked, but I've learned a few things about Tor and bandwidth usage that caused be to reevaluate the model a bit. Instead of going with an Ethereum-style P2P networking where there's a constant chatter, I'll go with a silent-unless-must-speak model. That should help drastically reduce bandwidth and battery drain. It'll probably take me a few days (not too much free time) to rewrite the entire thing, but as a first step I reevaluated the crypto identities I was using and had a nice epiphany.
A recent Go release (1.13) graduated Edward curves into the stdlib, meaning they are also now fully integrated into the TLS crypto. This is super news, because I could drop the old ECDSA crypto for ED25519 (smaller keys, more secure). The signatures are also deterministic, which means the the TLS cert itself no longer needs to be part of the identity, because it can be regenerated on the fly. The only place this might not work is for public identities, as I'm unsure I can poke deep enough into Go's HTTPS/TLS code to add a custom cert verification hook.
What does this all mean from a UI perspective? The pairing secret just got meaningfully smaller :)
vs.
Still working on getting event checkin codes down to a similar size. Not sure if I can, it needs a bit of TLS/HTTPS/Go code fu.
Sounds good. I've decided to drop a few of my current projects to focus on this for the foreseeable future. I'm wrapping up a few loose ends in the next day or two to fully shift my attention here, your unwillingness to compromise on privacy is inspiring, so long as that remains the case I'm happy to donate my time to the project.
Phew, https://github.com/coronanet/go-coronanet/pull/25. Spent all weekend + 3 entire afternoons gutting everything out and rewriting the entire P2P layer. 3500 line diff on a 3000 line codebase.
The new communication model is "eventual consistency", meaning that peers are generally disconnected from each other, and based on how important an update is, they connect and push it out faster or slower. I still need to finish the infra for this, currently the scheduler is a bit hacked in. Will do that tonight + add a debug endpoint for manually force connection to a peer to allow testing without waiting for the scheduling timeouts (hours).
Moving conversation from https://github.com/coronanet/rn-coronanet/pull/12 back here
- I'm wondering if we could turn the first one into the splash screen that just shows up while things are loading and then switch over to the second for signup?
Hard to Mock loading screen in Figma but consider it done.
- On the second page, I'd replace the
generate private key
with simplycreate new user
. All the crypto and P2P and decentralized mumbo jumbo are irrelevant for the user actually in front of the screen.Done.
- For signing up, I think a subset of the profile update screen would be needed. The only really important thing to set initially is a single full-name string. Everything else can be done later (especially virus status and message and whatnot), definitely not when creating a user.
Done.
Ben hits QR/pair --> *Display QR on screen while valid* // should agree on a timeout so people cant spam your phone? Alissa hits QR/scan --> *Scan QR with camera* --> Display Profile Page ---> Alissa hits Follow //Remove the contact if navigates away instead Alissa hits profile/Ben --> Unfollow (delete contact)
*know some of this UX is missing presently from prototype
Done.
https://www.figma.com/proto/y3VSBxXOrX6f6tCeHFF7HB/Corona-Net?node-id=1%3A4438&scaling=min-zoom
Spent a bit of time sketching out how the more advanced pairing could work. The use cases behind the below chart were:
Based on the above requirements, I sketched up the workflow below:
Synchronicity requirements:
This pairing workflow should be able to handle a lot of very complex interactions, but it is a lot more complex than the previous one. On the backend side it's fine, it's just a few more back and forth on an already established channel + some REST endpoints, so I can do that easily. On the frontend side however things get a lot more complex, since we have multiple steps on both sides that have certain async to them. Do you think @daodesigner that this is something you'd be willing to do?
Also general feedback welcome. I haven't yet figured out the exact protocol details and the REST API details, I'm mostly trying to just sketch up the general exchange, then we can wrap it in something that makes sense and is easy.
Been thinking that we might want to leave the fancy pairing for a second release, not the initial PoC. The UX is significantly better than what we have currently, but the current one is workable and a viable starting point. There are enough other hard problems to solve in getting this off the ground.
To take a next step, I've specced out the way events could work, both from a user's perspective as well as technically + wire protocol wise. Will sleep on it to see if it still makes sense tomorrow, and if so, I'll try to define the REST API + implement the whole thing by tomorrow evening.
https://github.com/coronanet/go-coronanet/blob/master/spec/events.md
Again, the same thing holds as for pairing, I'm trying to aim for the minimal subset of features that allow events to work. I'm sure we could make them even nicer like confirming a checking after scanning the QR code.
Well, I didn't expect that basic event support will land at 3000 LOC. Either way, that's not upstreamed, so a event hosting and joining is now operational. It follows the event spec (updated since last time) and also ships the REST APIs for events.
Although many of the low level things have proper tests, I gave up on using Postman to test the API. Too many things can break, too many scenarios need testing and I'm incapable of manually doing that any more. Thus I started on an integration test suite on top of the APIs, running multiple nodes against each other. https://github.com/coronanet/go-coronanet/pull/31
For now I'm polishing up the basics, found some issues in pairing + adding trace logs to everything so I can see what's going on. All in all I think the system is pretty amazingly stable, but I do need to iron out all the nasty (synthetic) corner cases.
Awesome, got a little side tracked but sounds like we're ready to implement a full MVP UI. Have raised funding to allow me to work on this around 20hrs/week for the next few months, will try to make everything as plug-able as possible for a UX revamp once this grows up into a full fledged social network. Let's try to agree on a final MVP for the Coronanet prototype in the next few days: https://www.figma.com/proto/y3VSBxXOrX6f6tCeHFF7HB/Corona-Net?node-id=1%3A4438&scaling=min-zoom I can then crunch out most of the app MVP by the end of next week based on the prototype ( once I get your thumbs up ofcourse).
Well, I didn't expect that basic event support will land at 3000 LOC. Either way, that's not upstreamed, so a event hosting and joining is now operational. It follows the event spec (updated since last time) and also ships the REST APIs for events.
I'd just started to wrap my head around the last 3000L diff for the P2P layer https://github.com/coronanet/go-coronanet/pull/25 😅. the test suite is awesome. Will pull the updated rest API into the ghostbridge context in the RN side and post in the coronanet repo to make sure I understand all the changes.
@karalabe ?
Sorry for dropping the ball on this. I went way overboard with the push and managed to get into a mini-burnout. Will try to gradually ease myself into the project again.
Hey @karalabe just a friendly bump on this project. I really feel like this needs to exist and I'm starting to lose sleep thinking about the project just sitting here. Anyway we can pick this up again or that I could fork/branch/be added to the project/pay you for a license/open source the project to keep the vision of a privacy first/decentralized social network alive?
This is a tracking issue for defining the minimum requirements and mock UI for the app to be useful.
Mock screens (you can switch between these by commenting out various "Screen" components at https://github.com/coronanet/rn-coronanet/blob/master/App.js#L21):
The state of the mock UI as of March 15, 2020 is: