import orLogo from "~/assets/orSpot.svg"; import micOff from "~/assets/mic-off-red.svg"; import micOn from "~/assets/mic-on-dark.svg"; import Login from "~/entrypoints/popup/Login"; import Settings from "~/entrypoints/popup/Settings"; import { createSignal, createEffect, onMount } from "solid-js"; import Dropdown from "~/entrypoints/popup/Dropdown"; import Button from "~/entrypoints/popup/Button"; import { ChevronSvg, RecordDesktopSvg, RecordTabSvg, HomePageSvg, SlackSvg, SettingsSvg, } from "./Icons"; async function getAudioDevices() { try { await navigator.mediaDevices.getUserMedia({ audio: true }); const devices = await navigator.mediaDevices.enumerateDevices(); const audioDevices = devices .filter((device) => device.kind === "audioinput") .map((device) => ({ label: device.label, id: device.deviceId })); return { granted: true, audioDevices }; } catch (error) { console.error("Error accessing audio devices:", error); const msg = error.message ?? ""; return { granted: false, denied: msg.includes("denied"), audioDevices: [], }; } } const orSite = () => { window.open("https://openreplay.com", "_blank"); }; function Header({ openSettings }: { openSettings: () => void }) { const openHomePage = async () => { const { settings } = await chrome.storage.local.get("settings"); return window.open(`${settings.ingestPoint}/spots`, "_blank"); }; return (
{"OpenReplay
OpenReplay Spot
); } const STATE = { empty: "empty", login: "login", ready: "ready", starting: "starting", recording: "recording", }; function App() { const [state, setState] = createSignal(STATE.empty); const [isSettingsOpen, setIsSettingsOpen] = createSignal(false); const [mic, setMic] = createSignal(false); const [selectedAudioDevice, setSelectedAudioDevice] = createSignal(""); const [hasPermissions, setHasPermissions] = createSignal(false); onMount(() => { browser.runtime.onMessage.addListener((message) => { if (message.type === "popup:no-login") { setState(STATE.login); } if (message.type === "popup:login") { setState(STATE.ready); } if (message.type === "popup:stopped") { setState(STATE.ready); } if (message.type === "popup:started") { setState(STATE.recording); } if (message.type === "popup:mic-status") { setMic(message.status); } }); void browser.runtime.sendMessage({ type: "popup:check-status" }); }); const startRecording = async (reqTab: "tab" | "desktop") => { setState(STATE.starting); await browser.runtime.sendMessage({ type: "popup:start", area: reqTab, mic: mic(), audioId: selectedAudioDevice(), permissions: hasPermissions(), }); window.close(); }; const stopRecording = () => { void browser.runtime.sendMessage({ type: "popup:stop", mic: mic(), audioId: selectedAudioDevice(), }); }; const toggleMic = async () => { setMic(!mic()); }; const openSettings = () => { setIsSettingsOpen(true); }; const closeSettings = () => { setIsSettingsOpen(false); }; return (
{isSettingsOpen() ? ( ) : (
{state() === STATE.login ? ( ) : ( <> {state() === STATE.recording ? (
) : null} )}
)} ); } interface IAudioPicker { mic: () => boolean; toggleMic: () => void; selectedAudioDevice: () => string; setSelectedAudioDevice: (value: string) => void; setHasPermissions: (value: boolean) => void; } function AudioPicker(props: IAudioPicker) { const [audioDevices, setAudioDevices] = createSignal( [] as { label: string; id: string }[], ); const [checkedAudioDevices, setCheckedAudioDevices] = createSignal(0); createEffect(() => { chrome.storage.local.get("audioPerm", (data) => { if (data.audioPerm && audioDevices().length === 0) { props.setHasPermissions(true); void checkAudioDevices(); } }); }); const checkAudioDevices = async () => { const { granted, audioDevices, denied } = await getAudioDevices(); if (!granted && !denied) { void browser.runtime.sendMessage({ type: "popup:get-audio-perm", }); browser.runtime.onMessage.addListener((message) => { if (message.type === "popup:audio-perm") { void checkAudioDevices(); } }); } else if (audioDevices.length > 0) { chrome.storage.local.set({ audioPerm: granted }); setAudioDevices(audioDevices); props.setSelectedAudioDevice(audioDevices[0]?.id || ""); } }; const checkAudio = async () => { if (checkedAudioDevices() > 0) { return; } setCheckedAudioDevices(1); await checkAudioDevices(); setCheckedAudioDevices(2); }; const onSelect = (value) => { props.setSelectedAudioDevice(value); if (!props.mic()) { props.toggleMic(); } }; const onMicToggle = async () => { if (!audioDevices().length) { return await checkAudioDevices(); } if (!props.selectedAudioDevice() && audioDevices().length) { onSelect(audioDevices()[0].id); } else { props.toggleMic(); } }; return (
{props.mic()
{audioDevices().length === 0 ? (
{checkedAudioDevices() === 1 ? "Loading audio devices" : "Grant microphone access"}
) : ( )}
); } export default App;