massive rewrite of frontend
This commit is contained in:
parent
683462cc10
commit
3acfe8a9ea
@ -12,7 +12,7 @@
|
||||
<select id="peers">
|
||||
|
||||
</select>
|
||||
<input id="message-input" />
|
||||
<input id="message-input" disabled />
|
||||
<input type="file" id="sendfile" disabled />
|
||||
</div>
|
||||
</div>
|
||||
@ -20,22 +20,6 @@
|
||||
{% block pagescripts %}
|
||||
<script>
|
||||
(() => {
|
||||
let message_list = document.getElementById("message-list")
|
||||
let peers = document.getElementById("peers")
|
||||
let inputbox = document.getElementById("message-input")
|
||||
let sendfile = document.getElementById("sendfile")
|
||||
|
||||
let search = new URLSearchParams(location.search);
|
||||
let sender = search.get("name") || "";
|
||||
|
||||
let room = search.get("room") || "public";
|
||||
|
||||
let peerMap = new Map();
|
||||
let channelMap = new Map();
|
||||
|
||||
let ws;
|
||||
let wsReconnectInterval = 5000;
|
||||
|
||||
const MessageBootstrap = "Bootstrap";
|
||||
const MessageDiscoverRequest = "DiscoverRequest";
|
||||
const MessageDiscoverResponse = "DiscoverResponse";
|
||||
@ -43,34 +27,438 @@
|
||||
const MessageSessionAnswer = "SessionAnswer";
|
||||
const MessageICECandidate = "IceCandidate";
|
||||
|
||||
let updateSendfile = () => {
|
||||
if (peers.value != "") {
|
||||
sendfile.removeAttribute("disabled")
|
||||
} else {
|
||||
sendfile.setAttribute("disabled", "")
|
||||
}
|
||||
}
|
||||
peers.addEventListener("change", updateSendfile)
|
||||
sendfile.addEventListener("change", async (ev) => {
|
||||
let file = sendfile.files[0]
|
||||
sendfile.value = ""
|
||||
function ChatRoom(room, name, ui, options) {
|
||||
options = options || {};
|
||||
let { iceServers } = options;
|
||||
|
||||
let peer = peerMap.get(peers.value)
|
||||
if (!peer) {
|
||||
this.room = room;
|
||||
this.name = name;
|
||||
this.iceServers = iceServers;
|
||||
|
||||
this.peers = new Map();
|
||||
this.channels = new Map();
|
||||
this.i_am_polite = new Map();
|
||||
this.i_am_offering = new Map();
|
||||
|
||||
this.ws = null;
|
||||
|
||||
this.ui = ui;
|
||||
|
||||
this.event = new EventTarget();
|
||||
}
|
||||
|
||||
let nativeEvent = window.Event;
|
||||
function Event(type, data) {
|
||||
let event = new nativeEvent(type);
|
||||
|
||||
if (data) {
|
||||
for (let [k, v] of Object.entries(data)) {
|
||||
event[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
// ---------------- Chat Room Methods ------------
|
||||
|
||||
|
||||
// ---------------- Utilities --------------------
|
||||
|
||||
ChatRoom.prototype.kickoff = function () {
|
||||
this.event.dispatchEvent(new Event("kickoff"))
|
||||
this.send_message({
|
||||
message: {
|
||||
type: MessageBootstrap,
|
||||
},
|
||||
})
|
||||
}
|
||||
ChatRoom.prototype.setup_peerconnection = function (sender) {
|
||||
peer = this.peers.get(sender);
|
||||
if (peer == null) {
|
||||
peer = new RTCPeerConnection({
|
||||
iceServers: [{ urls: this.iceServers }]
|
||||
})
|
||||
peer.addEventListener("iceconnectionstatechange", () => {
|
||||
if (peer.iceConnectionState === "failed") {
|
||||
peer.restartIce();
|
||||
}
|
||||
})
|
||||
peer.addEventListener("negotiationneeded", async (ev) => {
|
||||
this.on_negotiation_needed(sender)
|
||||
})
|
||||
peer.addEventListener("icecandidate", (ev) => {
|
||||
if (!ev.candidate) {
|
||||
return
|
||||
}
|
||||
display(`你给 ${peers.value} 传了一个文件: ${file.name}。传输中...`)
|
||||
|
||||
let reader = new FileReader()
|
||||
let dc = peer.createDataChannel(`file:${file.name}`)
|
||||
reader.addEventListener("load", (ev) => {
|
||||
dc.send(reader.result)
|
||||
dc.close()
|
||||
let candidate = ev.candidate.toJSON()
|
||||
this.on_local_ice_candidate(sender, candidate)
|
||||
})
|
||||
dc.addEventListener("open", () => {
|
||||
reader.readAsArrayBuffer(file)
|
||||
peer.addEventListener("datachannel", ({ channel }) => {
|
||||
this.on_datachannel(sender, channel)
|
||||
})
|
||||
this.peers.set(sender, peer);
|
||||
}
|
||||
|
||||
return {
|
||||
peer,
|
||||
}
|
||||
}
|
||||
|
||||
ChatRoom.prototype.setup_chat_datachannel = function (sender, channel) {
|
||||
console.log("setup chat datachannel", { sender, channel })
|
||||
channel.addEventListener("open", (ev) => {
|
||||
this.event.dispatchEvent(new Event("chat-channel-open", {
|
||||
sender,
|
||||
channel
|
||||
}))
|
||||
})
|
||||
channel.addEventListener("message", (ev) => {
|
||||
console.log(ev)
|
||||
this.on_chat_message(sender, ev.data)
|
||||
})
|
||||
channel.addEventListener("close", () => {
|
||||
this.event.dispatchEvent(new Event("chat-channel-close", {
|
||||
sender,
|
||||
channel
|
||||
}))
|
||||
})
|
||||
}
|
||||
ChatRoom.prototype.setup_file_datachannel = function (sender, channel) {
|
||||
let filename = channel.label.substr(5)
|
||||
let buffers = [];
|
||||
channel.addEventListener("open", ev => {
|
||||
this.event.dispatchEvent(new Event("file_receiving", {
|
||||
filename,
|
||||
}))
|
||||
// this.display(`${sender} 给你发了一个文件: ${filename}。 接收中...`)
|
||||
})
|
||||
channel.addEventListener("message", ev => {
|
||||
buffers.push(ev.data)
|
||||
})
|
||||
channel.addEventListener("close", () => {
|
||||
this.event.dispatchEvent(new Event("file_received", {
|
||||
buffer: new Blob(buffers),
|
||||
filename
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
ChatRoom.prototype.reconnect = function () {
|
||||
this.event.dispatchEvent(new Event("ws_reconnect", { ws: this.ws }))
|
||||
if (this.ws && (this.ws.readyState == WebSocket.CONNECTING || this.ws.readyState == WebSocket.OPEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let protocol = "ws://";
|
||||
if (location.protocol === "https:") {
|
||||
protocol = "wss://"
|
||||
}
|
||||
|
||||
let ws = new WebSocket(protocol + location.host + "/ws");
|
||||
ws.addEventListener("error", (ev) => {
|
||||
console.error("ws error", ev)
|
||||
this.reconnect()
|
||||
})
|
||||
ws.addEventListener("close", () => {
|
||||
console.warn("ws closed")
|
||||
this.reconnect()
|
||||
})
|
||||
|
||||
ws.addEventListener("message", ({ data }) => {
|
||||
this.on_ws_message(JSON.parse(data))
|
||||
})
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
this.kickoff()
|
||||
})
|
||||
this.ws = ws;
|
||||
}
|
||||
|
||||
ChatRoom.prototype.send_message = function (message) {
|
||||
this.event.dispatchEvent(new Event("send_wsmessage", { message }))
|
||||
console.trace("sending message", message)
|
||||
this.ws.send(JSON.stringify({
|
||||
room: this.room,
|
||||
sender: this.name,
|
||||
...message
|
||||
}))
|
||||
}
|
||||
|
||||
// ----------------- Message from websocket --------------
|
||||
|
||||
|
||||
|
||||
ChatRoom.prototype.on_ws_message = function (wsMessage) {
|
||||
this.event.dispatchEvent(new Event("recv_wsmessage", { wsMessage }))
|
||||
console.trace("receiving message", wsMessage)
|
||||
switch (wsMessage.message.type) {
|
||||
case MessageBootstrap:
|
||||
this.on_bootstrap(wsMessage);
|
||||
break;
|
||||
case MessageDiscoverRequest:
|
||||
this.on_discover_request(wsMessage)
|
||||
break
|
||||
case MessageDiscoverResponse:
|
||||
this.on_discover_response(wsMessage)
|
||||
break;
|
||||
case MessageSessionOffer:
|
||||
this.on_session_message(wsMessage)
|
||||
break
|
||||
case MessageSessionAnswer:
|
||||
this.on_session_message(wsMessage)
|
||||
break
|
||||
case MessageICECandidate:
|
||||
this.on_remote_ice_candidate(wsMessage)
|
||||
}
|
||||
}
|
||||
|
||||
ChatRoom.prototype.on_bootstrap = function ({ sender, iceServers }) {
|
||||
this.event.dispatchEvent(new Event("bootstrap", { sender }))
|
||||
this.name = sender;
|
||||
this.iceServers = iceServers || this.iceServers || ["stun:nhz.jeffthecoder.xyz:3478", "stun:nhz.jeffthecoder.xyz:3479", "stun:nhz.jeffthecoder.xyz:13478"];
|
||||
|
||||
this.send_message({
|
||||
message: {
|
||||
type: MessageDiscoverRequest,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
ChatRoom.prototype.on_discover_request = function ({ sender }) {
|
||||
this.event.dispatchEvent(new Event("discover_request", { sender }))
|
||||
|
||||
this.i_am_polite.set(sender, false)
|
||||
this.setup_peerconnection(sender)
|
||||
|
||||
this.send_message({
|
||||
message: {
|
||||
type: MessageDiscoverResponse,
|
||||
},
|
||||
receiver: sender,
|
||||
})
|
||||
}
|
||||
|
||||
ChatRoom.prototype.on_discover_response = async function ({ sender }) {
|
||||
this.i_am_polite.set(sender, true)
|
||||
let { peer } = this.setup_peerconnection(sender);
|
||||
|
||||
let channel = peer.createDataChannel("chat");
|
||||
this.setup_chat_datachannel(sender, channel)
|
||||
this.channels.set(sender, channel)
|
||||
}
|
||||
|
||||
ChatRoom.prototype.on_session_message = async function ({ sender, message }) {
|
||||
let description = JSON.parse(message.sdp)
|
||||
let polite = this.i_am_polite.get(sender);
|
||||
let makingOffer = this.i_am_offering.get(sender) || false;
|
||||
let peer = this.peers.get(sender)
|
||||
|
||||
console.log("on session message", { message, description, polite, makingOffer })
|
||||
|
||||
const offerCollision =
|
||||
description.type === "offer" &&
|
||||
(makingOffer || peer.signalingState !== "stable");
|
||||
|
||||
ignoreOffer = !polite && offerCollision;
|
||||
if (ignoreOffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
await peer.setRemoteDescription(description);
|
||||
if (description.type === "offer") {
|
||||
await peer.setLocalDescription();
|
||||
this.event.dispatchEvent(new Event("offer", { sender, description }))
|
||||
|
||||
this.send_message({
|
||||
message: {
|
||||
type: MessageSessionAnswer,
|
||||
sdp: JSON.stringify(peer.localDescription),
|
||||
sender: this.name,
|
||||
kind: 3,
|
||||
},
|
||||
receiver: sender,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ChatRoom.prototype.on_remote_ice_candidate = function ({ sender, message }) {
|
||||
let candidate = JSON.parse(message.candidate);
|
||||
this.event.dispatchEvent(new Event("candidate", { sender, candidate }))
|
||||
|
||||
let peer = this.peers.get(sender)
|
||||
console.log("remote_candidate", sender, message)
|
||||
peer.addIceCandidate(candidate)
|
||||
}
|
||||
|
||||
// ----------------- webrtc state & events -----------------
|
||||
|
||||
ChatRoom.prototype.on_negotiation_needed = async function (sender) {
|
||||
let polite = this.i_am_polite.get(sender);
|
||||
console.log("negotiationneeded", { sender, polite })
|
||||
|
||||
let peer = this.peers.get(sender);
|
||||
this.i_am_offering.set(sender, true)
|
||||
try {
|
||||
await peer.setLocalDescription();
|
||||
this.send_message({
|
||||
message: {
|
||||
type: MessageSessionOffer,
|
||||
sdp: JSON.stringify(peer.localDescription),
|
||||
sender: this.name,
|
||||
kind: 3,
|
||||
},
|
||||
receiver: sender,
|
||||
})
|
||||
} finally {
|
||||
this.i_am_offering.delete(sender)
|
||||
}
|
||||
}
|
||||
|
||||
ChatRoom.prototype.on_local_ice_candidate = function (sender, candidate) {
|
||||
this.event.dispatchEvent(new Event("candidate", { candidate }))
|
||||
this.send_message({
|
||||
message: {
|
||||
type: MessageICECandidate,
|
||||
candidate: JSON.stringify(candidate),
|
||||
sender: this.name,
|
||||
kind: 3,
|
||||
},
|
||||
receiver: sender,
|
||||
})
|
||||
}
|
||||
|
||||
ChatRoom.prototype.on_chat_message = function (sender, data) {
|
||||
this.event.dispatchEvent(new Event("chat_message", { sender, data }))
|
||||
}
|
||||
|
||||
ChatRoom.prototype.on_channel_open = function (sender, channel) {
|
||||
this.event.dispatchEvent(new Event("channel_open", { data }))
|
||||
}
|
||||
|
||||
ChatRoom.prototype.on_channel_close = function (sender, channel) {
|
||||
this.event.dispatchEvent(new Event("channel_close", { data }))
|
||||
}
|
||||
|
||||
ChatRoom.prototype.on_datachannel = function (sender, channel) {
|
||||
console.trace("on datachannel", sender, channel)
|
||||
if (channel.label === "chat") {
|
||||
this.channels.set(sender, channel);
|
||||
this.setup_chat_datachannel(sender, channel)
|
||||
} else if (channel.label.startsWith("file:")) {
|
||||
this.setup_file_datachannel(sender, channel)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
function ChatUI(messages, peerOptions, inputbox, sendfile) {
|
||||
this.messages = messages;
|
||||
this.peerOptions = peerOptions;
|
||||
this.inputbox = inputbox;
|
||||
this.sendfile = sendfile;
|
||||
|
||||
this.receiver = null;
|
||||
|
||||
this.event = new EventTarget();
|
||||
}
|
||||
|
||||
|
||||
ChatUI.prototype.setup = function () {
|
||||
this.sendfile.addEventListener("change", e => this.on_sendfile_changed(e))
|
||||
this.peerOptions.addEventListener("change", e => this.on_peer_list_changed(e))
|
||||
this.inputbox.addEventListener("keyup", e => this.on_key_up(e))
|
||||
}
|
||||
|
||||
ChatUI.prototype.on_peer_list_changed = function () {
|
||||
this.receiver = this.peerOptions.value;
|
||||
if (this.peerOptions.value != "") {
|
||||
this.sendfile.removeAttribute("disabled")
|
||||
this.inputbox.removeAttribute("disabled")
|
||||
} else {
|
||||
this.sendfile.setAttribute("disabled", "")
|
||||
this.inputbox.setAttribute("disabled", "")
|
||||
}
|
||||
}
|
||||
|
||||
ChatUI.prototype.on_key_up = function (e) {
|
||||
if (!(e.key === 'Enter' || e.keyCode === 13)) {
|
||||
return
|
||||
}
|
||||
if (!this.inputbox.value || this.inputbox.value.trim().length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let value = this.inputbox.value;
|
||||
let receiver = this.receiver;
|
||||
|
||||
this.event.dispatchEvent(new Event("send_message", {
|
||||
value,
|
||||
receiver,
|
||||
}))
|
||||
this.inputbox.value = ""
|
||||
}
|
||||
|
||||
ChatUI.prototype.on_sendfile_changed = function () {
|
||||
let file = sendfile.files[0]
|
||||
let receiver = this.receiver;
|
||||
sendfile.value = ""
|
||||
|
||||
this.display(`你给 ${this.receiver} 传了一个文件: ${file.name}。传输中...`)
|
||||
|
||||
// let reader = new FileReader()
|
||||
// let dc = peer.createDataChannel(`file:${file.name}`)
|
||||
// reader.addEventListener("load", (ev) => {
|
||||
// dc.send(reader.result)
|
||||
// dc.close()
|
||||
// })
|
||||
// dc.addEventListener("open", () => {
|
||||
// reader.readAsArrayBuffer(file)
|
||||
// })
|
||||
this.event.dispatchEvent(new Event("send_file", {
|
||||
file,
|
||||
receiver,
|
||||
}))
|
||||
}
|
||||
|
||||
ChatUI.prototype.add_peer = function (peerName) {
|
||||
let newNode = document.createElement("option")
|
||||
newNode.id = `peer-${peerName}`
|
||||
newNode.setAttribute("value", peerName)
|
||||
newNode.innerHTML = peerName;
|
||||
this.peerOptions.appendChild(newNode)
|
||||
|
||||
this.on_peer_list_changed()
|
||||
}
|
||||
ChatUI.prototype.remove_peer = function (peerName) {
|
||||
let el = document.getElementById(`peer-${peerName}`);
|
||||
if (el) el.remove()
|
||||
|
||||
this.on_peer_list_changed()
|
||||
}
|
||||
|
||||
ChatUI.prototype.display = function (...message) {
|
||||
let message_list = this.messages;
|
||||
|
||||
let scrollToBottom = Math.abs(message_list.scrollHeight - message_list.scrollTop - message_list.clientHeight) < 1;
|
||||
message_list.appendChild(createNode("div",
|
||||
`${new Date().toTimeString()}: `,
|
||||
...message
|
||||
))
|
||||
if (scrollToBottom) {
|
||||
message_list.scrollTo({
|
||||
top: message_list.scrollHeight
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let message_list = document.getElementById("message-list")
|
||||
let peers = document.getElementById("peers")
|
||||
let inputbox = document.getElementById("message-input")
|
||||
let sendfile = document.getElementById("sendfile")
|
||||
|
||||
const createNode = (tag, ...children) => {
|
||||
let node = document.createElement(tag)
|
||||
@ -88,254 +476,66 @@
|
||||
return node
|
||||
}
|
||||
|
||||
const display = (...message) => {
|
||||
let scrollToBottom = Math.abs(message_list.scrollHeight - message_list.scrollTop - message_list.clientHeight) < 1;
|
||||
message_list.appendChild(createNode("div",
|
||||
`${new Date().toTimeString()}: `,
|
||||
...message
|
||||
))
|
||||
if(scrollToBottom) {
|
||||
message_list.scrollTo({
|
||||
top: message_list.scrollHeight
|
||||
|
||||
let search = new URLSearchParams(location.search);
|
||||
|
||||
let room = new ChatRoom(search.get("room") || "public", search.get("name") || "", undefined, {})
|
||||
let ui = new ChatUI(message_list, peers, inputbox, sendfile);
|
||||
ui.setup()
|
||||
|
||||
room.event.addEventListener("kickoff", () => {
|
||||
ui.display("服务器连上啦。你等等,给你起个名字...")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
inputbox.addEventListener("keyup", (e) => {
|
||||
if (!(e.key === 'Enter' || e.keyCode === 13)) {
|
||||
return
|
||||
}
|
||||
if (!inputbox.value || inputbox.value.trim().length == 0) {
|
||||
return
|
||||
}
|
||||
let channel = channelMap.get(peers.value)
|
||||
if (!channel) {
|
||||
return
|
||||
}
|
||||
|
||||
display(`你 -> ${peers.value}: ${inputbox.value}`)
|
||||
channel.send(inputbox.value)
|
||||
inputbox.value = ""
|
||||
room.event.addEventListener("chat-channel-open", ({ channel, sender }) => {
|
||||
ui.display(`${sender} 连上了`)
|
||||
ui.add_peer(sender)
|
||||
})
|
||||
room.event.addEventListener("chat-channel-closed", ({ channel, sender }) => {
|
||||
ui.display(`${sender} 断开了`)
|
||||
ui.remove_peer(sender)
|
||||
})
|
||||
|
||||
const addPeer = (peerName) => {
|
||||
display(`连接上了 ${peerName}`)
|
||||
|
||||
let newNode = document.createElement("option")
|
||||
newNode.id = peerName
|
||||
newNode.setAttribute("value", peerName)
|
||||
newNode.innerHTML = peerName;
|
||||
peers.appendChild(newNode)
|
||||
|
||||
updateSendfile()
|
||||
}
|
||||
const removePeer = (peerName) => {
|
||||
display(`${peerName} 走了...`)
|
||||
let el = document.getElementById(peerName);
|
||||
if(el) el.remove()
|
||||
|
||||
updateSendfile()
|
||||
}
|
||||
|
||||
let timeout;
|
||||
|
||||
let reconnect = () => {
|
||||
if(ws && (ws.readyState == WebSocket.CONNECTING || ws.readyState == WebSocket.OPEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let protocol = "ws://";
|
||||
if (location.protocol === "https:") {
|
||||
protocol = "wss://"
|
||||
}
|
||||
|
||||
ws = new WebSocket(protocol+location.host+"/ws");
|
||||
ws.addEventListener("error", reconnect)
|
||||
ws.addEventListener("close", reconnect)
|
||||
|
||||
ws.addEventListener("message", ({data}) => {
|
||||
handle_ws_message(JSON.parse(data))
|
||||
room.event.addEventListener("chat_message", ({ sender, data }) => {
|
||||
ui.display(`${sender} -> 你: ${data}`)
|
||||
})
|
||||
ws.addEventListener("open", () => {
|
||||
display("服务器连上啦。你等等,给你起个名字...")
|
||||
ws.send(JSON.stringify({
|
||||
message: {
|
||||
type: MessageBootstrap,
|
||||
},
|
||||
room,
|
||||
sender,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
let handle_ws_message = (wsMessage) => {
|
||||
let recreateAndSetupPeer = async (peerName) => {
|
||||
if (!peerMap.has(peerName)) {
|
||||
|
||||
let peer = new RTCPeerConnection({
|
||||
iceServers: [{ urls: ["stun:nhz.jeffthecoder.xyz:3478", "stun:nhz.jeffthecoder.xyz:3479", "stun:nhz.jeffthecoder.xyz:13478"] }]
|
||||
})
|
||||
peer.addEventListener("signalingstatechange", (ev) => {
|
||||
console.log("signaling state changed: ", peer.signalingState)
|
||||
})
|
||||
peer.addEventListener("connectionstatechange", (ev) => {
|
||||
console.log("peer connection state changed: ", peer.connectionState)
|
||||
switch (peer.connectionState) {
|
||||
case "closed":
|
||||
break;
|
||||
case "disconnected":
|
||||
case "failed":
|
||||
peer.restartIce()
|
||||
break
|
||||
}
|
||||
})
|
||||
peer.addEventListener("icecandidate", (ev) => {
|
||||
if (!ev.candidate) {
|
||||
console.log("gather end")
|
||||
return
|
||||
}
|
||||
let candidate = ev.candidate.toJSON()
|
||||
ws.send(JSON.stringify({
|
||||
message: {
|
||||
type: MessageICECandidate,
|
||||
candidate: JSON.stringify(candidate),
|
||||
sender,
|
||||
kind: 3,
|
||||
},
|
||||
room,
|
||||
sender,
|
||||
receiver: wsMessage.sender,
|
||||
}))
|
||||
})
|
||||
peer.addEventListener("icegatheringstatechange", ev => {
|
||||
console.log("gather", peer.iceGatheringState)
|
||||
})
|
||||
peer.addEventListener("datachannel", ({channel}) => {
|
||||
if(channel.label === "chat") {
|
||||
channelMap.set(peerName, channel);
|
||||
channel.addEventListener("open", (ev) => {
|
||||
addPeer(peerName)
|
||||
})
|
||||
channel.addEventListener("message", (ev) => {
|
||||
display(`${peerName} -> You: ${ev.data}`)
|
||||
})
|
||||
channel.addEventListener("close", () => {
|
||||
removePeer(peerName)
|
||||
})
|
||||
} else if (channel.label.startsWith("file:")) {
|
||||
let filename = channel.label.substr(5)
|
||||
let buffers = [];
|
||||
channel.addEventListener("open", ev => {
|
||||
display(`${peerName} 给你发了一个文件: ${filename}。 接收中...`)
|
||||
})
|
||||
channel.addEventListener("message", ev => {
|
||||
buffers.push(ev.data)
|
||||
})
|
||||
channel.addEventListener("close", () => {
|
||||
room.event.addEventListener("file_received", ({ filename, buffer }) => {
|
||||
var link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(new Blob(buffers));
|
||||
link.href = window.URL.createObjectURL(buffer);
|
||||
link.download = filename;
|
||||
link.innerHTML = filename
|
||||
display(
|
||||
link.innerHTML = filename;
|
||||
ui.display(
|
||||
"文件:",
|
||||
link,
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
peerMap.set(peerName, peer);
|
||||
}
|
||||
let peer = peerMap.get(peerName);
|
||||
ui.event.addEventListener("send_message", ({ receiver, value }) => {
|
||||
let channel = room.channels.get(receiver)
|
||||
if (!channel) {
|
||||
|
||||
let resultSdp = null, resultMessageType;
|
||||
if(wsMessage.message.type === MessageDiscoverResponse) {
|
||||
let channel = peer.createDataChannel("chat");
|
||||
channel.addEventListener("open", (ev) => {
|
||||
addPeer(peerName)
|
||||
})
|
||||
channel.addEventListener("message", (ev) => {
|
||||
display(`${peerName} -> 你: ${ev.data}`)
|
||||
})
|
||||
channel.addEventListener("close", () => {
|
||||
removePeer(peerName)
|
||||
})
|
||||
channelMap.set(peerName, channel);
|
||||
|
||||
|
||||
resultSdp = await peer.createOffer();
|
||||
resultMessageType = MessageSessionOffer;
|
||||
peer.setLocalDescription(resultSdp)
|
||||
console.log("set local offer")
|
||||
} else {
|
||||
peer.setRemoteDescription(JSON.parse(wsMessage.message.sdp))
|
||||
console.log("set remote offer")
|
||||
resultSdp = await peer.createAnswer();
|
||||
resultMessageType = MessageSessionAnswer;
|
||||
peer.setLocalDescription(resultSdp)
|
||||
console.log("set local answer")
|
||||
}
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
message: {
|
||||
type: resultMessageType,
|
||||
sdp: JSON.stringify(resultSdp),
|
||||
sender,
|
||||
kind: 3,
|
||||
},
|
||||
room,
|
||||
sender,
|
||||
receiver: wsMessage.sender,
|
||||
}))
|
||||
}
|
||||
|
||||
let peer = peerMap.get(wsMessage.sender)
|
||||
|
||||
switch (wsMessage.message.type) {
|
||||
case MessageBootstrap:
|
||||
sender = wsMessage.sender;
|
||||
ws.send(JSON.stringify({
|
||||
message: {
|
||||
type: MessageDiscoverRequest,
|
||||
},
|
||||
room,
|
||||
sender: wsMessage.sender,
|
||||
}))
|
||||
display(`决定了,就叫 ${sender}. 我们等等小伙伴吧...`)
|
||||
break;
|
||||
case MessageDiscoverRequest:
|
||||
display(`正在尝试连接 ${wsMessage.sender}`)
|
||||
ws.send(JSON.stringify({
|
||||
message: {
|
||||
type: MessageDiscoverResponse,
|
||||
},
|
||||
room,
|
||||
sender,
|
||||
receiver: wsMessage.sender,
|
||||
}))
|
||||
break
|
||||
case MessageDiscoverResponse:
|
||||
recreateAndSetupPeer(wsMessage.sender)
|
||||
break;
|
||||
case MessageSessionOffer:
|
||||
recreateAndSetupPeer(wsMessage.sender)
|
||||
break
|
||||
case MessageSessionAnswer:
|
||||
console.log("set remote answer")
|
||||
peer.setRemoteDescription(JSON.parse(wsMessage.message.sdp))
|
||||
peer.restartIce()
|
||||
break
|
||||
case MessageICECandidate:
|
||||
let candidate = JSON.parse(wsMessage.message.candidate);
|
||||
if(!peer) {
|
||||
console.warn("candidate dropped", candidate)
|
||||
ui.display(`${receiver} 没准备好,这条消息发不出去: ${value}`)
|
||||
return
|
||||
}
|
||||
peer.addIceCandidate(candidate)
|
||||
}
|
||||
}
|
||||
ui.display(`你 -> ${receiver}: ${value}`)
|
||||
channel.send(value)
|
||||
})
|
||||
ui.event.addEventListener("send_file", ({file,receiver}) => {
|
||||
let peer = room.peers.get(receiver);
|
||||
|
||||
reconnect()
|
||||
let reader = new FileReader()
|
||||
let dc = peer.createDataChannel(`file:${file.name}`)
|
||||
reader.addEventListener("load", (ev) => {
|
||||
dc.send(reader.result)
|
||||
dc.close()
|
||||
})
|
||||
dc.addEventListener("open", () => {
|
||||
reader.readAsArrayBuffer(file)
|
||||
})
|
||||
})
|
||||
|
||||
let display = ui.display.bind(ui);
|
||||
|
||||
room.reconnect()
|
||||
})()
|
||||
</script>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user