Steven
Steven5 хв читання

Ваш застосунок для Mac отримує доступ до мікрофона — і забуває його при кожному запуску

GeekBye попросив доступ до мікрофона, ви його надали, і все запрацювало. Наступний запуск: доступу немає. А в Системних параметрах → Мікрофон застосунок узагалі не з’явився. Винуватцем виявилася функція безпеки macOS, що тихо запускає застосунок зі шляху, який зникає — ось діагноз і виправлення в одне натискання.

macOS
Дозволи
Інженерія
Релізи GeekBye
Ваш застосунок для Mac отримує доступ до мікрофона — і забуває його при кожному запуску

Ось вада, через яку починаєш сумніватися у власних очах. Ви встановлюєте GeekBye, він просить доступ до мікрофона, ви натискаєте Дозволити, і транскрипція працює. Чудово. Ви закриваєте, відкриваєте наступного ранку — а він просить доступ до мікрофона знову. Ви йдете в Системні параметри → Конфіденційність і безпека → Мікрофон, щоб виправити це вручну, а GeekBye узагалі немає в списку. Не заборонений. Не дозволений. Просто відсутній, наче він ніколи й не питав.

Кожен окремий шматочок виглядав правильним. Запит був справжнім. Дозвіл працював у ту мить. Застосунок був належно підписаний і нотаризований із правильними рядками використання. І все ж наданий дозвіл випаровувався при кожному запуску. GeekBye v2.0.6 це виправив — а першопричина в одній із найпідступніших речей, які macOS робить, щоб вас захистити.

Функція, що ховала застосунок від нього самого

Винуватець — macOS App Translocation, функція безпеки Gatekeeper. Коли ви завантажуєте застосунок і запускаєте його прямо з DMG чи з папки ~/Downloads — усюди, де він усе ще «в карантині» — macOS насправді запускає його не звідти, де ви його бачите. Він прозоро копіює його у випадковий шлях лише для читання глибоко в /private/var/folders/.../AppTranslocation/… і запускає цю копію. Це хороший захист: він заважає шкідливому завантаженню підміняти файли поруч із собою.

Але ось зіткнення. Система дозволів macOS (TCC — те, що відстежує, хто може використовувати ваш мікрофон, камеру, екран) ідентифікує застосунок за його шляхом та ідентичністю коду. Коли застосунок транслоковано, цей шлях випадковий і тимчасовий. Тож коли ви надаєте доступ до мікрофона, macOS сумлінно записує надання — до шляху, якого не буде при наступному запуску. Відкриваєте застосунок знову, macOS транслокує його в інший випадковий шлях, бачить застосунок, про який у нього немає запису, і питає знову. А оскільки цей фантомний шлях ніколи не буває стабільним місцем, застосунок так і не отримує постійний рядок у Системних параметрах → Мікрофон.

Застосунок надавав дозвіл привиду.

Саме тому з цим стикалися лише деякі люди. Якщо ваша копія GeekBye уже жила в /Applications — бо ви перетягнули її туди або бо вона потрапила туди через автооновлення — немає карантину, немає транслокації, є стабільний шлях, і все зберігається ідеально. Вада була невидимою для нас і для всіх, хто пройшов першу інсталяцію, а це саме той тип вади, що виживає найдовше.

Виправлення: дайте застосунку справжній дім

Раз уся проблема — у нестабільному шляху, виправлення в тому, щоб перемістити застосунок на стабільний. v2.0.6 виявляє, коли GeekBye працює транслокованим (або просто працює поза /Applications), і пропонує в одне натискання «Move to Applications» — використовуючи виклик переміщення macOS, що копіює бандл у /Applications і перезапускає його звідти. З цього моменту застосунок має фіксовану ідентичність: надання мікрофона тримається, запис екрана тримається, і GeekBye нарешті з’являється в Системних параметрах там, де ви цього очікуєте.

Запит ввічливий щодо цього. Він пропонує Move to Applications, Not Now і Don't Ask Again — і запам’ятовує останній вибір, тож застосунок ніколи не докучає тому, хто має свідому причину запускати його з іншого місця. Рішення про те, чи показувати запит узагалі, ізольоване в маленькі чисті функції (чи транслокована ця збірка? чи вона поза /Applications? чи вимкнув її користувач?), тож логіка покрита юніт-тестами без запуску справжньої нотаризованої збірки на справжньому карантинному томі.

Той самий реліз спростив і сам досвід роботи з дозволами. GeekBye раніше викидав кастомне, саморобне вбудоване вікно дозволів — пару сотень рядків інтерфейсу, що намагалися відтворити те, що ОС і так робить добре. v2.0.6 видалив його й опертий на нативний запит дозволів macOS, підкріплений тихим неблокуючим банером, що з’являється лише тоді, коли потрібного дозволу справді бракує. Менше коду, і поведінка, яку користувачі вже впізнають, бо будь-який інший застосунок для Mac працює так само.

Частина, якою я пишаюся найбільше: ми довели це, перш ніж повірили

Було б легко вгадати транслокацію й випустити виправлення. Натомість реліз спершу випустив стартову діагностику: при запуску GeekBye тепер повідомляє власний шлях виконуваного файлу й те, чи транслокований він, чи знаходиться всередині /Applications, а також поточний статус дозволів на мікрофон та екран. Ця телеметрія перетворила правдоподібну теорію на підтверджену — виробничі дані показали, що зачеплені сесії справді працювали з транслокованих шляхів поза /Applications, точно як передбачалося.

Цей порядок важливий. У «запит з’являється, але дозвіл не тримається» є кілька можливих пояснень — проблема з підписом, відсутній рядок використання, питання з entitlement, примха бази TCC. Ми виключили причини на рівні API (шлях запиту був очевидно коректним), а потім дозволили реальним даним указати на шар ідентичності/шляху, замість того щоб випускати виправлення навмання й сподіватися, що звернення в підтримку припиняться.

Три речі, яких навчає ця вада

  1. Дозвіл, який запитується, але не зберігається — це проблема ідентичності, а не API. Якщо код запиту коректний, а надання все одно зникає, перестаньте переписувати запит. Спитайте, до якого шляху та ідентичності коду ОС прив’язує надання — і чи стабільна ця ідентичність між запусками.
  2. Випускайте діагностику разом із (або перед) виправленням. Кілька полів — звідки я працюю, який мій статус дозволів — перетворили обґрунтований здогад на перевірену першопричину й сказали нам точно, які користувачі були зачеплені. Оснастіть інструментами межу, яку підозрюєте, перш ніж її латати.
  3. Вади, що ховаються від розробників — це ті, що працюють у «неправильному» середовищі. Наша жила в /Applications на кожній машині розробника, тож була структурно невидимою для нас, потрапляючи при цьому в нових користувачів. Коли звернення не відтворюється, перше питання — що відрізняється в місці, де воно працює, а не чи не помиляється користувач.

GeekBye v2.0.6 випустив запит переміщення й діагностику разом. Про ширшу дугу надійності, у яку це вписується, див. скільки насправді коштує версія 2 (v2.0.0) і чому запис екрана захоплює не той монітор (v2.0.10) — ще одна вада, що виринула лише в конкретному середовищі. Про реліз із дрібними деталями по сусідству — спокійне ПЗ: виправлення мерехтіння й чип режиму відповіді (v2.0.3 + v2.0.5).

Схожі статті

Відрізнити дзвінок від відкритого застосунку
Steven
Steven8 хв читання

Відрізнити дзвінок від відкритого застосунку

GeekBye вміє помітити, що ти приєднався до відеозустрічі, і запропонувати її записати. Виявлення виявляється легшою половиною — нативний Swift-бінарник читає заголовки вікон кожні десять секунд. Важка половина — точність: не спрацьовувати, коли Zoom просто відкритий, не пропонувати записати зустріч, яку ти вже записуєш, і не глушити мікрофон у дзвінку, в якому ти насправді сидиш. Три релізи, і кожен із них — запобіжник, якому довелося навчитися не перемагати самого себе.

Інженерія
macOS
Десктоп
Тиша була несуча
Steven
Steven7 хв читання

Тиша була несуча

Два останні релізи GeekBye v1 — про одну й ту саму незручну правду: транскрипція в реальному часі через справжню мережу не є безвтратною, і чесний хід — перестати вдавати, що вона така. v1.8.20 зберігав копію кожного фрагмента звуку на диску, перш ніж відкинути його під час перепідключення, і почав уголос позначати пропуски в транскрипті. v1.9.0 перестав надсилати тишу, щоб заощадити трафік — і виявив, що тиша була саме тим сигналом, за яким транскрайбер розумів, що речення скінчилося. Два релізи про ціну викидання речей.

Інженерія
Аудіо
Надійність
Три дієслова, що тримають Web Audio живим
Steven
Steven8 хв читання

Три дієслова, що тримають Web Audio живим

Два точкові релізи GeekBye, розділені двома місяцями і в двох різних файлах, дали нашому аудіокоду той самий урок із протилежних кінців: перестань поводитися з браузерним AudioContext як з одноразовим. Один реліз навчився робити resume() контексту, який macOS тихо призупинив посеред запису; інший навчився suspend() замість close(), щоб сесії одна за одною перестали врізатися в накинуту Chromium стелю приблизно в шість контекстів. Resume, suspend, close — ось і весь сюжет.

Інженерія
Аудіо
Десктоп