Teamwork is at the heart of all engineering disciplines, and software engineering is no exception. It is common and productive in our industry to see multiple engineers editing the same files simultaneously while thinking through problems in real time. Some tools, like VS Code's Live Share, are tied to an IDE. But like many developers, we only feel truly at home in a textual interface. That's why we felt the need to enable real-time terminal sharing that just works.
Pear allows multiple people to simultaneously access a terminal as if everyone had a keyboard plugged into the same computer, except the users can be on opposite sides of the planet. With Pear, real-time live editing and collaboration can extend to any tool in the terminal—which for software engineers who use Neovim is nearly every tool in the development environment.
Pear is built on libp2p, the same networking library which powers the
InterPlanetary File System. A lightweight registry server
helps start peer-to-peer connections between clients. The entire project is
written in Go, an excellent choice for a project like this. Other libraries we
used include cobra, a command-line interface builder
tool, and bbolt, a KV database.
libp2p was not designed for ease of use. With sparse documentation,
difficult-to-discover examples, and nothing but a formal specification for
describing how its components fit together at a high level, the API was
difficult to work with. Additionally, it is not trivial for nodes to discover
each other. The most basic method, rendezvous, is not decentralized, which
defeats some of the purpose of a peer-to-peer network. The next method, mDNS,
works well, but only over local networks. Finally, there is a distributed hash
table (DHT) method, but not only is this extremely complex and somewhat
non-performant, it also does not work unless the first few nodes are pre-known
in a "bootstrap" connection. We decided to compromise by writing a simple
registry server to solve node discovery, but this proved extraordinarily
difficult itself because the process of working around the masking effected by
network address translation (NAT) is extremely complicated and equally poorly
documented.
The issue that took the longest to resolve was a subset of the node connection process described above. What we didn't realize is that Fly.io by default gives projects a shared IPv4 address. This doesn't work well with P2P. This was not at all apparent given the error messages we were seeing. We solved the issue by purchasing an upgraded version of Fly.io that gives each project its own IP address.
Two of us had minimal or no Go experience, so we are proud to have learned it so quickly. We are also proud of sticking it out and fighting through frustrating issues with the network problems that comprised the bulk of the challenge in this project. Overall, we engineered our way around some significant challenges, and we're quite happy with the result.
Two of us learned Go. All of us learned one of the biggest and most complex
libraries we have ever seen, the Go implementation of libp2p. We learned
about the intricate process involved in overcoming the problems imposed by the
prevalence of NAT in modern network infrastructure. (See
https://tailscale.com/blog/how-nat-traversal-works for a good read on why this
seemingly simple problem has become very complex.)
Without the time constraints of a hackathon, we would have liked to implement buffers so that commands with high output do not flood the network. This should significantly improve the app's stability. Additionally, we would have implemented an additional layer of encryption using password-authenticated key agreement (PAKE). Another potential feature we had considered was voice calling capability.