From: Ruben Beltran del Rio Date: Wed, 13 Sep 2023 15:29:35 +0000 (+0200) Subject: Refactor content script X-Git-Url: https://git.r.bdr.sh/rbdr/junction/commitdiff_plain/06b7617a863d00dbcc91d1e46f09942c6aad366e Refactor content script --- diff --git a/Makefile b/Makefile index 0aecb76..14c1f2d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,4 @@ -default: preview - -build: docker-build +default: package start: docker-compose up @@ -8,10 +6,16 @@ start: docker-build: docker-compose build -package: package-for-firefox +build: create-build-folder + cd extension && pnpm build + +package: package-for-firefox package-for-chrome -package-for-firefox: create-build-folder +package-for-firefox: build cd extension && zip -r ../build/junction.xpi manifest.json junction.js build/content_script.js icons/ sounds/ +package-for-chrome: build + cd extension && zip -r ../build/junction.zip manifest.json junction.js build/content_script.js icons/ sounds/ + create-build-folder: mkdir -p build diff --git a/extension/content_script.js b/extension/content_script.js index 26da2b0..721176e 100644 --- a/extension/content_script.js +++ b/extension/content_script.js @@ -1,6 +1,14 @@ import { io } from "socket.io-client"; -import Peers from "./peers"; -import Media from "./media"; +import { initializeTada } from "./tada"; +import { + addPeer, + addIceCandidate, + answerPeerOffer, + processPeerAnswer, + removePeer, + resetPeers, +} from "./peers"; +import { startMedia, stopMedia } from "./media"; const internals = { kSocketUrl: "https://junction.tranquil.services", @@ -8,118 +16,57 @@ const internals = { port: null, socket: null, - peers: {}, onMessage(message) { internals[message.action](message.data); }, + onError(error) { + // TODO: How do we want to handle errors? + console.error(error.stack || error); + }, + async joinAudioCall({ currentUrl, tada }) { try { - const mediaStream = await Media.start(); + const mediaStream = await startMedia(); + const playTada = initializeTada(tada); - internals.socket = io(internals.kSocketUrl, { + const socket = (internals.socket = io(internals.kSocketUrl, { transports: ["websocket"], - }); + })); - internals.socket.on("error", function (error) { - console.error("GENERAL ERROR", error); - }); + socket.on("error", internals.handleError); + socket.on("connect_error", internals.handleError); - internals.socket.on("connect_error", function (error) { - console.error("CONNNECT ERROR", error); - }); - - internals.socket.on("connect", function () { - console.log("Connected to signaling server, group: ", currentUrl); - internals.socket.emit("join", { + socket.on("connect", () => { + console.debug("Connected to signaling server, group: ", currentUrl); + playTada(); + socket.emit("join", { room: currentUrl, }); }); - internals.socket.on("disconnect", function () { - console.log("disconnected from signaling server"); + socket.on("disconnect", () => { + console.debug("disconnected from signaling server"); }); - internals.socket.on("addPeer", function ({ peerId }) { - /** - * Eventually the whole rtc connection logic should be moved to Peers. - * Now it only plays tadas. - */ - Peers.add(peerId, tada); - - const peerConnection = new RTCPeerConnection( - { iceServers: internals.kIceServers }, - { optional: [{ DtlsSrtpKeyAgreement: true }] }, - ); - - internals.peers[peerId] = peerConnection; - mediaStream.getTracks().forEach((track) => { - peerConnection.addTrack(track, mediaStream); + socket.on("addPeer", ({ peerId }) => { + addPeer({ + peerId, + mediaStream, + onOffer: (data) => socket.emit("relayOffer", data), + socket }); - - peerConnection.onicecandidate = (event) => { - if (event.candidate) { - internals.socket.emit("relayICECandidate", { - peerId: peerId, - candidate: event.candidate, - }); - } - }; - - const remoteStream = new MediaStream(); - peerConnection.ontrack = (event) => { - remoteStream.addTrack(event.track); - const remoteAudioElement = new Audio(); - remoteAudioElement.srcObject = remoteStream; - remoteAudioElement.play(); - }; - - peerConnection.onnegotiationneeded = async () => { - console.log("Creating RTC offer to ", peerId); - const offer = await peerConnection.createOffer(); - await peerConnection.setLocalDescription(offer); - - // Emit the offer to the peer - internals.socket.emit("relayOffer", { offer, peerId }); - }; - - console.log(`There are now ${Peers.count()} participants`); + playTada(); }); - internals.socket.on("offerReceived", async ({ offer, peerId }) => { - const peerConnection = internals.peers[peerId]; - - const remoteDescription = new RTCSessionDescription(offer); - await peerConnection.setRemoteDescription(remoteDescription); - - const answer = await peerConnection.createAnswer(); - await peerConnection.setLocalDescription(answer); - - // Send the answer to the peer - internals.socket.emit("relayAnswer", { answer, peerId }); + socket.on("offerReceived", async ({ peerId, offer }) => { + socket.emit("relayAnswer", await answerPeerOffer({ peerId, offer })); }); - internals.socket.on("answerReceived", async ({ answer, peerId }) => { - const peerConnection = internals.peers[peerId]; - const remoteDescription = new RTCSessionDescription(answer); - await peerConnection.setRemoteDescription(remoteDescription); - }); - - internals.socket.on( - "ICECandidateReceived", - async ({ candidate, peerId }) => { - const peerConnection = internals.peers[peerId]; - const iceCandidate = new RTCIceCandidate(candidate); - await peerConnection.addIceCandidate(iceCandidate); - }, - ); - - internals.socket.on("removePeer", function ({ peerId }) { - delete internals.peers[peerId]; - Peers.remove(peerId); - console.log(`There are now ${Peers.count()} participants`); - }); + socket.on("answerReceived", (data) => processPeerAnswer(data)); + socket.on("ICECandidateReceived", (data) => addIceCandidate(data)); + socket.on("removePeer", (data) => removePeer(data)); } catch (err) { internals.port.postMessage({ action: "error", @@ -129,8 +76,8 @@ const internals = { }, hangUp() { - Peers.reset(); - Media.stop(); + resetPeers(); + stopMedia(); internals.socket.close(); internals.port.disconnect(); }, @@ -139,7 +86,7 @@ const internals = { internals.port = chrome.runtime.connect({ name: "content" }); internals.port.onMessage.addListener(internals.onMessage); -console.log("Content Script Loaded"); +console.debug("Content Script Loaded"); // Indicates to the background script that we executed correctly true; diff --git a/extension/media.js b/extension/media.js index 5e83ac7..416053a 100644 --- a/extension/media.js +++ b/extension/media.js @@ -2,26 +2,24 @@ const internals = { mediaStream: null, }; -export default { - async start() { - internals.mediaStream = - internals.mediaStream || - (await navigator.mediaDevices.getUserMedia({ - audio: true, - })); +export async function startMedia() { + internals.mediaStream = + internals.mediaStream || + (await navigator.mediaDevices.getUserMedia({ + audio: true, + })); - return internals.mediaStream; - }, + return internals.mediaStream; +} - stop() { - if (!internals.mediaStream) { - return; - } +export async function stopMedia() { + if (!internals.mediaStream) { + return; + } - for (const track of internals.mediaStream.getAudioTracks()) { - track.stop(); - } + for (const track of internals.mediaStream.getAudioTracks()) { + track.stop(); + } - internals.mediaStream = null; - }, -}; + internals.mediaStream = null; +} diff --git a/extension/peers.js b/extension/peers.js index 2b4e3d6..f9153fd 100644 --- a/extension/peers.js +++ b/extension/peers.js @@ -5,13 +5,7 @@ const internals = { const audioElement = document.createElement("audio"); audioElement.setAttribute("class", "junction-call-audio"); audioElement.autoplay = "autoplay"; - - // WE WILL NOT LOSE TADA SUPPORT - if (typeof source === "string") { - audioElement.src = source; - } else { - audioElement.srcObject = source; - } + audioElement.srcObject = source; document.querySelector("body").appendChild(audioElement); @@ -19,25 +13,81 @@ const internals = { }, }; -export default { - add(id, source) { - internals.peers[id] && this.remove(id); - internals.peers[id] = internals.createAudioElement(source); - }, +export function addPeer({ peerId, mediaStream, onOffer, socket }) { + const peerConnection = new RTCPeerConnection( + { iceServers: internals.kIceServers }, + { optional: [{ DtlsSrtpKeyAgreement: true }] }, + ); - remove(id) { - internals.peers[id] && internals.peers[id].remove(); - delete internals.peers[id]; - }, + internals.peers[peerId] = peerConnection; + mediaStream.getTracks().forEach((track) => { + peerConnection.addTrack(track, mediaStream); + }); - count() { - return Object.keys(internals.peers).length; - }, + peerConnection.onicecandidate = (event) => { + if (event.candidate) { + socket.emit("relayICECandidate", { + peerId: peerId, + candidate: event.candidate, + }); + } + }; - reset() { - internals.peers = {}; - document - .querySelectorAll(".junction-call-audio") - .forEach((audioElement) => audioElement.remove()); - }, -}; + const remoteStream = new MediaStream(); + peerConnection.ontrack = (event) => { + remoteStream.addTrack(event.track); + const remoteAudioElement = new Audio(); + remoteAudioElement.srcObject = remoteStream; + remoteAudioElement.play(); + }; + + peerConnection.onnegotiationneeded = async () => { + console.debug("Creating RTC offer to ", peerId); + const offer = await peerConnection.createOffer(); + await peerConnection.setLocalDescription(offer); + + onOffer({ peerId, offer }); + }; + + console.info(`There are now ${countPeers()} participants`); +} + +export function removePeer({ peerId }) { + delete internals.peers[peerId]; + console.info(`There are now ${countPeers()} participants`); +} + +export async function answerPeerOffer({ peerId, offer }) { + const peerConnection = internals.peers[peerId]; + + const remoteDescription = new RTCSessionDescription(offer); + await peerConnection.setRemoteDescription(remoteDescription); + + const answer = await peerConnection.createAnswer(); + await peerConnection.setLocalDescription(answer); + + return { peerId, answer }; +} + +export async function processPeerAnswer({ peerId, answer }) { + const peerConnection = internals.peers[peerId]; + const remoteDescription = new RTCSessionDescription(answer); + await peerConnection.setRemoteDescription(remoteDescription); +} + +export async function addIceCandidate({ peerId, candidate }) { + const peerConnection = internals.peers[peerId]; + const iceCandidate = new RTCIceCandidate(candidate); + await peerConnection.addIceCandidate(iceCandidate); +} + +export function countPeers() { + return Object.keys(internals.peers).length; +} + +export function resetPeers() { + internals.peers = {}; + document + .querySelectorAll(".junction-call-audio") + .forEach((audioElement) => audioElement.remove()); +} diff --git a/extension/tada.js b/extension/tada.js new file mode 100644 index 0000000..5898aea --- /dev/null +++ b/extension/tada.js @@ -0,0 +1,8 @@ +export function initializeTada(source) { + const audioElement = document.createElement("audio"); + audioElement.setAttribute("class", "junction-tada"); + audioElement.src = source; + document.querySelector("body").appendChild(audioElement); + + return () => audioElement.play(); +}