From 9dc316224ba126a0f6908e13fc4e078e0737bb94 Mon Sep 17 00:00:00 2001 From: Yury Getman Date: Tue, 24 Jun 2025 00:37:26 +0400 Subject: [PATCH 1/2] Implemented permissions request page --- public/permissions.html | 7 +- public/static/Lookup_512.png | Bin 0 -> 18236 bytes .../permissions_giver/permissions_giver.ts | 64 +++++++- src/shared/messaging.ts | 7 + src/shared/storage.ts | 15 ++ src/ui/pages/permissions/Permissions.tsx | 144 ++++++++++++++++++ src/ui/pages/permissions/permissions.ts | 14 +- src/ui/styles/global.css | 2 +- 8 files changed, 243 insertions(+), 10 deletions(-) create mode 100644 public/static/Lookup_512.png create mode 100644 src/ui/pages/permissions/Permissions.tsx diff --git a/public/permissions.html b/public/permissions.html index 7a75142..d05bcf3 100644 --- a/public/permissions.html +++ b/public/permissions.html @@ -1,11 +1,14 @@ - + + + klack: Permissions request - + +
diff --git a/public/static/Lookup_512.png b/public/static/Lookup_512.png new file mode 100644 index 0000000000000000000000000000000000000000..c106184df7bd21ff10d9a3460893d4cc8b007a52 GIT binary patch literal 18236 zcmafbbyU=E@b2zX3y6SpNGd5M2+~NXfTWakNOw0ZN=Zw134%y>vx0PYm$amGb6>vq zckj9XUC%*|vLANdc_*HEW@f)A%D=(EBFBP2AUM)*C6yr%7!(45LgDD(f0iG*T!U{= zN98x-km3Q#EeM1LA}#q!#VvJr-qlUT_!RkXKO8C^9)zXc7SrY2OC#0Zs?l0(YN^AVYuBe`G1P{kpbPh(NxPo9W9H$S?)?ZQESYqF5k z$Hwg_G8ps5pPL(lA<1*kodQnlk0<{B|BFtWRGyLMm^oJ-I>6Z?5B&>u_N%v=aeCh1 z@-#ZmIlE^t@>@<$JPS)fZb*p5@7VYbWsP7yQ9b5J3@mJ}kdRJ!*8EI|IrW;)UjDcGyXs5rJE$r!n64YNJft1%L zIKU|F-STAx;@(Q*FFlv}fsS14!}i|BBDtAZT^pz8CUDt*ZET`^Yk9hhlDpGSFr}m@ z*3;~5)zxqO?V^g7FQJKQFbfOqReK4JSbEVHEL#8kXhP^iWtnk|VpMhvcGxY92OYWM zCzqnQJ=wP2UuyJbdOyGqNHnp!TI#f~yKZb!ZZ=5uFfKOs&OZLbgJ&4TkR!j(118V> z@rI>CAQa5G&Tlnv*$ zQ7ghMJ^E36qRywfq@?R=Tk5C24^5f$aaIIG1!Gf1Tyx3Ni*M9fy8%N@39vRxan|Z^RSgCyew&Vw7uT;fYhqg&?qE zmHQDbC^vrSaj3`;B8PvjS>w~wjm;h}7Qg4M6nSB<2`$mYsR7S9@bek|c_T*{^J@t+qK+iEhb9Oy#GJQJ=c6W>5__%fA}4k}Y_ zGNc6M423JMBPaLQ`9I^Jli<>?9!n5hHqGB7g)Fw0O-K!Wywb^q9dPEy+2wFf#_OB; zM#xwR4-+5B$;qU#?2V*`vdq5^n&c?z#UEW?y;3!BNBSc0pZh7o@vXCq;fgZzh6CiH zO(TdAc0*=(RK#T)-e&2vfw5Qxisiyn!#tbYjfT5$-jv zQgK`(myu>aPxm*zCr=CI&wCkeFQg|=O>AKK$(O)M8_yHxx z20w$vDo;Z9VZKGr8Q5@|&tcWJHGKTbEcb7oB^CO1;MBdX1?ongXsl-<4mW5DW&sZb z*0t|%#}%J(L~2!_zYdRX@P&%G7|^9g z9;3@9$c(M;5aKUB^uC40rC?KgqxxPO*gDV^KAUPvSvcCL$ouP!#yLn|i*erF+~b-e zyadV@IG`V@=z)nbpD$yOa-$dMK4kc<^mjb}Rfbv-)2YXY#+shT0XR#KmOS(yMj6@0 zi^G+?4W=={RU5O<7N{HdWl3;)@D%D;0`ao@=IUY^NS@K;<#(x1(JtXHVP8Eb5D)%~ z-5`%pws?8Kt60&KxMSsZBsWz*zOX9!+k8F~T2pEEt8#a?o3*%nTaTc&osv#N-xioH zeq-qtFk3^!@cMwSYUl1Wifs!H|4159R>jQ_ZSn2DHzU`BtYjF+C^Ea<@8GnbNQi+I z6!0G442_%44Ww~?gi{Kc={e9@?65H{B~$x;EV_d^GT5<3*(!j0#iyJkfy(@lJd&tt z8mwj0(x2iyXsVPAw%}LPcsrTlyRN<%ga<9;WW>3|Aryz_)z-r~)gjlYm4{?CKEjP> zROji9PbJ?{va^4`O6Bi6@ts*Uar~UsfBKQZAJ5O;8b32uU~vb+b4pg@@xtrCZwJC; zrK+LXvwO7uZ$9Gjt$0OW1QN>==m+jyto=AM_OBt;ghTdVEgE)DJT$(C@|i)&R3fR3 z@GcK8wa}(*gqY5f4Gr1b3JwWc|ARqiaMr&fORSmkgS8(j^#A-W$MN&G6ALr2a||U0 z2i9-Dy=fM=gf#i3nQ+ng;}tIz0AD)^ahK5w8?2R%S!&qdk$o4;LK^t0=DkAud(FR= zuCMy`xAw{t5O}R}+V6qtJy7hr8qYN0V~a#r;#re7^yde?%F+%#=Z* zBgm9n3~5F;^zPW5!)y!cWq~Yf0>?=5IT^1k&|B0qd&Wt&`SJdql=$C-LUL6+^#$-Y zxY-#o7Thr&Qde{E(|(E~CF$(5EP0w0Kj>bRxOlV(C7Fsz0&#V)3>mIhEE76f(wtmNa2-)S&Ue=Oay}in&mXf0LSPEtk%MBN25U!@-R``n zs(PDO{o#HO<&qE9h}7{c&*vjqe_ektGr5=PALA?|It$3&N7vS950Hsm2Uq*mt4}5K zZ+3qZnCq5MjaJ*9k<8$2!rsAF53p(dt*zKV3_0m*NI$ynkWa&MT%U?b#3SY}QKE+s z?=0+cRDP-RI$#xh_zO>bs&%3QC03amVdJx+{N7+w(K9+kg)vR#bUX+1qiI`#YFuD@ z7`5-%o#%eT;CIw-zklS3$1sEqbQ>I3nipzhdHW+Q2T$yDa8^s`99T0pe&U*;kh4Ihcf#qac-)(`XbLxp5K=AXVM!oPfpih_ibziJCq{MPYx zX!zTk(f!2GCiqu>fBN=#rCX|4-}X+4chi+AKFE>&6clEdKOT9ka+O~TRC@i%>?mS= zee#Fc=fkPkSVAY>+)#$8-WB<-WuF zE1sHrkRQ0XwBP*sWV=YdTbP{J|GV5>W9<`$TX%c&se>+^oYE+RSe+%-h7{W|_}@v6 z(6U8!WF3-F+}dV!)6*bnx~Z%abq#g z($JN#*spFy#AgM!KlHW^^*CXn7Lb06AW(R=U0*vWZ9+Rl7Uf+orSV>-J=yip6vEbD zLcET4t3orpMfojmJj2HpmT-lwd~tCw{086kS?O?#XsT!NsJW!@N;qE+7BnahwRz2% zap~z2k?f;Kts;ZTlPJs2<4;oJlqXT{MzE*^iM=v9*(EDI@N0*+p7`T^?FLwe*_nk? ziS;}$gNA=PgvUP^l?%CHvaJHA6=zwMLeyy+vEc3VxlFQ;t#)V0zY2cqNOurEv8qYy z4@VHCAtDhjx1UrD45o!Zy2e(C=3=VevG;IzdK@|K0hancyxr~%@?n=*D zR~~L(8R^9E2Ov|`f>}(rN9Unxt7nT6^71eA*OU`2ZX4PAxvys#O1#;ulQ(T`J+DcYoxE(ytd4 z>Y?JnVrZk7ZdCb21ZI61xEB|%8vXIIA0Bk+-4#&i&T1FY`{T{P?h(qr869wuV}&1H zF*5igUXf@cF$1$G7I4kXtX~6+^vH~H$$Xa`Ig)F)Bxd&klNMrTMtW>9tt?1+-cUt` zNlW*|1z+Cm4-bTe=Sr+})GznvP|)}fWoef1;V-{-QNB;%_G?c$WF+54YPJgIvaDPs_yiv@}pI09{l3yrl zXf%cSj+?RF-Y+k(2gy7YVF z*>ZAo%REjjz!PUrU>Aa@n+!)3JsfNQ5Vw3WaK4jmiR}rWHk%~w&Uhz?&&`SYb8haN z4&$a9-Y+M|@=Y!c51s!ia`47Wc)W3(M5%|qXT6fh9Wxj6%&c?IvejJPezMz*M7ERY z72ddZfp?s*ch1SlDFx9?tysJJ@?<+wwj?Cz&3HhUSEK;SM#}y;T7Bg1P}b+u?T;Tn zmRis9-oNf}tv89!ma4&JSAac@g^EgbAJcfhS56@9IK{TvJ>>BPIK|iK=xhp~v8%Nb z_-4^fA*)*EMeZN-7zx>%v}3y0R??WRmF8`mFD#v?M@SM2 zVg))IqbgAbp0_+I>aTX_Pk!<3LxqX9WB-dcJ{){}2_TUuD=TYMnBY{H^cw=b#R)f- zV=MjE_6H~)mH^~-Uw_=P*0UHB(mq#hJ*!b=K_r{NZah)=p_%=8XEP)Ik&nu2W@m~6 z(X=V%_f|ikwTC^dpM9+2?rR48THUiRK-^CttLZmPeAxSpD?-D^r_RdD#XfN8wR0>P z^iU)eak@L}xEw&@u;9L7Kfu2+=LG(-p2TJ}84?+}5iQHU48HgeJ+VpHKOU}jzf0u& zwdkqykB-oVgr=ft5xaI+j`3Bx%Zzy{_?mO*WqUyO+&@jwwftlEqxHdz!M-H!X|1^fMg5_#nOdfB?qk4B-F&1sBu{`NN~iWT2S%_+ae#Iy}% zNurD8W4_EfNxG0qOx|_%dr8zOuM3J zy-vo}8xLBs!9|~YYFwaojgwH_2gDU*+PYoX_RZqm4Rw>pNw#{4uGM058nK_8ayVS( z2bGvs-ckPM;@z!l71jPv_>dk^sl(HP%1X^I@Pq<&T=u51t!EL3-HaJ#uF%G_c~f^0 zm(YJ67hVVY#k-3c_0Cn_cl@hrYhPZT?)I|P%(ZrXrFRFKbfWq`rvV2}!_DXL7ri#G zoVfmW%LzurrdgS6BSX@?dA5k zPeg$UpM@(_MK|4wfbxgMWFXSzN5xzsm(f&AX1b$K~?sAmTgq0hnugB+R%3u@n09$ zp=VF2kCV~`GP}Mmi4J{iMnD`9 z^zt+E>eO#NEeTFvWno}u_26Tkg6NHkrK?yDHeQ^7>t0`ubPQ$xD`sbo1A4pUZ(o8H zJ-fG1S!CqojDd^PFZ*B*|0RNspas866HKrs*m*=1$@p(gX9sRQ4#6$|0{oB4Fexjaoz40sYQo}a}Svi8y!Jpq>>X$X$sb?|aR>Fx+FuFli z^T@+o|#^OnN>SxlT|?v3lq2j@_EYVhSoJK2Uja9(*i~Bq=`o zG$8HgA}uRx)as8}zdW-z`HKXa!e<$TWqJd=Q`0_D=rDM@9+E|M_h|T)qvdjD(wpvx z%3~?XE;g;|cdW&7DZCKj!U0A++ols@-4b;^?FKjgN2CW{5Hc({cKx`KmDMwPU7zXr zR42J)o-dV^JVhE?u;7pZkt?g2>T!{crsAl%6N z%3|NASdN*HrhX<`hd4Mo^d z>FEQT!{69EZ$qRIr|2ZFU3i()DF>c<4HZ#=Thfh=o;#{j)6~@5+mn|7 zAoU(${vP}BURq9WJS2`Caj^CYn;qVL#FT3RTbXkTVmgkEj|Xuh-S^fVL4L0`diu|i zB9~nwlT7=lJh&gjWC7{yvEO(Up?2anspDE#fW36$VJwA!CqV?Dw?dfLY$^&2J4e7k zEap{sY!^~7sN9f7vdYPYdoaZ;INYkla+(@B{`~p#^VJxIO2hdHG!T{0ySuwJ%SO=f zZd1Kar^kIBR$w1QMm>3AwRYlBg*!{esSgoL9p~Tb>FQc)!xgQ(cJ2v9w4fg|ZK2lA z94xo}bGs&~1d*LZ4|f|&hFRBrjVNyv5B~|F6ygJf2dA9?(^D=(L}&YZw)yDy(BdQfQ@vz8FT-cVyEZFU0OeeC$!ke%N!*gVIQ?}v zx$4F;u0K10C8da0;(c~@Tz_|azJd#I!5cL-V(+Wd@R_z6SltghEfN|44*|-XJ@qIv zY1qE2q^6ZZSbCo?2WgHQC9vyk0=k%uf6D_DeyQ@bh9IwmsQy$dbJ|i?E7bUvd=HB> zw;l%?korP=A&!2uGCwAeI>>n_+ly-H(tFZ}h0pduY+PJB(hr4<+vHuYZ?nUi%&enB zD8J*n*}Te=Cr=LgxCd9EI9Af{KBwtG`sBs7#U`2yysrwbl3QVZDv>5@+vG><{YD_4 z-5b+E|H{8oyJc~ot6e@~4&6S0Mx3e1kAuD#kftyRy511wj%|o#=x6j|F{1l2P26Pl zKjD#)l_eAP5xQ3@Fv%a!_kiQJZ=XCOSV{hTVD+t`?>xyg{g)gXVjc)UM^n1y*WjuZ z9|m7|L+veTV^h3XE1xD{f3cP@8y&+w{kSwaBt-wl;+{sfIu zeZL7gDK9S}6!ZsOP;KsEsWZnpm0fm-3BH|!SY(&$I13hZb#3k6>8!0~`$KY`u0t=_ zzg~`(wxP$EyMMbflA9?7?5WIuIW`;ISDrFfRRR53Oy3XT22*&Je z^)g*HD=>9|r2?Na>{07nl?~k5@jhC}E8mTp?_U+{o+u-ND7|Zh+HYu?v8z%ujqm$iPz9AT7$#scg zyWAn+scbyB;ISgDgakdGX;mFWr0DI`DLZ6c1gebcUv_=%wY%~Oq#xt^=rxinoAkmm z{yrIAhqAF?9n-22LkZZtY-E#6e`v1*1at#R-|KzfyHT7HH$M$)xz~F+o{YO4>_{!! zrnN3hS$w?p*O65HKH=7>v(?tJ3ERaLPZ@ROE1T&)SS>MnYF0}X7?7HajaN*}6;-4J z!i1=B6+|vRmg4G>&sfc|0?+}1$VGT)Xy{7Sq;7BdQ)ao9j_}b-O1^+XY{Umt)YDgZ zZoNtxPeeua8a(bX=`Z-!nS9j~Mn-Puf`wF`E!gH4W;Prk`F>2g>Axebc2y&j7zQ~* zV7&FqZ*HO}0>5JJlt1B07=EnDgI&;}w(*TsRCIBef{q#1It83r=6gpoj`eKyS_g$q zDGNxiH8BrmF2 z=qdewB)^ttFI`6bF&q+|P}JyJ`3I1$uG=!2x-(Sy{re;#_wsv%!xWWUrTwhKjGXcD z4$l2Y`*Qs@1k>Ks0my%NG$;z9(}_eguM=5a%w99(=IRW?%*0uwUWh7kCFUCnE9|2P z!qV$sI|;zv^S0V4yw;oUNBz~SIkIOMYN*B1O&)&!TEGs!h59ASGb-8q`5o0)GA0odgOT8G=qNzO)La)prWl^*u-%f%FzsGpW%9UhC#dqL(P4) zi&(Fa#S#_P$u5{^|^CkBH3+CI4qZ@9a?G9PYzAqN4E>u~dH2BNg~fiNVeshPZ33&6)- z-8Q?H!^jJ_1N|au4TLhlM?KOf+Zd17+n}A6yWrP~Yokiets;+)or;#D6B9{qPfC2J zqhpG{5HMPJN2EM!Vssd^&OZ2AS*g;1&8d%synJzYMor zXyiXTvmZAven7$dt{%cW<#?b^vwSIfrL@H1dl5RyOt}WAE01|y{2oR}&DXEdzr7A0 z32~m)Ms&w8HhjN#Ah*hImSd$nWJ)heCdI>~1^>@1z)#^ACnN$|>`~%0>ymx>`~;VV zHZ$XfX{gftect<*J%7h%W^b?NBY$y*Ez)n@Q1M!kJ|*{@n6vME41j+JK#l<2)Z2V< zDkOw18(bc5Jz>1k+nWVXUoQH#faEoD<0lo5rZy`QdLNMAXMT0xlmXeIw*ZUnPD(}w z%QVedjppKIfpiRme4d@8u5P-^#xsP${2kQygrw5z;t>0$eP^Lb>*%Qj2LMjEqSuP7 zr*#|Y6+#(eUPt|Wn)BK*Z1msCo>MOQ$owd8x_j|}loMi6Z&E^V4~RCuMW)KrTdWch zqCWCjHPvGtC$A;19nxl{#vQ{Thd8r}m@PEAXT6(#Auq7?1f2K}(r+koj5N^Rwku-{ zO8+_xx$;l11yoEifH0Vej(J!z`SXU~h7m*!B*=bM^$<)_Ij?7DNg zDJv_}c9}MI+FuY}24VBc$uULS^`!OjP;HszKrAgIqc*O7B|sa*^lu`@{<|p<|LTgd z#gI~n2N+ZG%(+d1nG_9>dTT*^SXtRl_dxMs}*uCr* z>nY|#nSS5g147mHyyJcr*Z1t3Yw<059TEO?#MBp@&gXJieG6hno_t#EYP76Xw{(zl zk25Qb1S6+9d4`mFO>#hkG`7k zOqJ~{wk+Ce2R(dTyPj+rn-XX_=k!`k6~+O$#Od;nl1ZzpAHU0`tn(Gy%-1)rmsgci zdmn#Qn|-(iQ_m@}PMQ@_)3g*{8D6}I1L5d@QY91x-0Wm0weK?>h+wKGdk2BS zTPO`(a_7ph2+wYAf^MrE8uCwjXj?Y6SiomSUNceg5QywnG2`xHOUus=zoWxLH^6rQ z-#$$9_4$Ay#;_RzKLaVu+!;DkX)X~-C0wbXRW9+e4~>b$^41l_E^UJT?-(=1=Lb8P zkFsciFd%YYhKGiS8`t)_{3hqKNu5}I3;CVbsKUG-t8 zK=r3nn~oy}C5xXEw9gyrq@Y|mch7nP>||5BvafTIC%wdq>atV%B+Kl(AoROli%(RP zzPm_078X|J1}xRSgG|$&Yo$}R%oq32CVg(O3F9Ap?7ckRCp3O3)z zHJ2c2{H(J41E8^ViEh)qvX*{*uKF|Za^=B&HIb$$yCHxoLWy{QsX+@e;h^ZW9N`?GEou9B&jI*v`lBqyoz8m8-cq0RVLj4RgmT9(d=fM zA@`M{lp#3mbglggac*o&uJGT)yFhI_DKSFd&zi=726St>4XUDYTY_pvi-`LL$CvyBjImR<7OMFF5&4MP$%K#Exh+Y4=hJ^(Sc-un?5)zV5peOFWyQbMU z`D{P`89^Sj9l*4zsGqh$XBrGfn7Hil0Q$}lS^#Fu(62-m(0AZ-_da*ZFxqihf zzHyMqRHf(nD@Mwf$b6-5mExcdKL=Q`igAB#qGBVX1@ufeQXRFmjdc)a`wvKU8U)hB z$LvP!AWH=XqA709xU*cY7*2BkAsaxlwAyL|e&(L7X#aA~g_MZ%pHZ7qeFm$Xspy!9KYt8&Ka>J<5c*Bo^r_Qs*nP2BwSo6~jYHnl z-d{Nc)PV;%y%9wZiRPacyYS~li}Sb2jqbvPj)x@t9($Q`&ATg|E&7Tg%{#A8)6`aT ze*R&sI6Zxh_$iXwI^l=H=~}F|Nt3XP{U5gL7VJ;q+t?G_dwGjHAbghpInrzX-y3Vb zOdY|OuPpg&dvo*hMnQ7ZM?tYU_u>J_M)HO2m zd(;Pr=+O|sSD3P1?Y@syo`0dozWVqRy%fTLbQa+>k3sHxW0~*xMZn_Jis@9eaJ<4) zVG>-1>u(PLf>~Rl#tShMbjdGQ>4NR|>g4kvVj#!rG`JWqw*~+%)(a(8e`mI4aEma) z;TIao%d}5vKAaEer}Y#^zstGrlyx5NN=rxX^%RSEUk=s(HEuprJFhBo6pENPmTsW{ z0iY}93kwErYbw8H*4^b~%QFQ=yckA>iN7!&>zP=L{id28Q9ky1c=m<-Vg~o1lK{%$ z;bHAw{kBGnPt%@6YdHimRj=x6Q~CJbkCS=Kvni+sVo-=OtMuwGK4^3vhVcM}ahvp! z7HNoPtW2`zlAAYMy{=_h z%ky%LuxKNsUVM4ZQO92L0FdU=@~57k)!41DA${SxeFUmHD}ao15kM2X{z%&K-1ikg zFV{EwzLUB=HW>5lNb8#Uj-=+$OHJfwc|u3PH$t92!AormTF%P=2F*(%=8CBxE)I)K zU<>$Xu2Ny*W0d`MxIA~1*$&8%FF;t3pAXj$9d%b3Yiih2wAUCFaGY$6vC(&aUJu;- z0>pqS!wk?9~$@#yfDmI&RIqGHI0AY4fZtp7!h(Wo+ zb-$NN<2ZJ|Fu#J<4jqSrFD{p5MOpZS$)3v=m^{eA#q1m3g8lC7JOaEOV9*Zs8ul%U zcK*&({j}jbn*-#_cC%O4!gi0m?#Mp+TSgi?G&H7ug71B!W!gp{VE`ur+ekNve;Pwjj^@>p8uhO0Wf#@qvjgYmQo0J#@%FgvDdEyPjA@) zeK6#+5Jc@$Oa1{Z&qGSgFuM;MKGx{2*~#hpckBzGFs7rU0eI}SEHS@njA#3dsV~%^ zkRdYO^-R1j6AssB7klzgZ*8W~cClGLPd`p7(BQmj;r>9a*c`PMjCp`3TKRXrSg_Q? zTC!T+%RcI}kS^@0taVqx{bU{i`0=-VG&E2r=$wnd1VG7|&Pw@OHSC|$eFQQ$_U7ui zful}_geTMbp${w`7i!fEY!Rj|)i>dQfc|PQM9}7vSY86A$sLyA@s}A97;pNG8MG?D z(!a52M;}2$?u4E0%?oz*eR9;sr#XT>JMQJM+l*Q)efU_wES zSPne{Ltqe9fz?JI)A}cVXn(fj$T~|+5gt>Bjn!G_wGEczIO%nB@FlC-sNw zzWuq!fByVASBUZ?pL0%FVYI04_>q_HF~{30|8lp|$dxbLE6{)t%_%WiE;>58(lk5h zOO5lVKDGi1t@QBk_xvQljzpF&jwrV@Eq_8-D+Rx0F`XA&=3JCxZEIJmeU;j5|2OZPe4`C`8uKr6@chmho(N#78bH{il_#?cZ5K`17n;pL6b8lM}b`NSxO zCTV(vFR1Nvu_m9lMA#IchVnXs@`IYyw!I~7<~W+$^}kO0_y3WO25o`n?e}@_yteza zcYqeECvD-15*zwZz&`gs=xRMvnRO+{2W*yoo*M>Q!0Nm9JA3mDmXV&X9r$R}S3KJv z>eM?e=fud5;J9w1F_Lm4=<;In--VOEAmw*>0$|hdyq-kePLuSD2P>?;uRuivI+;(} z(qoS|U7!O(NAGMJcB?J=S^3vu-`^kKsqN>g%rF_NV? zbzOU)uwS3h`ZD%?%ig|w_kh~$A=$ZdgNrrr^JWWCg<40zK~YdxT5653J!tDfkAImK z#DObx0QvFb$DH@sd_6Aa2inZo5OtXoFpv2CUveigt)`Y@vL}75iHQZC4#HxQXmNXG zE>5ukjI%zV#~u>gPzyvYINdJEw3jpo#7eE4FIg0uuDg^a-(WefkD=jP-S-Uecx%Z& z#7fsbo4eb)nG;1?+yM$p&M=I)II!2y(T!H!Ihq48Z9V8Jpr`vv{ej;EV zXyCQCe`084td1f(as<+l(*kr*PCLY>8)N+R2*yxQymdVIgH9$BT4?eM)_$O#Jx|OMsK=gN7?jDyO_dp8G`Q}YfJ&K=goJ>aS^-qF`FbrPCx6H3 zLCwn`mz*Dn!?qa8h;z_DM+3r?X}WvIUv1cPk7Kh_OHifrTK9mq{wDN~_{j_auX!%e zN6#|u(MM36@?s)Ure4R>gv=cH`RIO&Wq|eN@q2Y=rTd8s6Z1W*m;d_tByKNj=3OWh zgim58mMH<&UkU&1j@sbb@Ewk%BsQC`e*u0(2$J^+iLQ_OX8rsbj0e-Ld+W)kxWvS_ z*|N<+58BQ(8H+0`kD>a7CuCdQpcyRJKlsv74-rKKNtErF1wGdgBF&#_s+>}_@{tO@ zaY+6RG3~c^ICOgAFsIEWC*n8mSJp*DLdToKD4PDwmXkTs1Rf`Ui4He#SlN?F6!$&JiadQPWvkuU^cr@X_c>G%%PK=BG@Ok4^-t`ms3;_`$#h$(hwBHy#@EEq zbttZ;sTD<8nceLLt@JZZQogyR0(LP6hv$nv+clOLNW%WmSDbrw8&tjNf|U6>4XN^3 zG8=EY2W$EHFJ<$Nf{A5*H26KqJ7P)zP2{CtnxmvW>ZoI7bdkDE9_G|FRDGzg=9A36 zIcE&n4R^a1CqzL=^mgvgv&{z1qwT&H7xi&|&~l8LCCC0_MQOVoU;0zLEYLDPKOR-+ z?&~|IAfcunydP#TRiy%5ph~B>2pfD(tRcakGF8^;*BUXyRjgz1Zpo3Tx1hBsikFEK|1!jU!b10U`_g|)o5yJOY zxkACXhu`jO%~kl%``nCw0kPQ!HH)bObY?dEI9!Cl z)KT2}w}j}zl|j!l`Nhb6j~UEbXqK&;M}W7PDmRv`ciAnVC>DN#r--jl`ue*I-;zH8 zr-19j)va$5yEAs}M4+sRty%QZ+D5qov{TNTv%Tt7m139O86{yp{V~sdB~9}5n9)he zDLc?8`wT78(ekhMlV?;M2KLwaHS2Mlf!`1q zafY!w3Pd^p_V-%rh%(ma=5qNrbC4P(3!xntoI|PP9pd~u;XJQX@(s+9u$DLpgnk2A z5ro3c-63`#5*;Tc6)KkF<$7mICYBMA)-h`nGpckVc@Xz7HcvtWL_gn;{s1<`(isXm z;^#}BajU1J$G9veiyt4-2Ywc=-gBO}fvD^47L5L*bl$B}tSJVT`4x1J?Q*ZHb8MG#5*H?HNN}!Llt|a;AiTF=X31~Fa@D=_EJke7-56VId*!EeVh9E3V`)8Rv|vzdrW=HH>k#=Q?Lv7P{#RoYAFVO|Sa2V(&j+J>87G!PI?4BXt^&}f5lY`)^G7R`g4;s*mrl_!F;#|)@~O2whyFar09=0z!cujOI>XWbruMPMW@zXzJZ^5Q z;OG57P+#BJ0wt4CAL%66h`2eK94$;*@Sl5|OrtRjP-w>|G;BbGDLSUgBUu6sRv~F0 z&BP*=|9UN(DC{PaNiv0-O{=jTM$73FisLQzlbbSLTOvJH-0mg z?`J>neD6gVEcYtF8HlGeYE@2ZL)OIqmxwUe6z_xG(V{%Z-fN zl9UT@hT67-dYE%72jNSDYgyZDaxB2iy`A4aB*Gd`E-{5PX>~O0g``V=;^HdahJ{h` zN4F(k4TJf2Tn7iFlxx~OSZC>1CH^Y#1hS*o>~$f5ToVf!?LIdm?V!@H8g-un#$oFh z>IX)5{)lq~93;QD7QMUX?u|Du(PztOJNWEOhQy#+R$Kfli!QWocy5 z{e%$cCsX#dX;+C#GP%6RF;?XjziMM5XTT&!e?2YvXcAtFt!Q)Mpt!fSe!4kK?}1D7 zDGvEH?K1$nyn?W{Dn2lp@+^;_G{%4wEzqD5J=LI@Z`g-&RThbP-}D33-!CU9=K~|M zr7j!4IP>Z9r;}T*DXcemxF0}J4V+AUvUye+lb`s+Z7X*u=L?7w_*NtkM3bjMXM|!N zVI_e7Z?@fGxyZK8+pr{(RNq=JH~s3=4GKo@b2v*Mxee$=8IV2=GzjB)@D1%ifo7F0 zwXd&FpPKw@Cl-1SXP9)L!7-0Q))mG<+H6R52q1buLB?*|e8B*S)Hcos_p@<;57^w# z3REs=2PMcx0kzn?H?#nBBeeDPx*ipL2Rx_xPYV6+VmX-~#K?WbVwFA13!*?A$MM7p zJhda(oP(QXNNBQ)ie|Kjk{kh0%x&C5SQInyu6UY= zof0f^BvIh%d~|WTg-to|eT|Eg^6(H4blK4dnAi%mTEUPPU<#k?yZTq`$i0`DM}0RM zk|soE@_LncMO@`?(F;q`((;~x+cF#2_n<${amop>T|Y;^iav2+Dl>igXLDFP=U^D1 z;d@@*5x7K7Ff=-!mk=rz%$%E?$ci(7dW%)byI3(JrX6rMfx*PY1o~%+U^v3SfR-u} zBeGLMSC`83bcg>iE-9(ISf^pC%94bcd1mzO>P@&PAFh5aSK+M8ete5g;~q}5 zh2_r$Q$q6;@-%D-zMmoT3I}E2_~C^MwaXvUE}*Zio*nP+Cky`#C=Kb{UYp=|bi#%d zxkJFx)-$l7aa7ws+s7rhSQbuUB)L(}fiFd+)0lWH5#%I}3la7++^%>eF3H+kW5@G= z{&B53>-mW6aw~R`yfpmH{VJ-$3FC)(f*naQTK^&AUXcM%w3$X&Ury%XK4H*IkHK@1 z5%o&Em5tvFY#}N5f&fbhp=m+@pAZNhb-|YQt7nMYbr<)SVmO)dONs(p=tU0$huoLSTQ9!eL(cM zT5Et88#2u#0XJZs4Tbbz`Ke@vw!Ci-OC)vMq%|Aw`EPg0*z?R`@)KHI#=Lu_1hNY0 zA$a&mJUE1s8OFqls+PEtA%dI0X))o?nBg?;CnSCz9iPw{USYbzeBxP9jj_4D{Sm0B zG}=fCVjO^kEvY#U9u}!@z+O&Fj{xk2w&SxZyf@5D%*!3q71Lfr8%!kYXP5Ix-2XP_;v5^{ znT2@gSZ-tYO@?Vo=(L1bZJ0Xqo@OaRucF+k^$I2C-b`q{9HsEZKF}U;aUyNCuTIN@ z5O!&%2^^+>Pj>~fP!PitreHtjwr($8li4}rg4P|Zbh#htx1wG>BFKIu!%U@H?$70W zz06TbxQP)~!N~o;x9LDXwNM9Pix3c5iDwTeGkUeJp++lS@OwLVur=V_%wJb`-)4TO z%Xi@^`bz*2N5o{xcIyv0C<0)g?8|qhI2GP*P{Z$OinQI2pdd~!1FR}Y!#TaV; zt)y)_THniTCvZTp# zx-a+7@b?MCDMCa`lzLJTG?lx3VTI#6(ahVeVWUq=R3S?OHMH=1y{TwM;1 zr;OZg{p7x#P!8B0s>@Vd3fSe5nfmW47$LT>!OP2r{BVli(??ys6@0Jo<6Sm3Mm=x6 zi1(&fSPKcyaSxI!?*j0aSqeiD4l+M15tvA6)sEHmcp}(sjVqCo)vvwz<8;vs;pT+o zJ`)nT5!m*H~T)zk$Oe9DUwZ$zO@ z{r|-2CF8n>#FgW|P7f`8Wvo3TyVk+{41kdrg(XU$U6`uk!NCM&Qeym$ z#YntN1yx@?uvxs)*_Qm`l#$8mY&@Rr+UUZ;MP28dc?sKbyj$0^0<5N9NqXTc$}>CD z9$?zp#Bu#pZ`;WDd{<;3MLJ`D>!Y5dc1$pt)5I1sA1d=3ZSd=`BL;0}^Q_^?`um z1r$X}D&Kr`sgwHGc9rlOe2=HP9Q%b@oq!5TyN+gQxb#G9_zgncR|4>qXbVE_@8va; z#&JN*Dwm6l%vb_7WsT|FuKMG?E|q3w@JU^rYCtT6-Y)^d@1qRIR|RY8OnI@F*auv8 zQbf>FH!h2jCkMiw+O>T?kwLyi8o$ovQoM>bv>?YAyi-ZQUTURONT8qqO6!-_*I~h7 zVnSRTDT&@PY2h}^rGJ7g=+s1j<>_dxlPuASzLVRE-F|sE?VQwqi~80GU#P1}{j{Pz z*E0=7GXHx9-1@$Owji?j=+;gOzCX-GPONu^ORSFcKUA)`!Orm6SGvqcGCxd^-;nfi z@-)xb0*reV7kzOXTm_@QF;y%hp$obV@fQm2tjODHsLw+{mFTAOm<2d|h_ zG2DjcLe48?U^M(>FO1NwX4l_F4F9bue1=}4(+hT{f6nZfQXH^zl6TdJf~{aXQDzuV z;v3>clL3mCEhCv#mc1e1<0-5LIgbEz0zk4gY(xO#eT2gv2 zB`Wg3%e4B+wANwxVxIrC{d!DUa-27|^Iu!*yf&Ks^>z2N6Al7X z3-El5GbhhHSq^Nyl)VTzYOlvMjrsGL^-CL$@5}-2$}a+LUQ{kV`S7S{R}**NoY(~- zIrsTiu1s}3C|^H?SJ{QZ-;=ZZ+q+ahH@3~jMjq$Rt=q9?JxfI3!$tcerhNg{8MaF} z3V>lPS1>bDpo9fY}jxSF<@+}Ox$^RUo)PI$-rqbt=Zufo+nd3K z;`?=q*Z({E%(b4g;KX-h;2|U%nnW1&7Ej4y_#d=JP!3;((PI&;yFCSZPO5n)Vp(JVbRv5Day|B7@! zn>DA8_4?l2sy(sxw_gY=>sAfTm7pV9RxVm3WeD7{CL;Fe?D_Chj~fp;^(<0K>gwWY zm~Xz>7BxFk{vT{U8fj_S+PZXUYo)8}ytC~5auMYZ4(xLU zHWkg(-^!Z3e^MU0 { - 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 createPermissionsTab() { + 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() { + 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.createPermissionsTab(); })().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.PermissionsPageClose: + 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; + }, +); diff --git a/src/shared/messaging.ts b/src/shared/messaging.ts index 79716c6..a9cce80 100644 --- a/src/shared/messaging.ts +++ b/src/shared/messaging.ts @@ -8,6 +8,7 @@ export enum MessageType { RecordingResume = "RecordingResume", RecordingCancel = "RecordingCancel", RecordingSave = "RecordingSave", + PermissionsPageClose = "PermissionsPageClose", // offscreen RecorderCreate = "RecorderCreate", RecorderStart = "RecorderStart", @@ -96,6 +97,12 @@ export const sender = { options, }); }, + permissionsPageClose: () => { + return chrome.runtime.sendMessage({ + type: MessageType.PermissionsPageClose, + target: "background", + }); + }, }, offscreen: { recorderCreate: (options: RecorderCreateOptions) => { diff --git a/src/shared/storage.ts b/src/shared/storage.ts index b0ab1f7..a2d4a56 100644 --- a/src/shared/storage.ts +++ b/src/shared/storage.ts @@ -5,9 +5,12 @@ export enum StorageKey { DevicesMicId = "devices.mic.id", DevicesMicName = "devices.mic.name", DevicesMicVolume = "devices.mic.volume", + DevicesMicPermissionGranted = "devices.mic.granted", DevicesVideoEnabled = "devices.video.enabled", DevicesVideoId = "devices.video.id", DevicesVideoName = "devices.video.name", + DevicesVideoPermissionGranted = "devices.video.granted", + PermissionsTabId = "permissions.tabId", RecordingState = "recording.state", RecordingDownloadId = "recording.download_id", UiCameraBubbleEnabled = "ui.cameraBubble.enabled", @@ -31,9 +34,12 @@ type StorageValueTypeMap = { [StorageKey.DevicesMicId]: string; [StorageKey.DevicesMicName]: string; [StorageKey.DevicesMicVolume]: number; + [StorageKey.DevicesMicPermissionGranted]: boolean; [StorageKey.DevicesVideoEnabled]: boolean; [StorageKey.DevicesVideoId]: string; [StorageKey.DevicesVideoName]: string; + [StorageKey.DevicesVideoPermissionGranted]: boolean; + [StorageKey.PermissionsTabId]: number; [StorageKey.RecordingState]: RecordingState; [StorageKey.RecordingDownloadId]: number; [StorageKey.UiCameraBubbleEnabled]: boolean; @@ -78,13 +84,22 @@ export const storage = { id: createStorageSetterGetter(StorageKey.DevicesMicId), name: createStorageSetterGetter(StorageKey.DevicesMicName), volume: createStorageSetterGetter(StorageKey.DevicesMicVolume), + permissionsGranted: createStorageSetterGetter( + StorageKey.DevicesMicPermissionGranted, + ), }, video: { enabled: createStorageSetterGetter(StorageKey.DevicesVideoEnabled), id: createStorageSetterGetter(StorageKey.DevicesVideoId), name: createStorageSetterGetter(StorageKey.DevicesVideoName), + permissionsGranted: createStorageSetterGetter( + StorageKey.DevicesVideoPermissionGranted, + ), }, }, + permissions: { + tabId: createStorageSetterGetter(StorageKey.PermissionsTabId), + }, recording: { state: createStorageSetterGetter(StorageKey.RecordingState), downloadId: createStorageSetterGetter(StorageKey.RecordingDownloadId), diff --git a/src/ui/pages/permissions/Permissions.tsx b/src/ui/pages/permissions/Permissions.tsx new file mode 100644 index 0000000..3dcf7ef --- /dev/null +++ b/src/ui/pages/permissions/Permissions.tsx @@ -0,0 +1,144 @@ +import { useEffect, useState } from "react"; +import { + IconVideo, + IconMicrophone, + IconLoader2, + IconCircleCheck, + IconCircleX, +} from "@tabler/icons-react"; +import lookup512 from "/static/Lookup_512.png"; +import { storage } from "@/shared/storage"; +import { sender } from "@/shared/messaging"; + +enum PermissionState { + NotGranted, + Granted, + NotAllowed, +} + +export const Permissions = () => { + const [videoPermissionState, setVideoPermissionState] = useState( + PermissionState.NotGranted, + ); + const [micPermissionsState, setMicPermissionsState] = useState( + PermissionState.NotGranted, + ); + + useEffect(() => { + (async () => { + await navigator.mediaDevices.getUserMedia({ + video: true, + }); + await storage.devices.video.permissionsGranted.set(true); + setVideoPermissionState(PermissionState.Granted); + })().catch(() => { + storage.devices.video.permissionsGranted.set(true).catch((err) => { + console.error( + `Can't set storage value for video device permissions: ${(err as Error).toString()}`, + ); + }); + setVideoPermissionState(PermissionState.NotAllowed); + }); + }, []); + + useEffect(() => { + (async () => { + await navigator.mediaDevices.getUserMedia({ + audio: true, + }); + setMicPermissionsState(PermissionState.Granted); + await storage.devices.mic.permissionsGranted.set(true); + })().catch(() => { + storage.devices.mic.permissionsGranted.set(true).catch((err) => { + console.error( + `Can't set storage value for video device permissions: ${(err as Error).toString()}`, + ); + }); + setMicPermissionsState(PermissionState.NotAllowed); + }); + }, []); + + useEffect(() => { + if ( + videoPermissionState === PermissionState.NotGranted || + micPermissionsState === PermissionState.NotGranted + ) { + return; + } + + setTimeout(() => { + sender.background.permissionsPageClose().catch((err) => { + console.error( + `Can't send event to background: ${(err as Error).toString()}`, + ); + }); + }, 1 * 1000); + }, [micPermissionsState, videoPermissionState]); + + return ( +
+ +
+

+ Now the browser will ask for your permission to use your camera and + microphone. +

+

Please do not close this tab, it will close automatically.

+
+
+
+ + {videoPermissionState === PermissionState.NotGranted ? ( + + ) : videoPermissionState === PermissionState.Granted ? ( + + ) : ( + + )} +
+
+ + {micPermissionsState === PermissionState.NotGranted ? ( + + ) : micPermissionsState === PermissionState.Granted ? ( + + ) : ( + + )} +
+
+
+ ); +}; diff --git a/src/ui/pages/permissions/permissions.ts b/src/ui/pages/permissions/permissions.ts index 2edd7ab..8e3b9be 100644 --- a/src/ui/pages/permissions/permissions.ts +++ b/src/ui/pages/permissions/permissions.ts @@ -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)); +} diff --git a/src/ui/styles/global.css b/src/ui/styles/global.css index e426ada..6233bc0 100644 --- a/src/ui/styles/global.css +++ b/src/ui/styles/global.css @@ -6,8 +6,8 @@ --color-klack-charcoal-700: oklch(0.3 0 0); --color-klack-charcoal-800: oklch(0.25 0 0); --color-klack-charcoal-900: oklch(0.2 0 0); + --color-klack-emerald-400: oklch(0.7688 0.1687 161.95); --color-klack-red-500: oklch(0.7 0.192 23.51); --color-klack-red-600: oklch(0.65 0.1825 23.69); - --color-klack-red-700: oklch(0.59 0.1619 23.45); --font-dosis: "Dosis", sans-serif; } From db5deb7ef0b8acf4a66883a1c8268c3006b6df2b Mon Sep 17 00:00:00 2001 From: Yury Getman Date: Wed, 25 Jun 2025 23:35:49 +0400 Subject: [PATCH 2/2] Added popup menu warning about permissions and ability to grant access to mic and video from it --- .../permissions_giver/permissions_giver.ts | 11 +- src/shared/messaging.ts | 13 +- src/shared/storage.ts | 10 -- src/ui/pages/permissions/Permissions.tsx | 120 ++++++++++-------- src/ui/pages/popup/Popup.tsx | 84 ++++++++++-- 5 files changed, 162 insertions(+), 76 deletions(-) diff --git a/src/background/services/permissions_giver/permissions_giver.ts b/src/background/services/permissions_giver/permissions_giver.ts index ab3debc..c2239d1 100644 --- a/src/background/services/permissions_giver/permissions_giver.ts +++ b/src/background/services/permissions_giver/permissions_giver.ts @@ -7,7 +7,8 @@ import { import { storage } from "@/shared/storage"; class PermissionsGiver { - static async createPermissionsTab() { + static async openPermissionsTab() { + console.log("PermissionsGiver.openPermissionsTab()"); const tab = await chrome.tabs.create({ active: true, url: chrome.runtime.getURL("permissions.html"), @@ -16,6 +17,7 @@ class PermissionsGiver { } static async closePermissionsTab() { + console.log("PermissionsGiver.closePermissionsTab()"); await chrome.tabs.remove(await storage.permissions.tabId.get()); await storage.permissions.tabId.set(0); } @@ -24,7 +26,7 @@ class PermissionsGiver { chrome.runtime.onInstalled.addListener((_details) => { console.log("Handle 'chrome.runtime.onInstalled'"); (async () => { - await PermissionsGiver.createPermissionsTab(); + await PermissionsGiver.openPermissionsTab(); })().catch((err) => { console.error( `Error in 'chrome.runtime.onInstalled' handler: ${(err as Error).toString()}`, @@ -44,7 +46,10 @@ chrome.runtime.onMessage.addListener( return; } switch (type) { - case MessageType.PermissionsPageClose: + case MessageType.PermissionsTabOpen: + await PermissionsGiver.openPermissionsTab(); + break; + case MessageType.PermissionsTabClose: await PermissionsGiver.closePermissionsTab(); break; } diff --git a/src/shared/messaging.ts b/src/shared/messaging.ts index a9cce80..260240a 100644 --- a/src/shared/messaging.ts +++ b/src/shared/messaging.ts @@ -8,7 +8,8 @@ export enum MessageType { RecordingResume = "RecordingResume", RecordingCancel = "RecordingCancel", RecordingSave = "RecordingSave", - PermissionsPageClose = "PermissionsPageClose", + PermissionsTabOpen = "PermissionsPageOpen", + PermissionsTabClose = "PermissionsPageClose", // offscreen RecorderCreate = "RecorderCreate", RecorderStart = "RecorderStart", @@ -97,9 +98,15 @@ export const sender = { options, }); }, - permissionsPageClose: () => { + permissionsTabOpen: () => { return chrome.runtime.sendMessage({ - type: MessageType.PermissionsPageClose, + type: MessageType.PermissionsTabOpen, + target: "background", + }); + }, + permissionsTabClose: () => { + return chrome.runtime.sendMessage({ + type: MessageType.PermissionsTabClose, target: "background", }); }, diff --git a/src/shared/storage.ts b/src/shared/storage.ts index a2d4a56..dbfc0e3 100644 --- a/src/shared/storage.ts +++ b/src/shared/storage.ts @@ -5,11 +5,9 @@ export enum StorageKey { DevicesMicId = "devices.mic.id", DevicesMicName = "devices.mic.name", DevicesMicVolume = "devices.mic.volume", - DevicesMicPermissionGranted = "devices.mic.granted", DevicesVideoEnabled = "devices.video.enabled", DevicesVideoId = "devices.video.id", DevicesVideoName = "devices.video.name", - DevicesVideoPermissionGranted = "devices.video.granted", PermissionsTabId = "permissions.tabId", RecordingState = "recording.state", RecordingDownloadId = "recording.download_id", @@ -34,11 +32,9 @@ type StorageValueTypeMap = { [StorageKey.DevicesMicId]: string; [StorageKey.DevicesMicName]: string; [StorageKey.DevicesMicVolume]: number; - [StorageKey.DevicesMicPermissionGranted]: boolean; [StorageKey.DevicesVideoEnabled]: boolean; [StorageKey.DevicesVideoId]: string; [StorageKey.DevicesVideoName]: string; - [StorageKey.DevicesVideoPermissionGranted]: boolean; [StorageKey.PermissionsTabId]: number; [StorageKey.RecordingState]: RecordingState; [StorageKey.RecordingDownloadId]: number; @@ -84,17 +80,11 @@ export const storage = { id: createStorageSetterGetter(StorageKey.DevicesMicId), name: createStorageSetterGetter(StorageKey.DevicesMicName), volume: createStorageSetterGetter(StorageKey.DevicesMicVolume), - permissionsGranted: createStorageSetterGetter( - StorageKey.DevicesMicPermissionGranted, - ), }, video: { enabled: createStorageSetterGetter(StorageKey.DevicesVideoEnabled), id: createStorageSetterGetter(StorageKey.DevicesVideoId), name: createStorageSetterGetter(StorageKey.DevicesVideoName), - permissionsGranted: createStorageSetterGetter( - StorageKey.DevicesVideoPermissionGranted, - ), }, }, permissions: { diff --git a/src/ui/pages/permissions/Permissions.tsx b/src/ui/pages/permissions/Permissions.tsx index 3dcf7ef..899f685 100644 --- a/src/ui/pages/permissions/Permissions.tsx +++ b/src/ui/pages/permissions/Permissions.tsx @@ -7,83 +7,103 @@ import { IconCircleX, } from "@tabler/icons-react"; import lookup512 from "/static/Lookup_512.png"; -import { storage } from "@/shared/storage"; import { sender } from "@/shared/messaging"; -enum PermissionState { - NotGranted, - Granted, - NotAllowed, -} - export const Permissions = () => { - const [videoPermissionState, setVideoPermissionState] = useState( - PermissionState.NotGranted, - ); - const [micPermissionsState, setMicPermissionsState] = useState( - PermissionState.NotGranted, - ); + const [videoPermissionState, setVideoPermissionState] = + useState("prompt"); + const [micPermissionsState, setMicPermissionsState] = + useState("prompt"); useEffect(() => { (async () => { - await navigator.mediaDevices.getUserMedia({ - video: true, - }); - await storage.devices.video.permissionsGranted.set(true); - setVideoPermissionState(PermissionState.Granted); - })().catch(() => { - storage.devices.video.permissionsGranted.set(true).catch((err) => { - console.error( - `Can't set storage value for video device permissions: ${(err as Error).toString()}`, - ); + const permissionsStatus = await navigator.permissions.query({ + name: "camera", }); - setVideoPermissionState(PermissionState.NotAllowed); + 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 () => { - await navigator.mediaDevices.getUserMedia({ - audio: true, + const permissionsStatus = await navigator.permissions.query({ + name: "microphone", }); - setMicPermissionsState(PermissionState.Granted); - await storage.devices.mic.permissionsGranted.set(true); - })().catch(() => { - storage.devices.mic.permissionsGranted.set(true).catch((err) => { - console.error( - `Can't set storage value for video device permissions: ${(err as Error).toString()}`, - ); - }); - setMicPermissionsState(PermissionState.NotAllowed); + 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 === PermissionState.NotGranted || - micPermissionsState === PermissionState.NotGranted + videoPermissionState !== "granted" || + micPermissionsState !== "granted" ) { return; } - setTimeout(() => { - sender.background.permissionsPageClose().catch((err) => { + sender.background.permissionsTabClose().catch((err) => { console.error( `Can't send event to background: ${(err as Error).toString()}`, ); }); - }, 1 * 1000); + }, 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 (
-
-

- Now the browser will ask for your permission to use your camera and - microphone. -

-

Please do not close this tab, it will close automatically.

+
+ {videoPermissionState === "denied" || + micPermissionsState === "denied" ? ( +

+ You denied access to required devices. Please enable them in your + browser settings and close this tab. +

+ ) : ( +

+ Now the browser will ask for your permission to use your camera and + microphone. Please do not close this tab, it will close + automatically. +

+ )}
@@ -92,13 +112,13 @@ export const Permissions = () => { stroke={2} size={64} /> - {videoPermissionState === PermissionState.NotGranted ? ( + {videoPermissionState === "prompt" ? ( - ) : videoPermissionState === PermissionState.Granted ? ( + ) : videoPermissionState === "granted" ? ( { stroke={2} size={64} /> - {micPermissionsState === PermissionState.NotGranted ? ( + {micPermissionsState === "prompt" ? ( - ) : micPermissionsState === PermissionState.Granted ? ( + ) : micPermissionsState === "granted" ? ( { className="h-[28px] w-[28px] stroke-black transition-colors hover:stroke-[#00d492]" stroke={2} onClick={() => { - (async () => { - await sender.background.recordingComplete(); - })().catch((err) => - console.error( - `Can't complete and download recording: ${(err as Error).toString()}`, - ), - ); + sender.background + .recordingComplete() + .catch((err) => + console.error( + `Can't complete and download recording: ${(err as Error).toString()}`, + ), + ); }} /> { }; export const Popup = () => { + const [micPermissionsState, setMicPermissionsState] = + useState("prompt"); + const [videoPermissionsState, setVideoPermissionsState] = + useState("prompt"); + + useEffect(() => { + (async () => { + const permissionsStatus = await navigator.permissions.query({ + name: "camera", + }); + setVideoPermissionsState(permissionsStatus.state); + permissionsStatus.onchange = () => { + setVideoPermissionsState(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()}`, + ); + }); + }, []); + return (
{
- - - + {micPermissionsState === "granted" && + videoPermissionsState === "granted" ? ( + <> + + + + + ) : ( +
+
+

+ Extension requires permissions to access your camera and + microphone +

+
+ +
+ )}
);