openreplay/spot/entrypoints/content/index.tsx
Delirium 1326bb2eae
feat spot: init commit for extension (#2452)
* feat spot: init commit for extension

* nvmrc

* fix login flow

* Spots Gridview Updates (#2422)

* feat ui: login flow for spot extension

* spot list, store and service created

* some fixing for header

* start work on single spot

* spot player start

* header for player, comments, icons, etc

* split stuff into compoennts, create player state manager

* player controls, activity panel etc etc

* comments, empty page, rename and stuff

* interval buttons etc

* access modal

* pubkey support

* fix tooltip

* limit 10 -> 9

* hls lib instead of videojs

* some warnings

* fix date display for exp

* change public links

* display more client data

* fix cleaning, init comment

* map network to replay player network ev

* stream support, console panel, close panels on X

* fixing streaming, destroy on leave

* fix autoplay

* show notification on spot login

* fix spot login

* backup player added, fix audio issue

* show thumbnail when no video, add spot roles

* add poster thumbnail

* some fixes to video check

* fix events jump

* fix play btn

* try catch over pubkey

* feat ui: login flow for spot extension

* spot list, store and service created

* some fixing for header

* start work on single spot

* spot player start

* header for player, comments, icons, etc

* split stuff into compoennts, create player state manager

* player controls, activity panel etc etc

* comments, empty page, rename and stuff

* interval buttons etc

* access modal

* pubkey support

* fix tooltip

* limit 10 -> 9

* hls lib instead of videojs

* some warnings

* fix date display for exp

* change public links

* display more client data

* fix cleaning, init comment

* map network to replay player network ev

* stream support, console panel, close panels on X

* fixing streaming, destroy on leave

* fix autoplay

* show notification on spot login

* fix spot login

* backup player added, fix audio issue

* show thumbnail when no video, add spot roles

* add poster thumbnail

* some fixes to video check

* fix events jump

* fix play btn

* try catch over pubkey

* icons

* Various updates

* Update SVG.tsx

* Update SideMenu.tsx

* SpotList & Menu updates

* feat ui: login flow for spot extension

* spot list, store and service created

* some fixing for header

* start work on single spot

* spot player start

* header for player, comments, icons, etc

* split stuff into compoennts, create player state manager

* player controls, activity panel etc etc

* comments, empty page, rename and stuff

* interval buttons etc

* access modal

* pubkey support

* fix tooltip

* limit 10 -> 9

* hls lib instead of videojs

* some warnings

* fix date display for exp

* change public links

* display more client data

* fix cleaning, init comment

* map network to replay player network ev

* stream support, console panel, close panels on X

* fixing streaming, destroy on leave

* fix autoplay

* show notification on spot login

* fix spot login

* backup player added, fix audio issue

* show thumbnail when no video, add spot roles

* add poster thumbnail

* some fixes to video check

* fix events jump

* fix play btn

* try catch over pubkey

* icons

* spot login pinging

* Spot List & Player Updates

* move spot login flow to login comp, use separate spot login path for unique jwt

* invalidate spot jwt on logout

* add visual data on page load event

* typo fix

* Spot Listing improvements post review.

* Update SpotListItem.tsx

* Improved Spot List and Item Details

* Minor improvements

* More improvements

* Public player header improvements

* Moved formatExpirationTime to utils

* fixes after merge

---------

Co-authored-by: nick-delirium <nikita@openreplay.com>

* set sso link to <a>?

* some small perf fixes

* login duck reformat...

* Update frontend.yaml

* add observer to spot list header

* split list header

* update spotjwt param in router

* fix toast in router

* fix async fetch, move ctx

* capture space btn ev

* fix header link

* public sharing error msg

* fix err msg for unsuccessful rec start

* fix list alignment

* Caching assets. Finally!!!

* fix typing in comment field

* add pubkey to comments, fix console jump btn

* no content comp

* change refresh token logic

* move thumbnail ts

* move thumbnail ts

* fix tab change

* switch up toggler

* early exit if no jwt present

* regenerate icons

* fix location str

* fix ctx

* change thumnail res, return autoplay for video player

* parse links in console rows, fix injected method parse?

* remove ts from js

* fix console parsing order?

* fixes for autoplay

* xray for spot player

* move to spot list after login;
esc to cancel;
fix signup link;
move ux commit

* kb sc for skipping; xray for spot ext

* track aborted requests

* tooltip for readability

* fixing empty state

* New blank state + various minor improvements (#2471)

* New blank state + various minor improvements

* apres merge

---------

Co-authored-by: nick-delirium <nikita@openreplay.com>

* rm temp v

* init or card

* empty state debug

* empty state debug

* empty state debug

* fix initor img

* spotonly scope support

* Improved Spot dead-end pages (#2475)

* Improved Spot dead-end pages

* Initiate OpenReplay Setup and some more

* get scope changes

* fix crash

* scope upgrade/downgrade

* scope setup flow

* ping for backend

* upgrade wxt deps

* cancel ping int on expiration

* check rec status

* fix ping

* check video processing state

* check video processing state

* fix xray close, network highlight, fcp rounding

* update wxt, move open spot stuff to settings

* fix some history issues

* fix spot login flow

* fix spot login again

* fix spot login again

* don't send two requests

* limit messages for logged users

* limit messages for logged users

* fix public ignore

* microphone stuff

* microphone stuff

* Various improvements (#2509)

* Various improvements

- Updated icons in mic settings
- Included prefix in Spot title
- Save recording notification has been updated
- Other minor UI improvements

* Inline declaration of spot name field, and settings UI

* str f

---------

Co-authored-by: nick-delirium <nikita@openreplay.com>

* UI changes in player header, spot list (#2510)

* Added UI elements in player page

- Badge with counts for comments
- Download and Delete dropdown in player
- Spot selection -- UI improvement

* Minor copy updates

* completing changes

---------

Co-authored-by: nick-delirium <nikita@openreplay.com>

* rm cmt

* fix cellmeasurer

* thumbnail dur

* fix download

* Minor fixes (#2512)

- Spot delete confirmation
- Spot comments UI update
- Minor copy updates

* limit number of notif messages

* add spot title to doc title, add cache groups for webpack

* drop mic controls from recording popup view

* fix for webpack compress

* fix for auto mic pickup

* change status banners

* move svgs around, remove undefined check

* refactor svgs

* fix timetable scaling

* fix error popup

* self contain css

* pre-select spot on spot onboarding

---------

Co-authored-by: Sudheer Salavadi <connect.uxmaster@gmail.com>
Co-authored-by: Rajesh Rajendran <rjshrjndrn@users.noreply.github.com>
2024-08-29 13:35:58 +02:00

367 lines
9.8 KiB
TypeScript

import { render } from "solid-js/web";
import {
startLocationRecording,
stopLocationRecording,
startClickRecording,
stopClickRecording,
} from "./eventTrackers";
import ControlsBox from "@/entrypoints/content/ControlsBox";
import { convertBlobToBase64, getChromeFullVersion } from "./utils";
import "./style.css";
import "~/assets/main.css";
export default defineContentScript({
matches: ["*://*/*"],
cssInjectionMode: "ui",
async main(ctx) {
const ui = await createShadowRootUi(ctx, {
name: "spot-ui",
position: "inline",
anchor: "body",
append: "first",
onMount: (container) => {
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: "ort:getMicStatus",
});
let int = setInterval(() => {
if (micResponse !== null) {
clearInterval(int);
res(micResponse);
}
}, 200);
});
};
// no perm - muted - unmuted
let audioPerm = 0;
const getAudioPerm = () => 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: "ort:stop" });
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: "ort:pause" });
};
const resume = () => {
recState = "recording";
browser.runtime.sendMessage({ type: "ort:resume" });
};
const muteMic = () => {
browser.runtime.sendMessage({ type: "ort:mute-microphone" });
};
const unmuteMic = () => {
browser.runtime.sendMessage({ type: "ort:unmute-microphone" });
};
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: "ort:save-spot",
spot,
});
let index = 0;
for (let part of videoData.result) {
if (part) {
await browser.runtime.sendMessage({
type: "ort:save-spot-part",
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: "ort:login-token",
token: event.data.token,
});
}
if (event.data.type === "orspot:invalidate") {
void browser.runtime.sendMessage({
type: "ort:invalidate-token",
});
}
if (event.data.type === "ort:bump-logs") {
void chrome.runtime.sendMessage({
type: "ort:bump-logs",
logs: event.data.logs,
});
}
});
function startConsoleTracking() {
const scriptEl = document.createElement("script");
scriptEl.src = browser.runtime.getURL("/injected.js");
document.head.appendChild(scriptEl);
setTimeout(() => {
window.postMessage({ type: "injected:start" });
}, 100);
}
function stopConsoleTracking() {
window.postMessage({ type: "injected:stop" });
}
function onRestart() {
chrome.runtime.sendMessage({
type: "ort: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: "ort:countend" })
.then((r: boolean) => {
onEndObj = {};
return r;
});
}
void browser.runtime.sendMessage({ type: "ort:content-ready" });
browser.runtime.onMessage.addListener((message: any, resp) => {
if (message.type === "content: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 === "content:start") {
if (recState === "recording") return;
clockStart = message.time;
recState = "recording";
micResponse = null;
startClickRecording();
startLocationRecording();
startConsoleTracking();
browser.runtime.sendMessage({ type: "ort:started" });
if (message.shouldMount) {
ui.mount();
}
return "pong";
}
if (message.type === "notif:display") {
window.postMessage(
{
type: "ornotif:display",
message: message.message,
},
"*",
);
}
if (message.type === "content:unmount") {
stopClickRecording();
stopLocationRecording();
stopConsoleTracking();
recState = "stopped";
ui.remove();
return "unmounted";
}
if (message.type === "content:video-chunk") {
videoChunks[message.index] = message.data;
if (message.total === message.index + 1) {
chunksReady = true;
}
}
if (message.type === "content:spot-saved") {
window.postMessage({ type: "ornotif:copy", url: message.url });
}
if (message.type === "content:stop") {
window.postMessage({ type: "content:trigger-stop" }, "*");
}
if (message.type === "content:mic-status") {
micResponse = message.micStatus;
}
if (message.type === "content:error-events") {
errorsReady = true;
errorData.push(...message.errorData);
}
});
},
});