Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions public/permissions.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" style="width: 100%; height: 100%">

<head>
<meta charset="UTF-8">
<link href="./klack_tailwind_global.css" rel="stylesheet">
<title>klack: Permissions request</title>
</head>

<body>
<body style="width: 100%; height: 100%; margin: 0; padding: 0;">
<div id="root" style="width: 100%; height: 100%;"></div>
<script type="module" src="./permissions.bundle.mjs"></script>
</body>

Expand Down
Binary file added public/static/Lookup_512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 65 additions & 4 deletions src/background/services/permissions_giver/permissions_giver.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,74 @@
chrome.runtime.onInstalled.addListener((_details) => {
console.log("Handle 'chrome.runtime.onInstalled'");
(async () => {
await chrome.tabs.create({
import {
Message,
MessageResponse,
MessageResponseType,
MessageType,
} from "@/shared/messaging";
import { storage } from "@/shared/storage";

class PermissionsGiver {
static async openPermissionsTab() {
console.log("PermissionsGiver.openPermissionsTab()");
const tab = await chrome.tabs.create({
active: true,
url: chrome.runtime.getURL("permissions.html"),
});
await storage.permissions.tabId.set(tab.id || 0);
}

static async closePermissionsTab() {
console.log("PermissionsGiver.closePermissionsTab()");
await chrome.tabs.remove(await storage.permissions.tabId.get());
await storage.permissions.tabId.set(0);
}
}

chrome.runtime.onInstalled.addListener((_details) => {
console.log("Handle 'chrome.runtime.onInstalled'");
(async () => {
await PermissionsGiver.openPermissionsTab();
})().catch((err) => {
console.error(
`Error in 'chrome.runtime.onInstalled' handler: ${(err as Error).toString()}`,
);
});
});

chrome.runtime.onMessage.addListener(
(
message: Message,
_sender,
senderResponse: (response: MessageResponse) => void,
) => {
(async () => {
const { type, target, options: _options } = message;
if (target !== "background") {
return;
}
switch (type) {
case MessageType.PermissionsTabOpen:
await PermissionsGiver.openPermissionsTab();
break;
case MessageType.PermissionsTabClose:
await PermissionsGiver.closePermissionsTab();
break;
}
})()
.then(() => {
senderResponse({
type: MessageResponseType.ResultOk,
} satisfies MessageResponse);
})
.catch((err) => {
console.error(
`Error in 'chrome.runtime.onMessage' handler: ${(err as Error).toString()}`,
);
senderResponse({
type: MessageResponseType.ResultError,
reason: (err as Error).toString(),
} satisfies MessageResponse);
});
// NOTE: We need to return `true`, because we using `sendResponse` asynchronously
return true;
},
);
14 changes: 14 additions & 0 deletions src/shared/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export enum MessageType {
RecordingResume = "RecordingResume",
RecordingCancel = "RecordingCancel",
RecordingSave = "RecordingSave",
PermissionsTabOpen = "PermissionsPageOpen",
PermissionsTabClose = "PermissionsPageClose",
// offscreen
RecorderCreate = "RecorderCreate",
RecorderStart = "RecorderStart",
Expand Down Expand Up @@ -96,6 +98,18 @@ export const sender = {
options,
});
},
permissionsTabOpen: () => {
return chrome.runtime.sendMessage<Message, MessageResponse>({
type: MessageType.PermissionsTabOpen,
target: "background",
});
},
permissionsTabClose: () => {
return chrome.runtime.sendMessage<Message, MessageResponse>({
type: MessageType.PermissionsTabClose,
target: "background",
});
},
},
offscreen: {
recorderCreate: (options: RecorderCreateOptions) => {
Expand Down
5 changes: 5 additions & 0 deletions src/shared/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export enum StorageKey {
DevicesVideoEnabled = "devices.video.enabled",
DevicesVideoId = "devices.video.id",
DevicesVideoName = "devices.video.name",
PermissionsTabId = "permissions.tabId",
RecordingState = "recording.state",
RecordingDownloadId = "recording.download_id",
UiCameraBubbleEnabled = "ui.cameraBubble.enabled",
Expand All @@ -34,6 +35,7 @@ type StorageValueTypeMap = {
[StorageKey.DevicesVideoEnabled]: boolean;
[StorageKey.DevicesVideoId]: string;
[StorageKey.DevicesVideoName]: string;
[StorageKey.PermissionsTabId]: number;
[StorageKey.RecordingState]: RecordingState;
[StorageKey.RecordingDownloadId]: number;
[StorageKey.UiCameraBubbleEnabled]: boolean;
Expand Down Expand Up @@ -85,6 +87,9 @@ export const storage = {
name: createStorageSetterGetter(StorageKey.DevicesVideoName),
},
},
permissions: {
tabId: createStorageSetterGetter(StorageKey.PermissionsTabId),
},
recording: {
state: createStorageSetterGetter(StorageKey.RecordingState),
downloadId: createStorageSetterGetter(StorageKey.RecordingDownloadId),
Expand Down
164 changes: 164 additions & 0 deletions src/ui/pages/permissions/Permissions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { useEffect, useState } from "react";
import {
IconVideo,
IconMicrophone,
IconLoader2,
IconCircleCheck,
IconCircleX,
} from "@tabler/icons-react";
import lookup512 from "/static/Lookup_512.png";
import { sender } from "@/shared/messaging";

export const Permissions = () => {
const [videoPermissionState, setVideoPermissionState] =
useState<PermissionState>("prompt");
const [micPermissionsState, setMicPermissionsState] =
useState<PermissionState>("prompt");

useEffect(() => {
(async () => {
const permissionsStatus = await navigator.permissions.query({
name: "camera",
});
setVideoPermissionState(permissionsStatus.state);
permissionsStatus.onchange = () => {
setVideoPermissionState(permissionsStatus.state);
};
})().catch((err) => {
console.error(
`Can't get permissions status for video: ${(err as Error).toString()}`,
);
});
}, []);

useEffect(() => {
(async () => {
const permissionsStatus = await navigator.permissions.query({
name: "microphone",
});
setMicPermissionsState(permissionsStatus.state);
permissionsStatus.onchange = () => {
setMicPermissionsState(permissionsStatus.state);
};
})().catch((err) => {
console.error(
`Can't get permissions status for video: ${(err as Error).toString()}`,
);
});
}, []);

useEffect(() => {
if (
videoPermissionState !== "granted" ||
micPermissionsState !== "granted"
) {
return;
}
setTimeout(() => {
sender.background.permissionsTabClose().catch((err) => {
console.error(
`Can't send event to background: ${(err as Error).toString()}`,
);
});
}, 3 * 1000);
}, [micPermissionsState, videoPermissionState]);

useEffect(() => {
(async () => {
await navigator.mediaDevices.getUserMedia({
video: true,
});
})().catch((err) => {
console.error(
`Error on 'getUserMedia' for video device permissions: ${(err as Error).toString()}`,
);
});
}, []);

useEffect(() => {
(async () => {
await navigator.mediaDevices.getUserMedia({
audio: true,
});
})().catch((err) => {
console.error(
`Can't on 'getUserMedia' for audio device permissions: ${(err as Error).toString()}`,
);
});
}, []);

return (
<div className="bg-klack-charcoal-800 flex h-full w-full flex-col items-center justify-center gap-25">
<img src={lookup512} />
<div className="font-dosis w-[665px] text-center text-3xl text-white">
{videoPermissionState === "denied" ||
micPermissionsState === "denied" ? (
<p>
You denied access to required devices. Please enable them in your
browser settings and close this tab.
</p>
) : (
<p>
Now the browser will ask for your permission to use your camera and
microphone. Please do not close this tab, it will close
automatically.
</p>
)}
</div>
<div className="flex flex-row items-center gap-[150px]">
<div className="flex flex-row items-center gap-[10px]">
<IconVideo
className="stroke-white"
stroke={2}
size={64}
/>
{videoPermissionState === "prompt" ? (
<IconLoader2
className="animate-spin stroke-white"
stroke={2}
size={32}
/>
) : videoPermissionState === "granted" ? (
<IconCircleCheck
className="fill-klack-emerald-400 stroke-white"
stroke={2}
size={48}
/>
) : (
<IconCircleX
className="fill-klack-red-500 stroke-white"
stroke={2}
size={48}
/>
)}
</div>
<div className="flex flex-row items-center gap-[10px]">
<IconMicrophone
className="stroke-white"
stroke={2}
size={64}
/>
{micPermissionsState === "prompt" ? (
<IconLoader2
className="animate-spin stroke-white"
stroke={2}
size={32}
/>
) : micPermissionsState === "granted" ? (
<IconCircleCheck
className="fill-klack-emerald-400 stroke-white"
stroke={2}
size={48}
/>
) : (
<IconCircleX
className="fill-klack-red-500 stroke-white"
stroke={2}
size={48}
/>
)}
</div>
</div>
</div>
);
};
14 changes: 11 additions & 3 deletions src/ui/pages/permissions/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
(async () => {
await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
})().catch((err) => console.error((err as Error).toString()));
import React from "react";
import ReactDOM from "react-dom/client";
import { Permissions } from "./Permissions";

const root = document.getElementById("root");

if (!root) {
console.error("Can't find element with id 'root");
} else {
ReactDOM.createRoot(root).render(React.createElement(Permissions));
}
Loading