You can host your own private HopToDesk network using the simplicity of Cloudflare Workers.

To get started, follow the steps below:

  1. 1. Login to your Cloudflare account and go to the domain zone you wish to use.

  2. 2. Create a new worker with the filename index.js.

  3. 3. Copy and paste the code below to the index.js file, save and deploy it.

  4. 4. For best preformance, enable Pseudo IPv4 for the zone.

  5. 5. Now you can use the Cloudflare Worker URL as the "rendezvous" JSON value in api.json file or your Custom API.

Note: You must have a Cloudflare paid plan starting at $5/month to use this worker, as it uses Durable Objects.

/*
HopSignal - Signaling server for HopToDesk

MIT License

Copyright (c) 2018 Rasmus Viitanen
Copyright (c) 2023 Begonia Holdings

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

async function handleErrors(request, func) {
  try {
	return await func();
  } catch (err) {
	if (request.headers.get("Upgrade") == "websocket") {
	  let pair = new WebSocketPair();
	  pair[1].accept();
	  pair[1].send(JSON.stringify({ error: err.stack }));
	  pair[1].close(1011, "Uncaught exception during session setup");
	  return new Response(null, { status: 101, webSocket: pair[0] });
	} else {
	  return new Response(err.stack, { status: 500 });
	}
  }
}
var src_default = {
  async fetch(request, env) {
	return await handleErrors(request, async () => {
	  return handleWebSockets(request, env);
	});
  }
};
async function handleWebSockets(request, env) {
  let id = env.group.idFromName("A");
  let roomObject = env.group.get(id);
  let newUrl = new URL(request.url);
  return roomObject.fetch(newUrl, request);
}
var SessionGroup = class {
  constructor(state, env) {
	this.state = state;
	this.env = env;
	this.sessions = [];
  }
  async fetch(request) {
	return await handleErrors(request, async () => {
	  const url = new URL(request.url);
	  const user = url.searchParams.get("user");
	  if (request.headers.get("Upgrade") != "websocket") {
		return new Response("expected websocket", { status: 400 });
	  }
	  let pair = new WebSocketPair();
	  await this.handleSession(pair[1], user);
	  return new Response(null, { status: 101, webSocket: pair[0] });
	});
  }
  async handleSession(webSocket, name) {
	webSocket.accept();
	let session = { webSocket, name };
	this.sessions.push(session);
	webSocket.addEventListener("message", async (msg) => {
	  try {
		if (session.quit) {
		  webSocket.close(1011, "WebSocket broken.");
		  return;
		}
		const payload = JSON.parse(msg.data);
		console.log(payload.protocol);
		switch (payload.protocol) {
		  case "one-to-one":
			this.sendTo(msg.data, payload.endpoint, name);
			break;
		  case "one-to-self":
			this.sendTo(msg.data, name, name);
			break;
		  default:
			let alert = "Invalid protocol, valid protocol include: 'one-to-one', 'one-to-self', 'one-to-all'";
			webSocket.send(alert);
		}
	  } catch (err) {
		console.log(err);
		let alert = "Invalid protocol, valid protocol include: 'one-to-one', 'one-to-self', 'one-to-all'";
		webSocket.send(alert);
	  }
	});
	let closeOrErrorHandler = (evt) => {
	  session.quit = true;
	  this.sessions = this.sessions.filter((member) => member !== session);
	};
	webSocket.addEventListener("close", closeOrErrorHandler);
	webSocket.addEventListener("error", closeOrErrorHandler);
  }
  sendTo(message, receiver, sender) {
	if (receiver === "") {
	  this.sessions.map((session) => {
		if (session.name) {
		  try {
			session.webSocket.send(message);
		  } catch (err) {
			console.log(err);
		  }
		}
	  });
	} else {
	  let sent = false;
	  this.sessions.map((session) => {
		if (session.name === receiver) {
		  try {
			session.webSocket.send(message);
			sent = true;
		  } catch (err) {
			console.log(err);
		  }
		}
	  });
	  if (!sent) {
		this.sessions.map((session) => {
		  if (session.name === sender) {
			try {
			  session.webSocket.send("Could not find a node with the name " + receiver);
			} catch (err) {
			  console.log(err);
			}
		  }
		});
	  }
	}
  }
};
export {
  SessionGroup,
  src_default as default
};
//# sourceMappingURL=index.js.map