5.0 KiB, 1 run
created by r2519314175:887, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // The user's own window onto the extension: what mode it is in, what page it is |
| 2 | // holding, and which sites it may touch. Every grant here can be withdrawn here. |
| 3 | |
| 4 | 'use strict'; |
| 5 | |
| 6 | (() => { |
| 7 | |
| 8 | const $ = (id) => document.getElementById(id); |
| 9 | const I = globalThis.DaimondExtI18n; |
| 10 | const t = (...a) => I.t(...a); |
| 11 | |
| 12 | /// A match pattern, said the way a person would say it. |
| 13 | function plain(pat) { |
| 14 | if (pat === '<all_urls>') return t('popup_any_tab'); |
| 15 | // Not an origin, and deliberately not spelled like one: what was granted |
| 16 | // is the machine. But it was granted to ONE page, and the page is named, |
| 17 | // because a user looking at this list is auditing what they gave away and |
| 18 | // "commands on this computer" alone does not say to whom. |
| 19 | if (pat === 'machine-hand') return t('popup_machine_hand'); |
| 20 | if (pat.indexOf('machine-hand:') === 0) return t('popup_machine_hand_origin', pat.slice('machine-hand:'.length)); |
| 21 | const m = /^\*:\/\/\*\.([^/]+)\/\*$/.exec(pat); |
| 22 | if (m) return t('popup_subdomains', m[1]); |
| 23 | const n = /^\*:\/\/([^/]+)\/\*$/.exec(pat); |
| 24 | return n ? n[1] : pat; |
| 25 | } |
| 26 | |
| 27 | /// A line of state: a sentence, and the small print under it. |
| 28 | function says(el, head, fine) { |
| 29 | el.textContent = ''; |
| 30 | const h = document.createElement('div'); |
| 31 | const f = document.createElement('small'); |
| 32 | h.textContent = head; |
| 33 | f.textContent = fine; |
| 34 | el.append(h, f); |
| 35 | } |
| 36 | |
| 37 | /// Paints the current state. |
| 38 | function paint(s) { |
| 39 | $('ver').textContent = `v${s.version}`; |
| 40 | |
| 41 | // A question is waiting. Its own window is in front, but a window can be |
| 42 | // covered, minimised or lost behind the app -- and then this icon is the |
| 43 | // only place left that knows. Say what is being asked and offer the way |
| 44 | // back to it. This RAISES that window; it never opens a second one. |
| 45 | const ask = $('ask'); |
| 46 | if (s.pending) { |
| 47 | ask.hidden = false; |
| 48 | if (s.pending.kind === 'mirror') { |
| 49 | says(ask, t('popup_ask_mirror'), t('popup_ask_mirror_fine')); |
| 50 | } else if (s.pending.kind === 'hand') { |
| 51 | says(ask, t('popup_ask_hand'), t('popup_ask_hand_fine')); |
| 52 | } else { |
| 53 | says(ask, t('popup_ask_site', s.pending.host), t('popup_ask_site_fine', s.pending.host)); |
| 54 | } |
| 55 | $('raise').hidden = false; |
| 56 | } else { |
| 57 | ask.hidden = true; |
| 58 | $('raise').hidden = true; |
| 59 | } |
| 60 | |
| 61 | const mode = $('mode'); |
| 62 | mode.className = 'mode ' + s.mode; |
| 63 | if (s.mode === 'user') { |
| 64 | says(mode, t('popup_mode_user'), t('popup_mode_user_fine')); |
| 65 | $('take').hidden = false; |
| 66 | } else if (s.mode === 'agent') { |
| 67 | says(mode, t('popup_mode_agent'), t('popup_mode_agent_fine')); |
| 68 | $('take').hidden = true; |
| 69 | } else { |
| 70 | says(mode, t('popup_mode_idle'), t('popup_mode_idle_fine')); |
| 71 | $('take').hidden = true; |
| 72 | } |
| 73 | |
| 74 | $('url').textContent = s.url || ''; |
| 75 | // One question at a time. Offering the live view -- the broadest permission |
| 76 | // Chrome has -- beside a site question the user has not answered is how a |
| 77 | // clear flow turns back into three surprises. |
| 78 | $('mirror').hidden = (s.granted || []).includes('<all_urls>') || !!s.pending; |
| 79 | |
| 80 | const ul = $('granted'); |
| 81 | ul.textContent = ''; |
| 82 | if (!s.granted || !s.granted.length) { |
| 83 | const li = document.createElement('li'); |
| 84 | li.className = 'none'; |
| 85 | li.textContent = t('popup_none'); |
| 86 | ul.append(li); |
| 87 | return; |
| 88 | } |
| 89 | for (const pat of s.granted) { |
| 90 | const li = document.createElement('li'); |
| 91 | const span = document.createElement('span'); |
| 92 | const btn = document.createElement('button'); |
| 93 | // The pattern is what was granted; the plain form is what it MEANS. |
| 94 | // A user auditing what they have given away should not have to read |
| 95 | // a match pattern to find out that it covers subdomains. |
| 96 | span.textContent = plain(pat); |
| 97 | span.title = pat; |
| 98 | btn.textContent = t('popup_revoke'); |
| 99 | btn.addEventListener('click', async () => { |
| 100 | const res = await chrome.runtime.sendMessage({ type: 'revoke', pattern: pat }); |
| 101 | if (res && res.ok) refresh(); |
| 102 | }); |
| 103 | li.append(span, btn); |
| 104 | ul.append(li); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | async function refresh() { |
| 109 | const s = await chrome.runtime.sendMessage({ type: 'panel' }); |
| 110 | if (s && s.ok) paint(s); |
| 111 | } |
| 112 | |
| 113 | // Bring the waiting question back to the front. It is the same window, raised |
| 114 | // -- opening another would be the popup flood the live-view guard exists to |
| 115 | // stop. |
| 116 | $('raise').addEventListener('click', async () => { |
| 117 | const r = await chrome.runtime.sendMessage({ type: 'raise' }); |
| 118 | if (r && r.ok) window.close(); |
| 119 | else refresh(); |
| 120 | }); |
| 121 | |
| 122 | $('take').addEventListener('click', async () => { |
| 123 | await chrome.runtime.sendMessage({ type: 'takeover' }); |
| 124 | refresh(); |
| 125 | }); |
| 126 | |
| 127 | // A click in the popup is a user gesture, which is what Chrome insists on |
| 128 | // before it will widen a permission. So the request is made from here. |
| 129 | $('mirror').addEventListener('click', async () => { |
| 130 | try { |
| 131 | await chrome.permissions.request({ origins: ['<all_urls>'] }); |
| 132 | } catch (e) { |
| 133 | // The user said no. Nothing changes. |
| 134 | } |
| 135 | refresh(); |
| 136 | }); |
| 137 | |
| 138 | // The marked nodes are painted the moment the table is in, and the state is |
| 139 | // asked for after -- so the window never draws a translated question around |
| 140 | // English furniture. |
| 141 | I.ready().then(() => { I.paint(); refresh(); }); |
| 142 | |
| 143 | })(); |