Skip to content

Commit db5deb7

Browse files
committed
Added popup menu warning about permissions and ability to grant access to mic and video from it
1 parent 9dc3162 commit db5deb7

5 files changed

Lines changed: 162 additions & 76 deletions

File tree

src/background/services/permissions_giver/permissions_giver.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
import { storage } from "@/shared/storage";
88

99
class PermissionsGiver {
10-
static async createPermissionsTab() {
10+
static async openPermissionsTab() {
11+
console.log("PermissionsGiver.openPermissionsTab()");
1112
const tab = await chrome.tabs.create({
1213
active: true,
1314
url: chrome.runtime.getURL("permissions.html"),
@@ -16,6 +17,7 @@ class PermissionsGiver {
1617
}
1718

1819
static async closePermissionsTab() {
20+
console.log("PermissionsGiver.closePermissionsTab()");
1921
await chrome.tabs.remove(await storage.permissions.tabId.get());
2022
await storage.permissions.tabId.set(0);
2123
}
@@ -24,7 +26,7 @@ class PermissionsGiver {
2426
chrome.runtime.onInstalled.addListener((_details) => {
2527
console.log("Handle 'chrome.runtime.onInstalled'");
2628
(async () => {
27-
await PermissionsGiver.createPermissionsTab();
29+
await PermissionsGiver.openPermissionsTab();
2830
})().catch((err) => {
2931
console.error(
3032
`Error in 'chrome.runtime.onInstalled' handler: ${(err as Error).toString()}`,
@@ -44,7 +46,10 @@ chrome.runtime.onMessage.addListener(
4446
return;
4547
}
4648
switch (type) {
47-
case MessageType.PermissionsPageClose:
49+
case MessageType.PermissionsTabOpen:
50+
await PermissionsGiver.openPermissionsTab();
51+
break;
52+
case MessageType.PermissionsTabClose:
4853
await PermissionsGiver.closePermissionsTab();
4954
break;
5055
}

src/shared/messaging.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ export enum MessageType {
88
RecordingResume = "RecordingResume",
99
RecordingCancel = "RecordingCancel",
1010
RecordingSave = "RecordingSave",
11-
PermissionsPageClose = "PermissionsPageClose",
11+
PermissionsTabOpen = "PermissionsPageOpen",
12+
PermissionsTabClose = "PermissionsPageClose",
1213
// offscreen
1314
RecorderCreate = "RecorderCreate",
1415
RecorderStart = "RecorderStart",
@@ -97,9 +98,15 @@ export const sender = {
9798
options,
9899
});
99100
},
100-
permissionsPageClose: () => {
101+
permissionsTabOpen: () => {
101102
return chrome.runtime.sendMessage<Message, MessageResponse>({
102-
type: MessageType.PermissionsPageClose,
103+
type: MessageType.PermissionsTabOpen,
104+
target: "background",
105+
});
106+
},
107+
permissionsTabClose: () => {
108+
return chrome.runtime.sendMessage<Message, MessageResponse>({
109+
type: MessageType.PermissionsTabClose,
103110
target: "background",
104111
});
105112
},

src/shared/storage.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ export enum StorageKey {
55
DevicesMicId = "devices.mic.id",
66
DevicesMicName = "devices.mic.name",
77
DevicesMicVolume = "devices.mic.volume",
8-
DevicesMicPermissionGranted = "devices.mic.granted",
98
DevicesVideoEnabled = "devices.video.enabled",
109
DevicesVideoId = "devices.video.id",
1110
DevicesVideoName = "devices.video.name",
12-
DevicesVideoPermissionGranted = "devices.video.granted",
1311
PermissionsTabId = "permissions.tabId",
1412
RecordingState = "recording.state",
1513
RecordingDownloadId = "recording.download_id",
@@ -34,11 +32,9 @@ type StorageValueTypeMap = {
3432
[StorageKey.DevicesMicId]: string;
3533
[StorageKey.DevicesMicName]: string;
3634
[StorageKey.DevicesMicVolume]: number;
37-
[StorageKey.DevicesMicPermissionGranted]: boolean;
3835
[StorageKey.DevicesVideoEnabled]: boolean;
3936
[StorageKey.DevicesVideoId]: string;
4037
[StorageKey.DevicesVideoName]: string;
41-
[StorageKey.DevicesVideoPermissionGranted]: boolean;
4238
[StorageKey.PermissionsTabId]: number;
4339
[StorageKey.RecordingState]: RecordingState;
4440
[StorageKey.RecordingDownloadId]: number;
@@ -84,17 +80,11 @@ export const storage = {
8480
id: createStorageSetterGetter(StorageKey.DevicesMicId),
8581
name: createStorageSetterGetter(StorageKey.DevicesMicName),
8682
volume: createStorageSetterGetter(StorageKey.DevicesMicVolume),
87-
permissionsGranted: createStorageSetterGetter(
88-
StorageKey.DevicesMicPermissionGranted,
89-
),
9083
},
9184
video: {
9285
enabled: createStorageSetterGetter(StorageKey.DevicesVideoEnabled),
9386
id: createStorageSetterGetter(StorageKey.DevicesVideoId),
9487
name: createStorageSetterGetter(StorageKey.DevicesVideoName),
95-
permissionsGranted: createStorageSetterGetter(
96-
StorageKey.DevicesVideoPermissionGranted,
97-
),
9888
},
9989
},
10090
permissions: {

src/ui/pages/permissions/Permissions.tsx

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,83 +7,103 @@ import {
77
IconCircleX,
88
} from "@tabler/icons-react";
99
import lookup512 from "/static/Lookup_512.png";
10-
import { storage } from "@/shared/storage";
1110
import { sender } from "@/shared/messaging";
1211

13-
enum PermissionState {
14-
NotGranted,
15-
Granted,
16-
NotAllowed,
17-
}
18-
1912
export const Permissions = () => {
20-
const [videoPermissionState, setVideoPermissionState] = useState(
21-
PermissionState.NotGranted,
22-
);
23-
const [micPermissionsState, setMicPermissionsState] = useState(
24-
PermissionState.NotGranted,
25-
);
13+
const [videoPermissionState, setVideoPermissionState] =
14+
useState<PermissionState>("prompt");
15+
const [micPermissionsState, setMicPermissionsState] =
16+
useState<PermissionState>("prompt");
2617

2718
useEffect(() => {
2819
(async () => {
29-
await navigator.mediaDevices.getUserMedia({
30-
video: true,
31-
});
32-
await storage.devices.video.permissionsGranted.set(true);
33-
setVideoPermissionState(PermissionState.Granted);
34-
})().catch(() => {
35-
storage.devices.video.permissionsGranted.set(true).catch((err) => {
36-
console.error(
37-
`Can't set storage value for video device permissions: ${(err as Error).toString()}`,
38-
);
20+
const permissionsStatus = await navigator.permissions.query({
21+
name: "camera",
3922
});
40-
setVideoPermissionState(PermissionState.NotAllowed);
23+
setVideoPermissionState(permissionsStatus.state);
24+
permissionsStatus.onchange = () => {
25+
setVideoPermissionState(permissionsStatus.state);
26+
};
27+
})().catch((err) => {
28+
console.error(
29+
`Can't get permissions status for video: ${(err as Error).toString()}`,
30+
);
4131
});
4232
}, []);
4333

4434
useEffect(() => {
4535
(async () => {
46-
await navigator.mediaDevices.getUserMedia({
47-
audio: true,
36+
const permissionsStatus = await navigator.permissions.query({
37+
name: "microphone",
4838
});
49-
setMicPermissionsState(PermissionState.Granted);
50-
await storage.devices.mic.permissionsGranted.set(true);
51-
})().catch(() => {
52-
storage.devices.mic.permissionsGranted.set(true).catch((err) => {
53-
console.error(
54-
`Can't set storage value for video device permissions: ${(err as Error).toString()}`,
55-
);
56-
});
57-
setMicPermissionsState(PermissionState.NotAllowed);
39+
setMicPermissionsState(permissionsStatus.state);
40+
permissionsStatus.onchange = () => {
41+
setMicPermissionsState(permissionsStatus.state);
42+
};
43+
})().catch((err) => {
44+
console.error(
45+
`Can't get permissions status for video: ${(err as Error).toString()}`,
46+
);
5847
});
5948
}, []);
6049

6150
useEffect(() => {
6251
if (
63-
videoPermissionState === PermissionState.NotGranted ||
64-
micPermissionsState === PermissionState.NotGranted
52+
videoPermissionState !== "granted" ||
53+
micPermissionsState !== "granted"
6554
) {
6655
return;
6756
}
68-
6957
setTimeout(() => {
70-
sender.background.permissionsPageClose().catch((err) => {
58+
sender.background.permissionsTabClose().catch((err) => {
7159
console.error(
7260
`Can't send event to background: ${(err as Error).toString()}`,
7361
);
7462
});
75-
}, 1 * 1000);
63+
}, 3 * 1000);
7664
}, [micPermissionsState, videoPermissionState]);
7765

66+
useEffect(() => {
67+
(async () => {
68+
await navigator.mediaDevices.getUserMedia({
69+
video: true,
70+
});
71+
})().catch((err) => {
72+
console.error(
73+
`Error on 'getUserMedia' for video device permissions: ${(err as Error).toString()}`,
74+
);
75+
});
76+
}, []);
77+
78+
useEffect(() => {
79+
(async () => {
80+
await navigator.mediaDevices.getUserMedia({
81+
audio: true,
82+
});
83+
})().catch((err) => {
84+
console.error(
85+
`Can't on 'getUserMedia' for audio device permissions: ${(err as Error).toString()}`,
86+
);
87+
});
88+
}, []);
89+
7890
return (
7991
<div className="bg-klack-charcoal-800 flex h-full w-full flex-col items-center justify-center gap-25">
8092
<img src={lookup512} />
81-
<div className="font-dosis w-[665px] text-center text-3xl whitespace-pre-line text-white">
82-
<p>
83-
Now the browser will ask for your permission to use your camera and
84-
microphone.
85-
</p>
86-
<p>Please do not close this tab, it will close automatically.</p>
93+
<div className="font-dosis w-[665px] text-center text-3xl text-white">
94+
{videoPermissionState === "denied" ||
95+
micPermissionsState === "denied" ? (
96+
<p>
97+
You denied access to required devices. Please enable them in your
98+
browser settings and close this tab.
99+
</p>
100+
) : (
101+
<p>
102+
Now the browser will ask for your permission to use your camera and
103+
microphone. Please do not close this tab, it will close
104+
automatically.
105+
</p>
106+
)}
87107
</div>
88108
<div className="flex flex-row items-center gap-[150px]">
89109
<div className="flex flex-row items-center gap-[10px]">
@@ -92,13 +112,13 @@ export const Permissions = () => {
92112
stroke={2}
93113
size={64}
94114
/>
95-
{videoPermissionState === PermissionState.NotGranted ? (
115+
{videoPermissionState === "prompt" ? (
96116
<IconLoader2
97117
className="animate-spin stroke-white"
98118
stroke={2}
99119
size={32}
100120
/>
101-
) : videoPermissionState === PermissionState.Granted ? (
121+
) : videoPermissionState === "granted" ? (
102122
<IconCircleCheck
103123
className="fill-klack-emerald-400 stroke-white"
104124
stroke={2}
@@ -118,13 +138,13 @@ export const Permissions = () => {
118138
stroke={2}
119139
size={64}
120140
/>
121-
{micPermissionsState === PermissionState.NotGranted ? (
141+
{micPermissionsState === "prompt" ? (
122142
<IconLoader2
123143
className="animate-spin stroke-white"
124144
stroke={2}
125145
size={32}
126146
/>
127-
) : micPermissionsState === PermissionState.Granted ? (
147+
) : micPermissionsState === "granted" ? (
128148
<IconCircleCheck
129149
className="fill-klack-emerald-400 stroke-white"
130150
stroke={2}

src/ui/pages/popup/Popup.tsx

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect, useState } from "react";
12
import { RecordingState, StorageKey } from "@/shared/storage";
23
import { sender } from "@/shared/messaging";
34
import useStorageValue from "@/ui/hooks/useStorageValue";
@@ -119,13 +120,13 @@ const RecordingControl = () => {
119120
className="h-[28px] w-[28px] stroke-black transition-colors hover:stroke-[#00d492]"
120121
stroke={2}
121122
onClick={() => {
122-
(async () => {
123-
await sender.background.recordingComplete();
124-
})().catch((err) =>
125-
console.error(
126-
`Can't complete and download recording: ${(err as Error).toString()}`,
127-
),
128-
);
123+
sender.background
124+
.recordingComplete()
125+
.catch((err) =>
126+
console.error(
127+
`Can't complete and download recording: ${(err as Error).toString()}`,
128+
),
129+
);
129130
}}
130131
/>
131132
<PlayButton
@@ -168,6 +169,43 @@ const RecordingDuration = () => {
168169
};
169170

170171
export const Popup = () => {
172+
const [micPermissionsState, setMicPermissionsState] =
173+
useState<PermissionState>("prompt");
174+
const [videoPermissionsState, setVideoPermissionsState] =
175+
useState<PermissionState>("prompt");
176+
177+
useEffect(() => {
178+
(async () => {
179+
const permissionsStatus = await navigator.permissions.query({
180+
name: "camera",
181+
});
182+
setVideoPermissionsState(permissionsStatus.state);
183+
permissionsStatus.onchange = () => {
184+
setVideoPermissionsState(permissionsStatus.state);
185+
};
186+
})().catch((err) => {
187+
console.error(
188+
`Can't get permissions status for video: ${(err as Error).toString()}`,
189+
);
190+
});
191+
}, []);
192+
193+
useEffect(() => {
194+
(async () => {
195+
const permissionsStatus = await navigator.permissions.query({
196+
name: "microphone",
197+
});
198+
setMicPermissionsState(permissionsStatus.state);
199+
permissionsStatus.onchange = () => {
200+
setMicPermissionsState(permissionsStatus.state);
201+
};
202+
})().catch((err) => {
203+
console.error(
204+
`Can't get permissions status for video: ${(err as Error).toString()}`,
205+
);
206+
});
207+
}, []);
208+
171209
return (
172210
<div
173211
className="flex h-full w-full flex-col items-center justify-center"
@@ -186,9 +224,35 @@ export const Popup = () => {
186224
</div>
187225
</header>
188226
<main className="flex h-full w-full flex-col items-center justify-start">
189-
<Settings />
190-
<RecordingControl />
191-
<RecordingDuration />
227+
{micPermissionsState === "granted" &&
228+
videoPermissionsState === "granted" ? (
229+
<>
230+
<Settings />
231+
<RecordingControl />
232+
<RecordingDuration />
233+
</>
234+
) : (
235+
<div className="flex h-full w-full flex-col items-center justify-center gap-[20px]">
236+
<div className="font-dosis h-[77px] w-[266px] text-center text-xl text-black select-none">
237+
<p>
238+
Extension requires permissions to access your camera and
239+
microphone
240+
</p>
241+
</div>
242+
<button
243+
className="bg-klack-red-500 hover:bg-klack-red-600 font-dosis flex h-[50px] w-[140px] cursor-pointer items-center justify-center text-lg font-bold text-white select-none"
244+
onClick={() => {
245+
sender.background.permissionsTabOpen().catch((err) => {
246+
console.error(
247+
`Can't open permissions tab: ${(err as Error).toString()}`,
248+
);
249+
});
250+
}}
251+
>
252+
Grant access
253+
</button>
254+
</div>
255+
)}
192256
</main>
193257
</div>
194258
);

0 commit comments

Comments
 (0)