1/** 2 * GriGsi â confirm before camera/microphone access when permission is not granted. 3 */ 4(function (global) { 5 "use strict"; 6 7 const QUESTION = "ÐÑкаÑе ли да го ÑазÑеÑиÑе?"; 8 const DENIED_HINT = 9 "ÐоÑÑÑпÑÑ Ðµ блокиÑан в наÑÑÑойкиÑе на бÑаÑзÑÑа â ÑазÑеÑеÑе Ð¾Ñ Ð¸ÐºÐ¾Ð½Ð°Ñа на каÑинаÑа в адÑеÑнаÑа ленÑа."; 10 const BTN_ALLOW = "РазÑеÑи"; 11 const BTN_DENY = "Ðе Ñега"; 12 13 let overlayEl = null; 14 let pendingResolve = null; 15 16 function injectStyles() { 17 if (document.getElementById("grigsi-media-perm-style")) return; 18 const style = document.createElement("style"); 19 style.id = "grigsi-media-perm-style"; 20 style.textContent = 21 ".grigsi-media-perm-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;padding:1rem;background:rgba(12,8,24,.62);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}" + 22 ".grigsi-media-perm-panel{background:#fff;border-radius:14px;max-width:22rem;width:100%;padding:1.35rem 1.4rem 1.15rem;box-shadow:0 14px 40px rgba(0,0,0,.28);font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif}" + 23 ".grigsi-media-perm-q{margin:0 0 .55rem;font-size:1.05rem;font-weight:600;color:#1a1228;line-height:1.35}" + 24 ".grigsi-media-perm-detail{margin:0 0 .65rem;font-size:.92rem;color:#4a3d5c;line-height:1.4}" + 25 ".grigsi-media-perm-hint{margin:0 0 1rem;font-size:.82rem;color:#7a2e2e;line-height:1.35}" + 26 ".grigsi-media-perm-actions{display:flex;gap:.55rem;justify-content:flex-end;flex-wrap:wrap}" + 27 ".grigsi-media-perm-btn{border:0;border-radius:9px;padding:.55rem .95rem;font-size:.92rem;font-weight:600;cursor:pointer}" + 28 ".grigsi-media-perm-btn--allow{background:#3d2860;color:#fff}" + 29 ".grigsi-media-perm-btn--allow:hover{background:#4e3478}" + 30 ".grigsi-media-perm-btn--deny{background:#ece8f2;color:#3d2860}" + 31 ".grigsi-media-perm-btn--deny:hover{background:#e0d8ec}" + 32 ".grigsi-media-perm-overlay.hidden{display:none!important;pointer-events:none!important;visibility:hidden!important}"; 33 document.head.appendChild(style); 34 } 35 36 function detailLine(wantVideo, wantAudio) { 37 if (wantVideo && wantAudio) return "ÐамеÑа и микÑоÑон"; 38 if (wantVideo) return "ÐамеÑа"; 39 if (wantAudio) return "ÐикÑоÑон"; 40 return ""; 41 } 42 43 function ensureOverlay() { 44 injectStyles(); 45 if (overlayEl) return overlayEl; 46 overlayEl = document.createElement("div"); 47 overlayEl.className = "grigsi-media-perm-overlay hidden"; 48 overlayEl.setAttribute("role", "dialog"); 49 overlayEl.setAttribute("aria-modal", "true"); 50 overlayEl.setAttribute("aria-labelledby", "grigsi-media-perm-q"); 51 overlayEl.innerHTML = 52 '<div class="grigsi-media-perm-panel">' + 53 '<p class="grigsi-media-perm-q" id="grigsi-media-perm-q"></p>' + 54 '<p class="grigsi-media-perm-detail" id="grigsi-media-perm-detail"></p>' + 55 '<p class="grigsi-media-perm-hint hidden" id="grigsi-media-perm-hint"></p>' + 56 '<div class="grigsi-media-perm-actions">' + 57 '<button type="button" class="grigsi-media-perm-btn grigsi-media-perm-btn--deny" id="grigsi-media-perm-deny"></button>' + 58 '<button type="button" class="grigsi-media-perm-btn grigsi-media-perm-btn--allow" id="grigsi-media-perm-allow"></button>' + 59 "</div></div>"; 60 document.body.appendChild(overlayEl); 61 overlayEl.querySelector("#grigsi-media-perm-deny").textContent = BTN_DENY; 62 overlayEl.querySelector("#grigsi-media-perm-allow").textContent = BTN_ALLOW; 63 overlayEl.querySelector("#grigsi-media-perm-deny").addEventListener("click", () => finishDialog(false)); 64 overlayEl.querySelector("#grigsi-media-perm-allow").addEventListener("click", () => finishDialog(true)); 65 overlayEl.addEventListener("click", (e) => { 66 if (e.target === overlayEl) finishDialog(false); 67 }); 68 return overlayEl; 69 } 70 71 function finishDialog(allowed) { 72 if (!overlayEl) return; 73 overlayEl.classList.add("hidden"); 74 overlayEl.setAttribute("aria-hidden", "true"); 75 const resolve = pendingResolve; 76 pendingResolve = null; 77 if (resolve) resolve(!!allowed); 78 } 79 80 function showDialog(opts) { 81 const wantVideo = !!opts.wantVideo; 82 const wantAudio = !!opts.wantAudio; 83 const anyDenied = !!opts.anyDenied; 84 const el = ensureOverlay(); 85 el.querySelector("#grigsi-media-perm-q").textContent = QUESTION; 86 const detail = el.querySelector("#grigsi-media-perm-detail"); 87 const hint = el.querySelector("#grigsi-media-perm-hint"); 88 const line = detailLine(wantVideo, wantAudio); 89 if (detail) { 90 detail.textContent = line; 91 detail.classList.toggle("hidden", !line); 92 } 93 if (hint) { 94 hint.textContent = DENIED_HINT; 95 hint.classList.toggle("hidden", !anyDenied); 96 } 97 el.classList.remove("hidden"); 98 el.setAttribute("aria-hidden", "false"); 99 return new Promise((resolve) => { 100 pendingResolve = resolve; 101 }); 102 } 103 104 async function queryPermissionName(name) { 105 if (!navigator.permissions || typeof navigator.permissions.query !== "function") { 106 return "unknown"; 107 } 108 try { 109 const r = await navigator.permissions.query({ name: name }); 110 return String(r && r.state ? r.state : "unknown"); 111 } catch (_) { 112 return "unknown"; 113 } 114 } 115 116 function sessionGranted(kind) { 117 try { 118 return sessionStorage.getItem("grigsi_media_ok_" + kind) === "1"; 119 } catch (_) { 120 return false;
121 } 122 } 123 124 function markSessionGranted(opts) { 125 try { 126 if (opts && opts.wantVideo) sessionStorage.setItem("grigsi_media_ok_camera", "1"); 127 if (opts && opts.wantAudio) sessionStorage.setItem("grigsi_media_ok_mic", "1"); 128 } catch (_) {} 129 } 130 131 function normalizeState(state, kind) { 132 const s = String(state || "unknown"); 133 if (s === "granted" || s === "denied" || s === "prompt") return s; 134 return sessionGranted(kind) ? "granted" : "unknown"; 135 } 136 137 async function permissionStates(opts) { 138 const wantVideo = !!opts.wantVideo; 139 const wantAudio = !!opts.wantAudio; 140 const video = wantVideo ? normalizeState(await queryPermissionName("camera"), "camera") : "granted"; 141 const audio = wantAudio ? normalizeState(await queryPermissionName("microphone"), "microphone") : "granted"; 142 const allGranted = 143 (!wantVideo || video === "granted") && (!wantAudio || audio === "granted"); 144 const anyDenied = (wantVideo && video === "denied") || (wantAudio && audio === "denied"); 145 const needsPrompt = 146 !allGranted && 147 ((wantVideo && video !== "granted") || (wantAudio && audio !== "granted")); 148 return { video, audio, allGranted, anyDenied, needsPrompt }; 149 } 150 151 async function needsPrompt(opts) { 152 if (!opts || (!opts.wantVideo && !opts.wantAudio)) return false; 153 const st = await permissionStates(opts); 154 if (st.allGranted) return false; 155 return true; 156 } 157 158 /** 159 * When camera/mic not granted: show confirm dialog. Resolves true if user allows. 160 * If already granted, resolves true immediately (same user-gesture turn as caller). 161 */ 162 async function ensureAllowed(opts) { 163 const wantVideo = !!(opts && opts.wantVideo); 164 const wantAudio = !!(opts && opts.wantAudio); 165 if (!wantVideo && !wantAudio) return true; 166 const st = await permissionStates({ wantVideo, wantAudio }); 167 if (st.allGranted) return true; 168 return showDialog({ wantVideo, wantAudio, anyDenied: st.anyDenied }); 169 } 170 171 /** 172 * onAllow runs after user confirms (or immediately when already granted). 173 */ 174 async function runIfAllowed(opts) { 175 const onAllow = opts && opts.onAllow; 176 const onDeny = opts && opts.onDeny; 177 const ok = await ensureAllowed(opts || {}); 178 if (ok) { 179 if (onAllow) onAllow(); 180 return true; 181 } 182 if (onDeny) onDeny(); 183 return false; 184 } 185 186 global.GriGsiMediaPermission = { 187 QUESTION, 188 BTN_ALLOW, 189 BTN_DENY, 190 needsPrompt, 191 ensureAllowed, 192 runIfAllowed, 193 markSessionGranted, 194 }; 195})(typeof window !== "undefined" ? window : global);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.