1
0
mirror of https://github.com/ekzhang/rustpad.git synced 2021-06-08 23:07:11 +03:00

Implement multiplexing for editors

This commit is contained in:
Eric Zhang
2021-06-02 23:20:34 -05:00
parent e97e19c1e3
commit 40a28d4850
7 changed files with 56 additions and 20 deletions

11
Cargo.lock generated
View File

@@ -118,6 +118,16 @@ dependencies = [
"libc",
]
[[package]]
name = "dashmap"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
dependencies = [
"cfg-if 1.0.0",
"num_cpus",
]
[[package]]
name = "digest"
version = "0.9.0"
@@ -843,6 +853,7 @@ name = "rustpad-server"
version = "0.1.0"
dependencies = [
"anyhow",
"dashmap",
"dotenv",
"futures",
"log",

View File

@@ -6,6 +6,7 @@ edition = "2018"
[dependencies]
anyhow = "1.0.40"
dashmap = "4.0.2"
dotenv = "0.15.0"
futures = "0.3.15"
log = "0.4.14"

View File

@@ -5,6 +5,7 @@
use std::sync::Arc;
use dashmap::DashMap;
use rustpad::Rustpad;
use warp::{filters::BoxedFilter, ws::Ws, Filter, Reply};
@@ -24,21 +25,34 @@ fn frontend() -> BoxedFilter<(impl Reply,)> {
/// Construct backend routes, including WebSocket handlers.
fn backend() -> BoxedFilter<(impl Reply,)> {
let rustpad = Arc::new(Rustpad::new());
let rustpad = warp::any().map(move || Arc::clone(&rustpad));
let rustpad_map: Arc<DashMap<String, Arc<Rustpad>>> = Default::default();
let rustpad_map = warp::any().map(move || Arc::clone(&rustpad_map));
let socket = warp::path("socket")
.and(warp::path::param())
.and(warp::path::end())
.and(warp::ws())
.and(rustpad.clone())
.map(|ws: Ws, rustpad: Arc<Rustpad>| {
ws.on_upgrade(move |socket| async move { rustpad.on_connection(socket).await })
});
.and(rustpad_map.clone())
.map(
|id: String, ws: Ws, rustpad_map: Arc<DashMap<String, Arc<Rustpad>>>| {
let rustpad = rustpad_map.entry(id).or_default();
let rustpad = Arc::clone(rustpad.value());
ws.on_upgrade(move |socket| async move { rustpad.on_connection(socket).await })
},
);
let text = warp::path("text")
.and(warp::path::param())
.and(warp::path::end())
.and(rustpad.clone())
.map(|rustpad: Arc<Rustpad>| rustpad.text());
.and(rustpad_map.clone())
.map(
|id: String, rustpad_map: Arc<DashMap<String, Arc<Rustpad>>>| {
rustpad_map
.get(&id)
.map(|rustpad| rustpad.text())
.unwrap_or_default()
},
);
socket.or(text).boxed()
}

View File

@@ -63,11 +63,6 @@ impl From<ServerMsg> for Message {
}
impl Rustpad {
/// Construct a new, empty Rustpad object.
pub fn new() -> Self {
Default::default()
}
/// Handle a connection from a WebSocket.
pub async fn on_connection(&self, socket: WebSocket) {
let id = self.count.fetch_add(1, Ordering::Relaxed);

View File

@@ -30,7 +30,7 @@ impl JsonSocket {
/// Connect a new test client WebSocket.
async fn connect(filter: &BoxedFilter<(impl Reply + 'static,)>) -> Result<JsonSocket> {
let client = warp::test::ws()
.path("/api/socket")
.path("/api/socket/foobar")
.handshake(filter.clone())
.await?;
Ok(JsonSocket(client))
@@ -38,7 +38,10 @@ async fn connect(filter: &BoxedFilter<(impl Reply + 'static,)>) -> Result<JsonSo
/// Check the text route.
async fn expect_text(filter: &BoxedFilter<(impl Reply + 'static,)>, text: &str) {
let resp = warp::test::request().path("/api/text").reply(filter).await;
let resp = warp::test::request()
.path("/api/text/foobar")
.reply(filter)
.await;
assert_eq!(resp.status(), 200);
assert_eq!(resp.body(), text);
}

View File

@@ -25,10 +25,11 @@ import languages from "./languages.json";
set_panic_hook();
const WS_URI =
const id = window.location.hash.slice(1);
const wsUri =
(window.location.origin.startsWith("https") ? "wss://" : "ws://") +
window.location.host +
"/api/socket";
`/api/socket/${id}`;
function App() {
const toast = useToast();
@@ -42,7 +43,7 @@ function App() {
model.setValue("");
model.setEOL(0); // LF
const rustpad = new Rustpad({
uri: WS_URI,
uri: wsUri,
editor,
onConnected: () => setConnected(true),
onDisconnected: () => setConnected(false),
@@ -52,7 +53,7 @@ function App() {
}, [editor]);
async function handleCopy() {
await navigator.clipboard.writeText(`${window.location.origin}/`);
await navigator.clipboard.writeText(`${window.location.origin}/#${id}`);
toast({
title: "Copied!",
description: "Link copied to clipboard",
@@ -114,7 +115,7 @@ function App() {
pr="3.5rem"
variant="outline"
bgColor="white"
value={`${window.location.origin}/`}
value={`${window.location.origin}/#${id}`}
/>
<InputRightElement width="3.5rem">
<Button h="1.4rem" size="xs" onClick={handleCopy}>

View File

@@ -3,6 +3,17 @@ import ReactDOM from "react-dom";
import { ChakraProvider } from "@chakra-ui/react";
import "./index.css";
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const idLen = 6;
if (!window.location.hash) {
let id = "";
for (let i = 0; i < idLen; i++) {
id += chars[Math.floor(Math.random() * chars.length)];
}
window.location.hash = id;
}
// An asynchronous entry point is needed to load WebAssembly files.
import("./App").then(({ default: App }) => {
ReactDOM.render(