404 lines
11 KiB
TypeScript
404 lines
11 KiB
TypeScript
import { render } from "solid-js/web";
|
|
import {
|
|
startLocationRecording,
|
|
stopLocationRecording,
|
|
startClickRecording,
|
|
stopClickRecording,
|
|
} from "./eventTrackers";
|
|
import ControlsBox from "~/entrypoints/content/ControlsBox";
|
|
import { messages } from '~/utils/messages';
|
|
import { convertBlobToBase64, getChromeFullVersion } from "./utils";
|
|
import "./style.css";
|
|
import "~/assets/main.css";
|
|
|
|
export default defineContentScript({
|
|
matches: ["*://*/*"],
|
|
cssInjectionMode: "ui",
|
|
|
|
async main(ctx) {
|
|
if (!ctx.isValid) {
|
|
console.error("Spot: context is invalidated on mount")
|
|
return;
|
|
}
|
|
const ui = await createShadowRootUi(ctx, {
|
|
name: "spot-ui",
|
|
position: "inline",
|
|
anchor: "body",
|
|
append: "first",
|
|
onMount: (container, s, host) => {
|
|
Object.assign(host.style, { visibility: "visible", display: "block" });
|
|
|
|
return render(
|
|
() => (
|
|
<ControlsBox
|
|
getMicStatus={getMicStatus}
|
|
pause={pause}
|
|
resume={resume}
|
|
stop={stop}
|
|
getVideoData={getVideoData}
|
|
onClose={onClose}
|
|
getClockStart={getClockStart}
|
|
muteMic={muteMic}
|
|
unmuteMic={unmuteMic}
|
|
getInitState={() => recState}
|
|
callRecording={countEnd}
|
|
onRestart={onRestart}
|
|
getErrorEvents={getErrorEvents}
|
|
getAudioPerm={getAudioPerm}
|
|
/>
|
|
),
|
|
container,
|
|
);
|
|
},
|
|
onRemove: (unmount) => {
|
|
unmount?.();
|
|
},
|
|
});
|
|
|
|
let micResponse: boolean | null = null;
|
|
const getMicStatus = async () => {
|
|
return new Promise((res) => {
|
|
browser.runtime.sendMessage({
|
|
type: messages.content.from.checkMicStatus,
|
|
});
|
|
let int = setInterval(() => {
|
|
if (micResponse !== null) {
|
|
clearInterval(int);
|
|
res(micResponse);
|
|
}
|
|
}, 200);
|
|
});
|
|
};
|
|
// no perm - muted - unmuted
|
|
type AudioPermState = 0 | 1 | 2;
|
|
let audioPerm: AudioPermState = 0;
|
|
const getAudioPerm = (): AudioPermState => audioPerm;
|
|
let clockStart = 0;
|
|
let recState = "stopped";
|
|
const getClockStart = () => {
|
|
return clockStart;
|
|
};
|
|
let data: Record<string, any> | null = null;
|
|
const videoChunks: string[] = [];
|
|
let chunksReady = false;
|
|
let errorsReady = false;
|
|
const errorData: { title: string; time: number }[] = [];
|
|
|
|
const getErrorEvents = async (): Promise<any> => {
|
|
let tries = 0;
|
|
browser.runtime.sendMessage({ type: "ort:get-error-events" });
|
|
return new Promise((res) => {
|
|
const interval = setInterval(async () => {
|
|
if (errorsReady) {
|
|
clearInterval(interval);
|
|
errorsReady = false;
|
|
res(errorData);
|
|
}
|
|
// 3 sec timeout
|
|
if (tries > 30) {
|
|
clearInterval(interval);
|
|
res([]);
|
|
}
|
|
tries += 1;
|
|
}, 100);
|
|
});
|
|
};
|
|
|
|
const getVideoData = async (): Promise<any> => {
|
|
let tries = 0;
|
|
return new Promise((res) => {
|
|
const interval = setInterval(async () => {
|
|
if (data && chunksReady) {
|
|
clearInterval(interval);
|
|
videoChunks.length = 0;
|
|
chunksReady = false;
|
|
res(data);
|
|
}
|
|
// 10 sec timeout
|
|
if (tries > 100) {
|
|
clearInterval(interval);
|
|
res(null);
|
|
}
|
|
tries += 1;
|
|
}, 100);
|
|
});
|
|
};
|
|
|
|
const stop = async () => {
|
|
recState = "stopped";
|
|
stopClickRecording();
|
|
stopLocationRecording();
|
|
const result = await browser.runtime.sendMessage({ type: messages.content.from.toStop });
|
|
if (result.status === "full") {
|
|
chunksReady = true;
|
|
data = result;
|
|
return result;
|
|
}
|
|
if (result.status === "parts") {
|
|
return new Promise((res) => {
|
|
const interval = setInterval(() => {
|
|
if (chunksReady) {
|
|
data = Object.assign({}, result, {
|
|
base64data: videoChunks.concat([]),
|
|
});
|
|
clearInterval(interval);
|
|
res(true);
|
|
}
|
|
}, 100);
|
|
});
|
|
} else {
|
|
console.log(result);
|
|
}
|
|
};
|
|
|
|
const pause = () => {
|
|
recState = "paused";
|
|
browser.runtime.sendMessage({ type: messages.content.from.pause });
|
|
};
|
|
|
|
const resume = () => {
|
|
recState = "recording";
|
|
browser.runtime.sendMessage({ type: messages.content.from.resume });
|
|
};
|
|
|
|
const muteMic = () => {
|
|
browser.runtime.sendMessage({ type: messages.content.from.muteMic });
|
|
};
|
|
|
|
const unmuteMic = () => {
|
|
browser.runtime.sendMessage({ type: messages.content.from.unmuteMic });
|
|
};
|
|
|
|
const onClose = async (
|
|
save: boolean,
|
|
spotObj?: {
|
|
blob?: Blob;
|
|
name?: string;
|
|
comment?: string;
|
|
useHook?: boolean;
|
|
thumbnail?: string;
|
|
crop?: [number, number];
|
|
},
|
|
) => {
|
|
if (!save || !spotObj) {
|
|
await chrome.runtime.sendMessage({
|
|
type: "ort:discard",
|
|
});
|
|
stopClickRecording();
|
|
stopLocationRecording();
|
|
ui.remove();
|
|
recState = "stopped";
|
|
return;
|
|
}
|
|
const { name, comment, useHook, thumbnail, crop, blob } = spotObj;
|
|
const videoData = await convertBlobToBase64(blob!);
|
|
const resolution = `${window.screen.width}x${window.screen.height}`;
|
|
const browserVersion = getChromeFullVersion();
|
|
const spot = {
|
|
name,
|
|
comment,
|
|
useHook,
|
|
preview: thumbnail,
|
|
resolution,
|
|
browserVersion,
|
|
crop,
|
|
};
|
|
|
|
try {
|
|
await browser.runtime.sendMessage({
|
|
type: messages.content.from.saveSpotData,
|
|
spot,
|
|
});
|
|
let index = 0;
|
|
for (let part of videoData.result) {
|
|
if (part) {
|
|
await browser.runtime.sendMessage({
|
|
type: messages.content.from.saveSpotVidChunk,
|
|
part,
|
|
index,
|
|
total: videoData.result.length,
|
|
});
|
|
index += 1;
|
|
}
|
|
}
|
|
|
|
ui.remove();
|
|
} catch (e) {
|
|
console.trace(
|
|
"error saving video",
|
|
spot,
|
|
videoData,
|
|
resolution,
|
|
browserVersion,
|
|
);
|
|
console.error(e);
|
|
}
|
|
};
|
|
|
|
window.addEventListener("message", (event) => {
|
|
if (event.data.type === "orspot:ping") {
|
|
window.postMessage({ type: "orspot:pong" }, "*");
|
|
}
|
|
if (event.data.type === "orspot:token") {
|
|
window.postMessage({ type: "orspot:logged" }, "*");
|
|
void browser.runtime.sendMessage({
|
|
type: messages.content.from.setLoginToken,
|
|
token: event.data.token,
|
|
});
|
|
}
|
|
if (event.data.type === "orspot:invalidate") {
|
|
void browser.runtime.sendMessage({
|
|
type: messages.content.from.invalidateToken,
|
|
});
|
|
}
|
|
if (event.data.type === "ort:bump-logs") {
|
|
void chrome.runtime.sendMessage({
|
|
type: messages.injected.from.bumpLogs,
|
|
logs: event.data.logs,
|
|
});
|
|
}
|
|
if (event.data.type === "ort:bump-network") {
|
|
void chrome.runtime.sendMessage({
|
|
type: messages.injected.from.bumpNetwork,
|
|
event: event.data.event,
|
|
});
|
|
}
|
|
});
|
|
|
|
let injected = false;
|
|
function injectScript() {
|
|
if (injected) return;
|
|
injected = true;
|
|
const scriptEl = document.createElement("script");
|
|
scriptEl.src = browser.runtime.getURL("/injected.js");
|
|
document.head.appendChild(scriptEl);
|
|
}
|
|
function startConsoleTracking() {
|
|
injectScript();
|
|
setTimeout(() => {
|
|
window.postMessage({ type: "injected:c-start" });
|
|
}, 100);
|
|
}
|
|
function startNetworkTracking() {
|
|
injectScript();
|
|
setTimeout(() => {
|
|
window.postMessage({ type: "injected:n-start" });
|
|
}, 100);
|
|
}
|
|
|
|
function stopConsoleTracking() {
|
|
window.postMessage({ type: "injected:c-stop" });
|
|
}
|
|
|
|
function stopNetworkTracking() {
|
|
window.postMessage({ type: "injected:n-stop" });
|
|
}
|
|
|
|
function onRestart() {
|
|
chrome.runtime.sendMessage({
|
|
type: messages.content.from.restart,
|
|
});
|
|
stopClickRecording();
|
|
stopLocationRecording();
|
|
stopConsoleTracking();
|
|
recState = "stopped";
|
|
ui.remove();
|
|
}
|
|
|
|
function mountNotifications() {
|
|
const scriptEl = document.createElement("script");
|
|
scriptEl.src = browser.runtime.getURL("/notifications.js");
|
|
document.head.appendChild(scriptEl);
|
|
}
|
|
|
|
function unmountNotifications() {
|
|
window.postMessage({ type: "ornotif:stop" });
|
|
}
|
|
|
|
mountNotifications();
|
|
|
|
let onEndObj = {};
|
|
async function countEnd(): Promise<boolean> {
|
|
return browser.runtime
|
|
.sendMessage({ ...onEndObj, type: messages.content.from.countEnd })
|
|
.then((r: boolean) => {
|
|
onEndObj = {};
|
|
return r;
|
|
});
|
|
}
|
|
|
|
setInterval(() => {
|
|
void browser.runtime.sendMessage({ type: messages.content.from.contentReady });
|
|
}, 250);
|
|
// @ts-ignore false positive
|
|
browser.runtime.onMessage.addListener((message: any, resp) => {
|
|
if (message.type === messages.content.to.mount) {
|
|
if (recState === "count") return;
|
|
recState = "count";
|
|
onEndObj = {
|
|
area: message.area,
|
|
mic: message.mic,
|
|
audioId: message.audioId,
|
|
};
|
|
audioPerm = message.audioPerm;
|
|
ui.mount();
|
|
}
|
|
if (message.type === messages.content.to.start) {
|
|
if (recState === "recording") return;
|
|
clockStart = message.time;
|
|
recState = "recording";
|
|
micResponse = null;
|
|
startClickRecording();
|
|
startLocationRecording();
|
|
if (message.withConsole) {
|
|
startConsoleTracking();
|
|
}
|
|
if (message.withNetwork) {
|
|
startNetworkTracking();
|
|
}
|
|
browser.runtime.sendMessage({ type: messages.content.from.started });
|
|
if (message.shouldMount) {
|
|
ui.mount();
|
|
}
|
|
return "pong";
|
|
}
|
|
if (message.type === messages.content.to.notification) {
|
|
window.postMessage(
|
|
{
|
|
type: "ornotif:display",
|
|
message: message.message,
|
|
},
|
|
"*",
|
|
);
|
|
}
|
|
if (message.type === messages.content.to.unmount) {
|
|
stopClickRecording();
|
|
stopLocationRecording();
|
|
stopConsoleTracking();
|
|
stopNetworkTracking();
|
|
recState = "stopped";
|
|
ui.remove();
|
|
return "unmounted";
|
|
}
|
|
if (message.type === messages.content.to.videoChunk) {
|
|
videoChunks[message.index] = message.data;
|
|
if (message.total === message.index + 1) {
|
|
chunksReady = true;
|
|
}
|
|
}
|
|
if (message.type === messages.content.to.spotSaved) {
|
|
window.postMessage({ type: "ornotif:copy", url: message.url });
|
|
}
|
|
if (message.type === messages.content.to.stop) {
|
|
window.postMessage({ type: "content:trigger-stop" }, "*");
|
|
}
|
|
if (message.type === messages.content.to.micStatus) {
|
|
micResponse = message.micStatus;
|
|
}
|
|
if (message.type === messages.content.to.updateErrorEvents) {
|
|
errorsReady = true;
|
|
errorData.push(...message.errorData);
|
|
}
|
|
});
|
|
},
|
|
});
|