Add connection approval modal

main
idylls 1 year ago
parent aa06e7a96b
commit 3f94ffccb1
Signed by: idylls
GPG Key ID: 52D7502B0C319049

@ -9,6 +9,10 @@ export type ConnectOffer = {
peerID: PeerID;
offer: RTCSessionDescriptionInit;
};
export type ConnectReject = {
kind: "connectReject";
peerID: PeerID;
};
export type ConnectAnswer = {
kind: "connectAnswer";
peerID: PeerID;
@ -21,10 +25,15 @@ export type ICECandidate = {
};
export type PearError = { error: string };
export type PearRequest = ConnectOffer | ConnectAnswer | ICECandidate;
export type PearRequest =
| ConnectOffer
| ConnectAnswer
| ConnectReject
| ICECandidate;
export type PearResponse =
| Registered
| ConnectAnswer
| ConnectOffer
| ConnectReject
| ICECandidate
| PearError;

@ -3,6 +3,7 @@ import { serve } from "./deps/std.ts";
import {
ConnectAnswer,
ConnectOffer,
ConnectReject,
ICECandidate,
PearRequest,
PearResponse,
@ -73,6 +74,28 @@ const iceCandidate = (peer: Peer, peerID: PeerID, msg: ICECandidate) => {
send(otherPeer, { kind: "iceCandidate", peerID, candidate: msg.candidate });
};
const connectReject = (peer: Peer, peerID: PeerID, msg: ConnectReject) => {
const otherPeer = getOtherPeer(peerID, msg.peerID);
if (typeof otherPeer === "string") {
send(peer, { error: otherPeer });
return;
}
send(otherPeer, { kind: "connectReject", peerID });
};
const HANDLERS = {
connectAnswer,
connectOffer,
connectReject,
iceCandidate,
} satisfies {
[K in PearRequest["kind"]]: (
thisPeer: Peer,
thisPeerID: PeerID,
data: PearRequest & { kind: K },
) => void;
};
const handleSocketMessage = function (this: WebSocket, ev: MessageEvent) {
const data: PearRequest = JSON.parse(ev.data);
console.debug(data);
@ -83,16 +106,8 @@ const handleSocketMessage = function (this: WebSocket, ev: MessageEvent) {
return;
}
switch (data.kind) {
case "connectOffer":
return connectOffer(this, peerID, data);
case "connectAnswer":
return connectAnswer(this, peerID, data);
case "iceCandidate":
return iceCandidate(this, peerID, data);
}
const handler = HANDLERS[data.kind];
handler(this, peerID, data as never);
};
const cleanupPeer = function (this: WebSocket) {

@ -1 +1,5 @@
export { swel } from "https://git.idylls.net/idylls/swel/raw/tag/2023.02.03/mod.ts";
// export { swel, type Renderable } from "../../../swel/mod.ts";
export {
swel,
type Renderable,
} from "https://git.idylls.net/idylls/swel/raw/tag/2023.02.17/mod.ts";

@ -6,8 +6,9 @@ import {
Marker,
} from "./deps/leaflet.ts";
import { initNetworking } from "./networking.ts";
import { AcceptDecision, initNetworking } from "./networking.ts";
import { PeerID } from "../common/types.ts";
import { showModal } from "./ui.ts";
type LatLng = {
lat: number;
@ -21,11 +22,29 @@ type Peer = {
};
const init = async () => {
const peers = new Map<PeerID, Peer>();
const onPeerConnected = (
const onPeerConnecting = async (
peerID: PeerID,
rtc: RTCPeerConnection,
dataChannel: RTCDataChannel,
connected: Promise<RTCDataChannel>,
) => {
const statusEl = swel("div", { className: "peerStatus" }, "Connecting");
const listItemEl = swel("div", { className: "peer" }, [
swel("div", { className: "peerID" }, peerID),
statusEl,
]);
peersList.appendChild(listItemEl);
const dataChannel = await connected;
statusEl.textContent = "Connected";
const peer = {
marker: null,
dataChannel,
listItemEl,
statusEl,
} as Peer;
peers.set(peerID, peer);
dataChannel.addEventListener("message", (msg) => {
const latLng: LatLng = JSON.parse(msg.data);
@ -46,23 +65,76 @@ const init = async () => {
}
});
const statusEl = swel("div", { className: "peerStatus" }, "Connected");
const listItemEl = swel("div", { className: "peer" }, [
swel("div", { className: "peerID" }, peerID),
statusEl,
]);
peersList.appendChild(listItemEl);
dataChannel.addEventListener("close", () => {
peer.statusEl.textContent = "Disconnected";
});
};
const peer = {
marker: null,
dataChannel,
listItemEl,
statusEl,
} as Peer;
peers.set(peerID, peer);
const onPeerConnectionRequest = (peerID: PeerID) => {
return new Promise<AcceptDecision>((r) => {
let secondsLeft = 5;
const interval = setInterval(() => {
secondsLeft--;
if (secondsLeft <= 0) {
r(false);
closeModal();
clearInterval(interval);
}
timeLeft.textContent = `${secondsLeft}s`;
}, 1000);
const timeLeft = swel("div", `${secondsLeft}s`);
const onClose = () => {
clearInterval(interval);
};
const closeModal = showModal({
content: [
`Accept connection from ${peerID}?`,
timeLeft,
swel(
"button",
{
on: {
click: () => {
r(true);
closeModal(false);
},
},
},
"Accept",
),
swel(
"button",
{
on: {
click: () => {
r(false);
closeModal(false);
},
},
},
"Reject",
),
],
onClose: () => {
r(false);
onClose();
},
});
});
};
const onConnectionRejected = (peerID: PeerID) => {
showModal({ content: `${peerID} rejected your connection request` });
};
const net = await initNetworking(onPeerConnected);
const net = await initNetworking({
onPeerConnecting,
onPeerConnectionRequest,
onConnectionRejected,
});
const peerIDInput = swel("input", { placeholder: "Peer ID" });
const connectButton = swel(

@ -8,13 +8,17 @@ import {
import { pearServerSocketAddress, rtcConfiguration } from "../ui.config.ts";
export const initNetworking = async (
onPeerConnected: (
export type AcceptDecision = true | false;
export const initNetworking = async (p: {
onPeerConnectionRequest: (peerID: PeerID) => Promise<AcceptDecision>;
onPeerConnecting: (
peerID: PeerID,
rtc: RTCPeerConnection,
dataChannel: RTCDataChannel,
) => void,
) => {
connected: Promise<RTCDataChannel>,
) => void;
onConnectionRejected: (peerID: PeerID) => void;
}) => {
const socket = new WebSocket(pearServerSocketAddress);
const pendingAnswers = new Map() as Map<
PeerID,
@ -23,13 +27,14 @@ export const initNetworking = async (
const connections = new Map() as Map<PeerID, RTCPeerConnection>;
type LR = Exclude<PearResponse, PearError>;
const on = {
const on: {
[K in LR["kind"]]: ((k: LR & { kind: K }) => void) | null;
} = {
registered: null,
connectAnswer: null,
connectOffer: null,
iceCandidate: null,
} as {
[K in LR["kind"]]: ((k: LR & { kind: K }) => void) | null;
connectReject: null,
};
socket.addEventListener("message", (ev) => {
@ -103,6 +108,12 @@ export const initNetworking = async (
};
on.connectOffer = async (msg) => {
const accept = await p.onPeerConnectionRequest(msg.peerID);
if (!accept) {
send({ kind: "connectReject", peerID: msg.peerID });
return;
}
const { rtc, connected } = rtcPeerConnection(msg.peerID);
connections.set(msg.peerID, rtc);
@ -120,10 +131,14 @@ export const initNetworking = async (
send({ kind: "connectAnswer", answer, peerID: msg.peerID });
const dataChannel = await dataChannelP;
await connected;
const connectedP = (async () => {
const dataChannel = await dataChannelP;
await connected;
return dataChannel;
})();
onPeerConnected(msg.peerID, rtc, dataChannel);
p.onPeerConnecting(msg.peerID, rtc, connectedP);
};
const connectToPeer = async (peerID: PeerID) => {
@ -138,9 +153,13 @@ export const initNetworking = async (
const answer = await sendConnectOffer(peerID, offer);
await rtc.setRemoteDescription(answer);
await connected;
const connectedP = (async () => {
await connected;
return dataChannel;
})();
onPeerConnected(peerID, rtc, dataChannel);
p.onPeerConnecting(peerID, rtc, connectedP);
};
on.iceCandidate = (msg) => {
@ -152,6 +171,10 @@ export const initNetworking = async (
rtc.addIceCandidate(msg.candidate);
};
on.connectReject = (msg) => {
p.onConnectionRejected(msg.peerID);
};
return {
connectToPeer,
peerID: myPeerID,

@ -51,3 +51,39 @@ main {
flex: 1;
}
}
.modalOverlay {
position: fixed;
top: 0;
left: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.2);
.modalContainer {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 50vw;
height: 50vw;
background: white;
padding: 1em;
.modalControls {
position: absolute;
top: 1em;
right: 1em;
}
}
}

@ -0,0 +1,32 @@
import { swel, type Renderable } from "./deps/swel.ts";
export const showModal = (p: { content: Renderable; onClose?: () => void }) => {
const close = (doOnClose = true) => {
if (doOnClose) {
p.onClose?.();
}
modal.remove();
};
const modal = swel(
"div",
{
className: "modalOverlay",
on: {
click: () => close(),
},
},
[
swel("div", { className: "modalContainer" }, [
swel("div", { className: "modalControls" }, [
swel("button", { on: { click: () => close() } }, "X"),
]),
swel("div", { className: "modalContent" }, p.content),
]),
],
);
document.body.appendChild(modal);
return close;
};
Loading…
Cancel
Save