{"id":479432,"date":"2026-05-12T10:30:22","date_gmt":"2026-05-12T10:30:22","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=479432"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=479432","title":{"rendered":"\u041a\u0430\u043a \u044f \u0441\u0434\u0435\u043b\u0430\u043b desktop-\u0432\u0435\u0440\u0441\u0438\u044e \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430 \u043d\u0430 vanilla Electron, \u043d\u0435 \u043d\u0430 React Native for Desktop. \u0418 \u043d\u0435 \u043f\u043e\u0436\u0430\u043b\u0435\u043b"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<blockquote>\n<p>\u0423\u0440\u043e\u0432\u0435\u043d\u044c: middle\/senior, \u043a\u0440\u043e\u0441\u0441-\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435\u043d\u043d\u0430\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0421\u0442\u0435\u043a: Electron 28, electron-builder, electron-updater, vanilla HTML\/JS \u0427\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438: \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f, IPC \u043c\u0435\u0436\u0434\u0443 \u043e\u043a\u043d\u0430\u043c\u0438, deep links \u043d\u0430 \u0442\u0440\u0451\u0445 \u041e\u0421, tray-first \u043f\u0430\u0442\u0442\u0435\u0440\u043d, auto-updater grace, custom \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u044b<\/p>\n<\/blockquote>\n<h3>\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442<\/h3>\n<p>\u042d\u0442\u043e \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0438\u0437 \u0441\u0435\u0440\u0438\u0438 \u043f\u0440\u043e \u0438\u043d\u0436\u0435\u043d\u0435\u0440\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0432 ONEMIX \u2014 \u043c\u043e\u0451\u043c \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0435 \u043d\u0430 React Native. \u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b <a href=\"#\" rel=\"noopener noreferrer nofollow\">\u0442\u0440\u0451\u0445\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 \u043a\u044d\u0448 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/a>, <a href=\"#\" rel=\"noopener noreferrer nofollow\">Double Ratchet E2E<\/a> \u0438 <a href=\"#\" rel=\"noopener noreferrer nofollow\">WebRTC \u0437\u0432\u043e\u043d\u043a\u0438 \u0441 trickle ICE<\/a>. \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u043f\u0440\u043e \u0437\u0432\u043e\u043d\u043a\u0438 \u043d\u0430\u0431\u0440\u0430\u043b\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u043e\u0432, \u0438 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437 \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u043b\u0438 \u043f\u0440\u043e \u0434\u0435\u0441\u043a\u0442\u043e\u043f: &#171;\u0430 \u043a\u0430\u043a \u0443 \u0442\u0435\u0431\u044f \u0442\u0430\u043c \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u043e?&#187;.<\/p>\n<p>\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u0440\u043e desktop-\u0432\u0435\u0440\u0441\u0438\u044e. \u0421\u0440\u0430\u0437\u0443 \u0441\u043a\u0430\u0436\u0443: \u044f <strong>\u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b React Native for Desktop<\/strong>, \u043d\u0435 Tauri, \u043d\u0435 React, \u043d\u0435 TypeScript. \u0427\u0438\u0441\u0442\u044b\u0439 Electron + vanilla HTML\/JS. \u042d\u0442\u043e \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0438 \u044f \u043e\u0431\u044a\u044f\u0441\u043d\u044e \u043f\u043e\u0447\u0435\u043c\u0443 \u043f\u043e\u0448\u0451\u043b \u044d\u0442\u0438\u043c \u043f\u0443\u0442\u0451\u043c, \u0447\u0442\u043e \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u0432\u044b\u0438\u0433\u0440\u0430\u043b, \u0438 \u0433\u0434\u0435 \u044d\u0442\u043e \u0431\u044c\u0451\u0442 \u043f\u043e \u0433\u043e\u043b\u043e\u0432\u0435.<\/p>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 vanilla Electron, \u0430 \u043d\u0435 RN-Desktop<\/h3>\n<p>\u041a\u043e\u0433\u0434\u0430 \u044f \u043d\u0430\u0447\u0438\u043d\u0430\u043b \u0434\u0435\u043b\u0430\u0442\u044c \u0434\u0435\u0441\u043a\u0442\u043e\u043f, \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u043b \u0447\u0435\u0442\u044b\u0440\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430:<\/p>\n<p><strong>React Native for Windows + macOS.<\/strong> \u042d\u0442\u043e \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 Microsoft \u0444\u043e\u0440\u043a RN \u0434\u043b\u044f Windows \u0438 \u0441\u0442\u0430\u0440\u044b\u0439 Facebook-\u0444\u043e\u0440\u043a \u0434\u043b\u044f macOS. \u0418\u0434\u0435\u044f \u0437\u0430\u043c\u0430\u043d\u0447\u0438\u0432\u0430\u044f \u2014 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u044c \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434. \u041d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0443 \u043c\u0435\u043d\u044f \u0431\u044b\u043b\u043e \u0434\u0432\u0430 \u0431\u043b\u043e\u043a\u0435\u0440\u0430. \u041f\u0435\u0440\u0432\u043e\u0435: \u043e\u0431\u0430 \u043f\u043e\u0440\u0442\u0430 \u0443\u0436\u0430\u0441\u043d\u043e \u043e\u0442\u0441\u0442\u0430\u044e\u0442 \u043e\u0442 mainline RN, \u043c\u043d\u043e\u0433\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 (<code>react-native-reanimated<\/code>, <code>react-native-svg<\/code>, <code>expo-secure-store<\/code>) \u043b\u0438\u0431\u043e \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f, \u043b\u0438\u0431\u043e \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u043c\u0443. \u0412\u0442\u043e\u0440\u043e\u0435: Linux-\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043d\u0435\u0442 \u0432 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435. \u0410 Linux \u044f \u0445\u043e\u0442\u0435\u043b.<\/p>\n<p><strong>Tauri.<\/strong> \u0421\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439, \u043b\u0451\u0433\u043a\u0438\u0439, \u043d\u0430 Rust. \u042f \u0441\u0435\u0440\u044c\u0451\u0437\u043d\u043e \u0435\u0433\u043e \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u043b \u0438 \u0434\u0430\u0436\u0435 \u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b. \u041c\u0438\u043d\u0443\u0441 \u043e\u0434\u0438\u043d, \u043d\u043e \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u044b\u0439: WebView \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u041e\u0421 \u0440\u0430\u0437\u043d\u044b\u0439 (Edge WebView2 \u043d\u0430 Windows, WebKit \u043d\u0430 macOS, WebKitGTK \u043d\u0430 Linux). \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u0447\u0442\u043e \u0443\u0441\u043b\u043e\u0432\u043d\u044b\u0439 CSS Grid \u0443 \u0442\u0435\u0431\u044f \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 Windows, \u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f \u043d\u0430 Linux, \u0438 \u043f\u043e\u0434\u0432\u0438\u0441\u0430\u0435\u0442 \u043d\u0430 macOS. \u041e\u0442\u043b\u0430\u0436\u0438\u0432\u0430\u0442\u044c \u043c\u0435\u0436\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435\u043d\u043d\u044b\u0435 \u0431\u0430\u0433\u0438 \u0432 Tauri \u2014 \u044d\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0436\u0430\u043d\u0440 \u0441\u0442\u0440\u0430\u0434\u0430\u043d\u0438\u0439. \u0423 Electron \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c Chromium, \u0432\u0435\u0437\u0434\u0435 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439, \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433 \u043f\u0440\u0435\u0434\u0441\u043a\u0430\u0437\u0443\u0435\u043c\u044b\u0439.<\/p>\n<p><strong>Electron + React (\u043a\u0430\u043a \u0434\u0435\u043b\u0430\u0435\u0442 Discord, Slack, WhatsApp Desktop).<\/strong> \u042d\u0442\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c. \u042f \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u043f\u043e \u043e\u0434\u043d\u043e\u0439 \u043f\u0440\u0438\u0447\u0438\u043d\u0435 \u2014 <strong>\u043f\u0435\u0440\u0435\u0443\u0441\u043b\u043e\u0436\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043c\u043e\u0438\u0445 \u0437\u0430\u0434\u0430\u0447<\/strong>. \u0423 \u043c\u0435\u043d\u044f \u043d\u0435\u0442 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0447\u0430\u0442\u043e\u0432 \u0438 \u0441\u043f\u0438\u0441\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u041d\u0435\u0442 state-\u043c\u0435\u043d\u0435\u0434\u0436\u043c\u0435\u043d\u0442\u0430 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 WebSocket + localStorage. \u0420\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u0431\u044d\u043a\u0435\u043d\u0434\u0435. \u0413\u043e\u0440\u043e\u0434\u0438\u0442\u044c webpack + babel + React + TypeScript \u0440\u0430\u0434\u0438 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 \u0441\u043f\u0438\u0441\u043a\u0430 \u0447\u0430\u0442\u043e\u0432 \u2014 \u044d\u0442\u043e \u0432\u0435\u0441 \u0440\u0430\u0434\u0438 \u0432\u0435\u0441\u0430. \u041d\u0430 vanilla \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0432 5 \u0440\u0430\u0437 \u043c\u0435\u043d\u044c\u0448\u0435 \u0431\u0438\u043b\u0434-\u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u0438 \u0432 3 \u0440\u0430\u0437\u0430 \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430.<\/p>\n<p><strong>Vanilla Electron + HTML\/JS.<\/strong> \u0422\u043e \u0447\u0442\u043e \u044f \u0432 \u0438\u0442\u043e\u0433\u0435 \u0432\u044b\u0431\u0440\u0430\u043b. \u041e\u0434\u0438\u043d main.js \u0441 main process. \u041e\u0434\u0438\u043d preload.js. \u041f\u044f\u0442\u044c HTML \u0444\u0430\u0439\u043b\u043e\u0432 (index, call, settings, join, share-group). \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0441\u0431\u043e\u0440\u0449\u0438\u043a\u0430. <code>electron .<\/code> \u2014 \u0438 \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u0448 \u0434\u0435\u0441\u043a\u0442\u043e\u043f \u2014 \u044d\u0442\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u044d\u043a\u0440\u0430\u043d\u043e\u0432, \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 reactivity \u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043a\u043e\u0434\u043e\u0432\u043e\u0439 \u0431\u0430\u0437\u043e\u0439, vanilla \u043d\u0435 \u043f\u043e\u0434\u043e\u0439\u0434\u0451\u0442. \u0411\u0435\u0440\u0438\u0442\u0435 React\/Vue\/Solid. \u041d\u043e \u0434\u043b\u044f \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430 \u0433\u0434\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0441\u043e\u0441\u0440\u0435\u0434\u043e\u0442\u043e\u0447\u0435\u043d\u0430 \u0432 \u0431\u044d\u043a\u0435\u043d\u0434\u0435 \u2014 \u044d\u0442\u043e \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c. \u0423 \u043c\u0435\u043d\u044f <code>package.json<\/code> \u0432 \u0434\u0435\u0441\u043a\u0442\u043e\u043f-\u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u2014 \u044d\u0442\u043e <strong>27 \u0441\u0442\u0440\u043e\u043a \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439<\/strong> (\u0432\u043a\u043b\u044e\u0447\u0430\u044f electron-builder \u0438 electron-updater). \u0421\u0431\u043e\u0440\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432\u0435\u0441\u0438\u0442 50MB \u0432\u043c\u0435\u0441\u0442\u043e 250MB \u0443 \u0441\u0440\u0435\u0434\u043d\u0435\u0433\u043e Electron-\u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430: \u0442\u0440\u0438 \u043e\u043a\u043d\u0430 \u0438 main process<\/h3>\n<p>\u0412 ONEMIX-\u0434\u0435\u0441\u043a\u0442\u043e\u043f\u0435 \u0442\u0440\u0438 \u0442\u0438\u043f\u0430 \u043e\u043a\u043e\u043d:<\/p>\n<p><strong>Main window<\/strong> \u2014 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0441 UI \u0447\u0430\u0442\u043e\u0432 \u0438 WebSocket-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435\u043c \u043a \u0431\u044d\u043a\u0435\u043d\u0434\u0443. \u042d\u0442\u043e \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u043e\u043a\u043d\u043e, \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0438\u0434\u0451\u0442 <strong>\u0432\u0441\u044f \u0441\u0435\u0442\u0435\u0432\u0430\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c<\/strong>. WebSocket \u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0434\u0435\u0441\u044c.<\/p>\n<p><strong>Call window<\/strong> \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0434\u043b\u044f \u0437\u0432\u043e\u043d\u043a\u043e\u0432. \u0421\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u043f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u0446\u0438\u0438 \u0437\u0432\u043e\u043d\u043a\u0430, \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438. \u0421\u043e\u0434\u0435\u0440\u0436\u0438\u0442 WebRTC PeerConnection, getUserMedia, \u0432\u0438\u0434\u0435\u043e-\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b.<\/p>\n<p><strong>Settings window<\/strong> \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a. \u0421\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u043f\u0440\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a, \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0438.<\/p>\n<p>\u0418\u0434\u0435\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043e\u043a\u043e\u043d \u043d\u0435 \u043c\u043e\u044f \u2014 \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0435\u0442 Telegram Desktop, \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0435\u0442 Skype. \u0417\u0432\u043e\u043d\u043e\u043a \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 <strong>\u0434\u043e\u043b\u0436\u043d\u044b<\/strong> \u0431\u044b\u0442\u044c \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u043c\u0438 \u043e\u043a\u043d\u0430\u043c\u0438 \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c:<\/p>\n<p>\u0417\u0432\u043e\u043d\u043e\u043a \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043a\u0440\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u043e\u0433\u0434\u0430 \u044e\u0437\u0435\u0440 \u0441\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e. \u0415\u0441\u043b\u0438 \u044e\u0437\u0435\u0440 \u043d\u0430 \u0437\u0432\u043e\u043d\u043a\u0435 \u0445\u043e\u0447\u0435\u0442 \u043e\u0442\u043a\u0440\u044b\u0442\u044c Excel\/\u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c \u2014 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0435\u043c\u0443 \u043c\u0435\u0448\u0430\u0435\u0442 \u0432 taskbar, \u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0437\u0432\u043e\u043d\u043a\u0430 \u043d\u0435\u0442.<\/p>\n<p>\u0417\u0432\u043e\u043d\u043e\u043a \u043c\u043e\u0436\u0435\u0442 (\u0438 \u0434\u043e\u043b\u0436\u0435\u043d) \u0431\u044b\u0442\u044c <code>alwaysOnTop<\/code>, \u0447\u0442\u043e\u0431\u044b \u0432\u0438\u0434\u0435\u043e \u0431\u044b\u043b\u043e \u0432\u0438\u0434\u043d\u043e \u043f\u043e\u0432\u0435\u0440\u0445 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u043e\u043a\u043e\u043d. \u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u2014 \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d.<\/p>\n<p>\u0417\u0432\u043e\u043d\u043e\u043a \u0438 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0436\u0438\u0432\u0443\u0442 \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u0436\u0438\u0437\u043d\u044f\u043c\u0438: \u0437\u0432\u043e\u043d\u043e\u043a \u043c\u043e\u0436\u0435\u0442 \u043e\u0431\u043e\u0440\u0432\u0430\u0442\u044c\u0441\u044f (\u0438 \u043e\u043a\u043d\u043e \u0437\u0430\u043a\u0440\u044b\u0442\u044c\u0441\u044f), \u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f. \u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u2014 \u0437\u0432\u043e\u043d\u043e\u043a \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c.<\/p>\n<pre><code class=\"javascript\">function createCallWindow(callState) {  if (callWindow &amp;&amp; !callWindow.isDestroyed()) { callWindow.focus(); return; }  const isVideo = callState.callType === 'video';  const { screen } = require('electron');  const { width: sw, height: sh } = screen.getPrimaryDisplay().workAreaSize;  const winW = isVideo ? 480 : 360;  const winH = isVideo ? 700 : 560;  callWindow = new BrowserWindow({    width: winW, height: winH,    minWidth: 300, minHeight: 420,    x: Math.round((sw - winW) \/ 2),    y: Math.round((sh - winH) \/ 2),    alwaysOnTop: true,    frame: false,    titleBarStyle: 'hidden',    backgroundColor: '#000000',    skipTaskbar: false,    webPreferences: {      preload: path.join(__dirname, 'preload.js'),      contextIsolation: true,      nodeIntegration: false,    },    show: false,  });  callWindow.loadFile(path.join(__dirname, 'src', 'call.html'), {    query: { state: JSON.stringify(callState) },  });}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 <code>query<\/code> URL \u2014 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0438 \u043d\u0430\u0434\u0451\u0436\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431. \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u0447\u0435\u0440\u0435\u0437 IPC \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0436\u0434\u0430\u0442\u044c <code>ready-to-show<\/code> \u0438 \u043f\u043e\u0442\u043e\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0441\u043b\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435, \u0447\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 race conditions.<\/p>\n<h3>WebRTC \u0447\u0435\u0440\u0435\u0437 relay: \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0417\u0432\u043e\u043d\u043e\u043a \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432 call window, \u043d\u043e WebSocket \u043a \u0431\u044d\u043a\u0435\u043d\u0434\u0443 \u0436\u0438\u0432\u0451\u0442 \u0432 main window. WebRTC \u0441\u0438\u0433\u043d\u0430\u043b\u044b (offer, answer, ICE candidates) \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0443\u0434\u0430-\u043e\u0431\u0440\u0430\u0442\u043d\u043e. \u041f\u0440\u044f\u043c\u043e\u0439 WebSocket \u0438\u0437 call window \u2014 \u043f\u043b\u043e\u0445\u0430\u044f \u0438\u0434\u0435\u044f: \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0434\u0432\u0430 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0445 WebSocket-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f, \u0433\u043e\u043d\u043a\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439, \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442\u044b \u043f\u0443\u0448\u0435\u0439.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 <strong>relay \u0447\u0435\u0440\u0435\u0437 main process<\/strong>:<\/p>\n<pre><code>[call window] \u2192 IPC \u2192 [main process] \u2192 IPC \u2192 [main window WS] \u2192 server   \u2191                                                              \u2502   \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 IPC \u2190\u2500\u2500\u2500 [main process] \u2190\u2500\u2500\u2500 IPC \u2190\u2500\u2500\u2500 WS message \u2500\u2500\u2500\u2500\u2518<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 call window (renderer):<\/p>\n<pre><code class=\"javascript\">\/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u0438\u0433\u043d\u0430\u043b\u0430window.electronAPI.callWinSendSignal({ callId, signal });\/\/ \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0438\u0433\u043d\u0430\u043b\u0430window.electronAPI.onCallSignal((data) =&gt; {  if (data.type === 'webrtc_answer') pc.setRemoteDescription(data.sdp);  \/\/ ... etc});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 main process:<\/p>\n<pre><code class=\"javascript\">\/\/ Forward signal from call window \u2192 main windowipcMain.on('webrtc-signal', (_, { callId, signal }) =&gt; {  if (mainWindow &amp;&amp; !mainWindow.isDestroyed())    mainWindow.webContents.send('relay-webrtc-signal', { callId, signal });});\/\/ Forward incoming WS message \u2192 call windowipcMain.handle('send-to-call-window', (_, message) =&gt; sendToCallWindowSafe(message));<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0433\u0440\u0430\u0431\u043b\u044f \u2014 <strong>call window \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0435\u0449\u0451 \u043d\u0435 \u0433\u043e\u0442\u043e\u0432<\/strong> \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u043e\u0433\u0434\u0430 \u0443\u0436\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043b\u0430\u0442\u044c <code>webContents.send<\/code>, \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0442\u0435\u0440\u044f\u044e\u0442\u0441\u044f. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 \u0431\u0443\u0444\u0435\u0440\u0438\u0437\u0430\u0446\u0438\u044f:<\/p>\n<pre><code class=\"javascript\">let callWindowReady = false;let callWindowBuffer = [];function sendToCallWindowSafe(message) {  if (!callWindow || callWindow.isDestroyed()) return;  if (callWindowReady) callWindow.webContents.send('call-signal', message);  else callWindowBuffer.push(message);}\/\/ Call window \u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0442 \u043a\u043e\u0433\u0434\u0430 \u0433\u043e\u0442\u043e\u0432 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044fipcMain.on('call-window-ready', () =&gt; {  callWindowReady = true;  if (callWindow &amp;&amp; !callWindow.isDestroyed()) {    for (const msg of callWindowBuffer) callWindow.webContents.send('call-signal', msg);  }  callWindowBuffer = [];});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>call-window-ready<\/code> \u0448\u043b\u0451\u0442\u0441\u044f \u0438\u0437 call window \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u043b\u043d\u043e\u0439 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 UI, \u043d\u0435 \u043f\u043e\u0441\u043b\u0435 <code>ready-to-show<\/code> (\u044d\u0442\u043e \u0440\u0430\u043d\u044c\u0448\u0435). \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0431\u0443\u0444\u0435\u0440 \u0441\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f, \u0438 \u0441\u0432\u044f\u0437\u044c \u0438\u0434\u0451\u0442 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e.<\/p>\n<p>\u042d\u0442\u043e\u0442 \u0431\u0443\u0444\u0435\u0440 \u2014 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u043d. \u0411\u0435\u0437 \u043d\u0435\u0433\u043e ~10% \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u043e\u0431\u0440\u044b\u0432\u0430\u043b\u0438\u0441\u044c \u0431\u044b \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0435, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u0435\u0440\u0432\u044b\u0439 offer \u043e\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u043b \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u0447\u0435\u043c \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440 \u0443\u0441\u043f\u0435\u0432\u0430\u043b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a.<\/p>\n<h3>Deep links \u043d\u0430 \u0442\u0440\u0451\u0445 \u041e\u0421 \u2014 \u0442\u0440\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u043e\u0434\u0445\u043e\u0434\u0430<\/h3>\n<p>\u0414\u0435\u0441\u043a\u0442\u043e\u043f-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0430\u043c \u0438\u0437 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430: <code>onemixdesktop:\/\/chat\/abc123<\/code> \u0438\u043b\u0438 \u0438\u0437 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 <a href=\"http:\/\/itpaxlive.ru\" rel=\"noopener noreferrer nofollow\">itpaxlive.ru<\/a>.<\/p>\n<p>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u0430\u044f \u0432\u0435\u0437\u0434\u0435:<\/p>\n<pre><code class=\"javascript\">if (process.defaultApp) {  if (process.argv.length &gt;= 2) {    app.setAsDefaultProtocolClient('onemixdesktop', process.execPath, [path.resolve(process.argv[1])]);  }} else {  app.setAsDefaultProtocolClient('onemixdesktop');}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0410 \u0432\u043e\u0442 <strong>\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430<\/strong> \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u041e\u0421 \u0440\u0430\u0437\u043d\u0430\u044f.<\/p>\n<p><strong>macOS:<\/strong> \u0435\u0441\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 event <code>open-url<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0448\u043b\u0451\u0442 URL \u043a\u043e\u0433\u0434\u0430 \u044e\u0437\u0435\u0440 \u043a\u043b\u0438\u043a\u0430\u0435\u0442 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 <code>onemixdesktop:\/\/...<\/code>. \u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e \u0438\u043b\u0438 \u043d\u0435\u0442 \u2014 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u0442\u0441\u044f.<\/p>\n<pre><code class=\"javascript\">app.on('open-url', (event, url) =&gt; {  event.preventDefault();  handleDeepLinkUrl(url);});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>Windows\/Linux:<\/strong> event <code>open-url<\/code> \u0442\u0443\u0442 \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u041a\u043e\u0433\u0434\u0430 \u044e\u0437\u0435\u0440 \u043a\u043b\u0438\u043a\u0430\u0435\u0442 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435, \u041e\u0421 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043d\u043e\u0432\u043e \u0441 URL \u0432 <code>process.argv<\/code>. \u0415\u0441\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e, \u0432\u0442\u043e\u0440\u043e\u0439 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0441\u044f \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u2014 \u043d\u0443\u0436\u043d\u0430 \u0437\u0430\u0449\u0438\u0442\u0430.<\/p>\n<pre><code class=\"javascript\">const gotSingleLock = app.requestSingleInstanceLock();if (!gotSingleLock) {  app.quit();  \/\/ \u0423\u0436\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u0438\u0433\u043d\u0430\u043b} else {  app.on('second-instance', (event, argv) =&gt; {    const url = argv.find(a =&gt; a.startsWith('onemixdesktop:\/\/'));    if (url) handleDeepLinkUrl(url);    if (mainWindow) { mainWindow.show(); mainWindow.focus(); }  });}\/\/ \u041f\u0440\u0438 cold start \u2014 URL \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0432 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u043c argvconst launchUrl = process.argv.find(a =&gt; a.startsWith('onemixdesktop:\/\/'));if (launchUrl) _pendingDeepLink = launchUrl;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0441\u043b\u043e\u0439 \u2014 <strong>\u043e\u043a\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0435\u0449\u0451 \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u043e<\/strong> \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0448\u0451\u043b deep link:<\/p>\n<pre><code class=\"javascript\">let _pendingDeepLink = null;function handleDeepLinkUrl(url) {  if (mainWindow &amp;&amp; !mainWindow.isDestroyed()) {    mainWindow.show();    mainWindow.focus();    mainWindow.webContents.send('deep-link', url);  } else {    _pendingDeepLink = url;  }}\/\/ \u0412 createWindow \u2192 ready-to-showmainWindow.once('ready-to-show', () =&gt; {  mainWindow.show();  if (_pendingDeepLink) {    setTimeout(() =&gt; {      mainWindow.webContents.send('deep-link', _pendingDeepLink);      _pendingDeepLink = null;    }, 1500); \/\/ 1.5s \u2014 \u0434\u0430\u0451\u043c \u0432\u0440\u0435\u043c\u044f \u043d\u0430 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440\u0430  }});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0440\u0438 \u041e\u0421, \u0442\u0440\u0438 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438 \u2014 \u0438 \u0432\u0441\u0435 \u043e\u043d\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e, \u0438\u043d\u0430\u0447\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u043a\u0430\u043a\u043e\u043c-\u0442\u043e \u0438\u0437 \u043d\u0438\u0445 \u043f\u043e\u043b\u043e\u043c\u0430\u044e\u0442\u0441\u044f.<\/p>\n<h3>\u041f\u0435\u0440\u0435\u0445\u0432\u0430\u0442 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 \u0438 web requests<\/h3>\n<p>\u0418\u0437 <code>file:\/\/<\/code> (\u043e\u0442\u043a\u0443\u0434\u0430 \u0433\u0440\u0443\u0437\u0438\u0442\u0441\u044f HTML) \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043a <a href=\"https:\/\/itpaxlive.ru\/*\" rel=\"noopener noreferrer nofollow\"><code>https:\/\/itpaxlive.ru\/*<\/code><\/a> \u0438\u0434\u0443\u0442 \u043a\u0430\u043a cross-origin. WebSocket \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043d\u043e <code>&lt;img src=\"<\/code><a href=\"https:\/\/itpaxlive.ru\/avatar.jpg\" rel=\"noopener noreferrer nofollow\"><code>https:\/\/itpaxlive.ru\/avatar.jpg<\/code><\/a><code>\"&gt;<\/code> \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u0434\u0442\u0438 \u0441 Authorization-\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443 \u043d\u0430\u0441 \u0432 localStorage.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 middleware \u043d\u0430 webRequest:<\/p>\n<pre><code class=\"javascript\">mainWindow.webContents.session.webRequest.onBeforeSendHeaders(  { urls: ['https:\/\/itpaxlive.ru\/*'] },  (details, callback) =&gt; {    const sessionData = _readSessionFile();    const token = sessionData?.token;    const headers = { ...details.requestHeaders };    if (token &amp;&amp; !headers['Authorization']) {      headers['Authorization'] = `Bearer ${token}`;    }    callback({ requestHeaders: headers });  });<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>_readSessionFile()<\/code> \u0447\u0438\u0442\u0430\u0435\u0442 \u0442\u043e\u043a\u0435\u043d \u0438\u0437 <code>app.getPath('userData')\/session.json<\/code> \u2014 \u044d\u0442\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0435\u0435 \u0447\u0435\u043c localStorage, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0435 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u0441\u044f \u043f\u0440\u0438 clearing browser data.<\/p>\n<p>\u0417\u0430\u043e\u0434\u043d\u043e \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044e \u2014 \u0435\u0441\u043b\u0438 \u044e\u0437\u0435\u0440 \u043a\u043b\u0438\u043a\u0430\u0435\u0442 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 <a href=\"https:\/\/itpaxlive.ru\/chat\/abc\" rel=\"noopener noreferrer nofollow\"><code>https:\/\/itpaxlive.ru\/chat\/abc<\/code><\/a>, \u043d\u0435 \u0443\u0432\u043e\u0434\u0438\u043c \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043f\u0440\u043e\u0447\u044c, \u0430 \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0432 deep link:<\/p>\n<pre><code class=\"javascript\">mainWindow.webContents.on('will-navigate', (event, url) =&gt; {  if (url.startsWith('file:\/\/')) return;  try {    const u = new URL(url);    if (u.hostname.includes('itpaxlive')) {      event.preventDefault();      const deepUrl = 'onemixdesktop:\/' + u.pathname;      mainWindow.webContents.send('deep-link', deepUrl);      return;    }  } catch {}  if (url.startsWith('onemixdesktop:\/\/')) {    event.preventDefault();    mainWindow.webContents.send('deep-link', url);    return;  }  \/\/ \u0412\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u2014 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435  event.preventDefault();  shell.openExternal(url);});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0411\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u044e\u0437\u0435\u0440 \u043a\u043b\u0438\u043a\u0430\u0435\u0442 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 &#171;\u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043f\u0440\u043e\u0444\u0438\u043b\u044c&#187; \u0438 \u0442\u0435\u0440\u044f\u0435\u0442 \u0441\u0432\u043e\u0451 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u2014 <code>file:\/\/<\/code> \u0443\u0445\u043e\u0434\u0438\u0442 \u0438 \u0433\u0440\u0443\u0437\u0438\u0442\u0441\u044f <a href=\"https:\/\/itpaxlive.ru\/\" rel=\"noopener noreferrer nofollow\"><code>https:\/\/itpaxlive.ru\/<\/code><\/a><code>...<\/code>. \u0412\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0432 \u0434\u0435\u0441\u043a\u0442\u043e\u043f \u0443\u0436\u0435 \u043d\u0435\u0442.<\/p>\n<h3>Tray-first: close \u043d\u0435 \u0432\u044b\u0445\u043e\u0434\u0438\u0442 \u0438\u0437 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/h3>\n<p>\u041b\u044e\u0431\u043e\u0439 \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440 \u043d\u0430 \u0434\u0435\u0441\u043a\u0442\u043e\u043f\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0436\u0438\u0442\u044c \u0432 tray. Closing window \u2014 \u044d\u0442\u043e \u043d\u0435 quit, \u044d\u0442\u043e hide. Quit \u0431\u044b\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u044f\u0432\u043d\u043e\u043c \u0432\u044b\u0431\u043e\u0440\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (&#171;\u0412\u044b\u0445\u043e\u0434&#187; \u0432 \u043c\u0435\u043d\u044e tray).<\/p>\n<pre><code class=\"javascript\">mainWindow.on('close', (e) =&gt; {  if (!isQuitting) {    e.preventDefault();    mainWindow.hide();    if (!mainWindow._trayHintShown &amp;&amp; tray) {      mainWindow._trayHintShown = true;      if (process.platform === 'win32') {        tray.displayBalloon({          title: 'OneMix \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u0444\u043e\u043d\u0435',          content: '\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043d\u0430 \u0438\u043a\u043e\u043d\u043a\u0443 \u0432 \u0442\u0440\u0435\u0435 \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c.',          icon: TRAY_ICON_PATH,        });      }    }  } else {    mainWindow = null;  }});app.on('window-all-closed', () =&gt; {  if (isQuitting) app.quit();  \/\/ \u0418\u043d\u0430\u0447\u0435 \u043d\u0435 \u0432\u044b\u0445\u043e\u0434\u0438\u043c \u2014 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0436\u0438\u0432\u0451\u0442 \u0432 tray});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>Balloon-\u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f <strong>\u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u0440\u0430\u0437<\/strong> \u2014 <code>_trayHintShown<\/code> \u0444\u043b\u0430\u0433. \u0418\u043d\u0430\u0447\u0435 \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0438 \u043e\u043a\u043d\u0430 \u044e\u0437\u0435\u0440 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0434\u0435\u0442\u044c \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0443, \u0447\u0442\u043e \u0440\u0430\u0437\u0434\u0440\u0430\u0436\u0430\u0435\u0442.<\/p>\n<p>Tray-\u0438\u043a\u043e\u043d\u043a\u0430 \u0432\u0435\u0434\u0451\u0442 \u0441\u0435\u0431\u044f \u043a\u0430\u043a \u0432 Telegram Desktop: \u043b\u0435\u0432\u044b\u0439 \u043a\u043b\u0438\u043a \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\/\u0441\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043e\u043a\u043d\u043e, \u043f\u0440\u0430\u0432\u044b\u0439 \u2014 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435 \u043c\u0435\u043d\u044e. \u041c\u0435\u043d\u044e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043d\u044b\u0445:<\/p>\n<pre><code class=\"javascript\">function buildTrayMenu(unread = 0) {  const label = unread &gt; 0 ? `OneMix (${unread} \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043d\u044b\u0445)` : 'OneMix';  const menu = Menu.buildFromTemplate([    { label, enabled: false },    { type: 'separator' },    { label: '\u041e\u0442\u043a\u0440\u044b\u0442\u044c OneMix', click: () =&gt; { mainWindow.show(); mainWindow.focus(); } },    { type: 'separator' },    { label: '\u0412\u044b\u0445\u043e\u0434', click: () =&gt; { isQuitting = true; app.quit(); } },  ]);  if (tray) tray.setContextMenu(menu);}ipcMain.on('set-badge', (_, count) =&gt; {  if (process.platform === 'darwin') app.dock?.setBadge(count &gt; 0 ? String(count) : '');  buildTrayMenu(count);  if (tray) tray.setToolTip(count &gt; 0 ? `OneMix \u2014 ${count} \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043d\u044b\u0445` : 'OneMix Messenger');});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430 macOS \u2014 dock badge. \u041d\u0430 Windows\/Linux \u2014 tray tooltip + \u0438\u0437\u043c\u0435\u043d\u0451\u043d\u043d\u043e\u0435 \u043c\u0435\u043d\u044e. \u041f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0443\u043d\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043e\u0434\u043d\u0438\u043c IPC-\u0432\u044b\u0437\u043e\u0432\u043e\u043c \u0438\u0437 renderer&#8217;\u0430: <code>electronAPI.setBadge(7)<\/code>.<\/p>\n<h3>Auto-updater \u0441 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0435\u0439 silent \u043e\u0448\u0438\u0431\u043e\u043a<\/h3>\n<p><code>electron-updater<\/code> \u2014 \u044d\u0442\u043e must-have \u0434\u043b\u044f \u0434\u0435\u0441\u043a\u0442\u043e\u043f-\u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043d\u0435 \u043b\u044e\u0431\u044f\u0442 \u0441\u0430\u043c\u0438 \u0445\u043e\u0434\u0438\u0442\u044c \u0437\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f\u043c\u0438.<\/p>\n<p>\u0411\u0430\u0437\u043e\u0432\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f:<\/p>\n<pre><code class=\"javascript\">let autoUpdater = null;try {  const eu = require('electron-updater');  autoUpdater = eu.autoUpdater;  autoUpdater.logger = null;  autoUpdater.autoDownload = false;       \/\/ \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u044e\u0437\u0435\u0440\u0430, \u043f\u043e\u0442\u043e\u043c \u043a\u0430\u0447\u0430\u0435\u043c  autoUpdater.autoInstallOnAppQuit = true;} catch (e) {  \/\/ electron-updater not installed (dev environment)}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>try\/catch<\/code> \u0432\u043e\u043a\u0440\u0443\u0433 require \u2014 \u044d\u0442\u043e \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e \u0434\u043b\u044f dev-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f. \u0412 dev \u043c\u044b \u043d\u0435 \u0445\u043e\u0442\u0438\u043c \u0442\u0430\u0449\u0438\u0442\u044c \u0442\u044f\u0436\u0451\u043b\u0443\u044e \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u0438 \u043d\u0435 \u0445\u043e\u0442\u0438\u043c \u0447\u0442\u043e\u0431\u044b updater \u043f\u044b\u0442\u0430\u043b\u0441\u044f \u0438\u0441\u043a\u0430\u0442\u044c \u0440\u0435\u043b\u0438\u0437\u044b.<\/p>\n<p>\u0421\u0430\u043c \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 \u2014 generic-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0441 \u043c\u043e\u0438\u043c \u0431\u044d\u043a\u0435\u043d\u0434\u043e\u043c:<\/p>\n<pre><code class=\"json\">\"publish\": [  {    \"provider\": \"generic\",    \"url\": \"https:\/\/onemix.me\/updates\/onemix\",    \"channel\": \"latest\"  }]<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430 \u0431\u044d\u043a\u0435\u043d\u0434\u0435 \u0440\u0430\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0442\u0440\u0438 \u0444\u0430\u0439\u043b\u0430: <code>latest.yml<\/code> \u0441 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u043c\u0438, <code>OneMix-Setup-1.2.0.exe<\/code> (Windows), <code>OneMix-1.2.0.dmg<\/code> (macOS), <code>OneMix-1.2.0.AppImage<\/code> (Linux). <code>electron-builder<\/code> \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u044d\u0442\u0438 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.<\/p>\n<p>\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0433\u0440\u0430\u0431\u043b\u044f auto-updater&#8217;\u0430 \u2014 <strong>silent errors<\/strong>. \u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043b\u044e\u0431\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u043a\u0430\u043a \u0430\u043b\u0435\u0440\u0442. \u041d\u043e 404 \u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 (\u0432\u044b\u0448\u0435\u043b \u0438\u0437 \u0441\u0442\u0440\u043e\u044f, \u043d\u0435 \u0437\u0430\u043b\u0438\u0442 \u0435\u0449\u0451), <code>ENOTFOUND<\/code> (\u043d\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430), <code>ECONNREFUSED<\/code> (\u0444\u0430\u0435\u0440\u0432\u043e\u043b) \u2014 \u044d\u0442\u043e \u043d\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044e\u0437\u0435\u0440\u0443 \u043d\u0443\u0436\u043d\u043e \u0432\u0438\u0434\u0435\u0442\u044c. \u042e\u0437\u0435\u0440 \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u0438\u0434\u0435\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b: &#171;\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e, \u043d\u043e \u043d\u0435 \u043a\u0430\u0447\u0430\u0435\u0442\u0441\u044f&#187;, &#171;\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0432\u0440\u0435\u0436\u0434\u0435\u043d\u043e&#187;.<\/p>\n<pre><code class=\"javascript\">autoUpdater.on('error', (err) =&gt; {  const msg = err.message || '';  const isSilent =    msg.includes('404') ||    msg.includes('ENOTFOUND') ||    msg.includes('ECONNREFUSED') ||    msg.includes('ETIMEDOUT') ||    msg.includes('net::ERR') ||    msg.includes('getaddrinfo');  if (isSilent) {    console.log('[updater] silenced error:', msg);    return;  \/\/ \u043d\u0435 \u0431\u0435\u0441\u043f\u043e\u043a\u043e\u0438\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f  }  mainWindow.webContents.send('update-error', msg);});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0442\u043e\u043d\u043a\u043e\u0435 \u043c\u0435\u0441\u0442\u043e \u2014 <code>quitAndInstall()<\/code> \u043d\u0430 Windows. NSIS-\u0438\u043d\u0441\u0442\u0430\u043b\u043b\u0435\u0440 \u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043d\u043e \u0435\u0441\u043b\u0438 \u044d\u0442\u0438 \u0444\u0430\u0439\u043b\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044b (\u0430 \u043e\u043d\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u044b \u2014 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e), Windows \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u0442 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044e.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 <strong>\u0443\u043d\u0438\u0447\u0442\u043e\u0436\u0438\u0442\u044c \u0432\u0441\u0435 \u043e\u043a\u043d\u0430<\/strong> \u043f\u0435\u0440\u0435\u0434 quitAndInstall, \u0434\u0430\u0442\u044c Windows \u0441\u0435\u043a\u0443\u043d\u0434\u0443 \u043e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0442\u044c handles, \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0442\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c installer:<\/p>\n<pre><code class=\"javascript\">ipcMain.on('update-install-now', () =&gt; {  if (autoUpdater) {    isQuitting = true;    \/\/ \u0423\u043d\u0438\u0447\u0442\u043e\u0436\u0430\u0435\u043c \u0442\u0440\u0435\u0439 \u0438 \u0432\u0441\u0435 \u043e\u043a\u043d\u0430 \u2014 Windows \u043e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0442 file handles    try { if (tray) { tray.destroy(); tray = null; } } catch {}    try { if (mainWindow &amp;&amp; !mainWindow.isDestroyed()) mainWindow.destroy(); } catch {}    try { if (settingsWindow &amp;&amp; !settingsWindow.isDestroyed()) settingsWindow.destroy(); } catch {}    setTimeout(() =&gt; {      autoUpdater.quitAndInstall(false, true);    }, 500);  }});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>500\u043c\u0441 \u2014 \u044d\u043c\u043f\u0438\u0440\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0434\u043e\u0431\u0440\u0430\u043d\u043d\u043e\u0435 \u0447\u0438\u0441\u043b\u043e. \u041c\u0435\u043d\u044c\u0448\u0435 \u2014 \u0438\u043d\u043e\u0433\u0434\u0430 NSIS \u043f\u0430\u0434\u0430\u0435\u0442 \u0441 &#171;\u0444\u0430\u0439\u043b \u0437\u0430\u043d\u044f\u0442&#187;. \u0411\u043e\u043b\u044c\u0448\u0435 \u2014 \u044e\u0437\u0435\u0440 \u0443\u0441\u043f\u0435\u0432\u0430\u0435\u0442 \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c \u0447\u0442\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043a\u0440\u044b\u043b\u043e\u0441\u044c \u043f\u0435\u0440\u0435\u0434 \u0430\u043f\u0434\u0435\u0439\u0442\u043e\u043c.<\/p>\n<h3>\u0427\u0442\u043e \u0431\u044b \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u043f\u043e-\u0434\u0440\u0443\u0433\u043e\u043c\u0443<\/h3>\n<p><strong>Code signing \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043d\u0430\u0447\u0430\u043b\u0430.<\/strong> \u042f \u0434\u043e\u043b\u0433\u043e \u043e\u0442\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u043b \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0431\u0438\u043b\u0434\u043e\u0432 \u0434\u043b\u044f Windows (\u043d\u0443\u0436\u0435\u043d EV-\u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442, ~$300-500\/\u0433\u043e\u0434). \u0411\u0435\u0437 \u043f\u043e\u0434\u043f\u0438\u0441\u0438 Windows SmartScreen \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0441\u0442\u0440\u0430\u0448\u043d\u043e\u0435 \u043e\u043a\u043d\u043e &#171;\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0438\u0437 \u043d\u0435\u043d\u0430\u0434\u0451\u0436\u043d\u043e\u0433\u043e \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430&#187;, \u0438 \u0447\u0430\u0441\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442. \u041d\u0430 macOS \u0431\u0435\u0437 \u043f\u043e\u0434\u043f\u0438\u0441\u0438 Apple Notarization \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435 \u043d\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0441\u044f. \u042d\u0442\u043e \u043e\u0433\u0440\u043e\u043c\u043d\u044b\u0439 \u043a\u043e\u043d\u0432\u0435\u0440\u0441\u0438\u043e\u043d\u043d\u044b\u0439 leak, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043b \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0434\u043e\u043b\u0433\u043e.<\/p>\n<p><strong>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043a\u043e\u0434\u0430 \u043f\u043e\u0434 TypeScript.<\/strong> vanilla JS \u0432 main.js \u043a\u043e\u0433\u0434\u0430 \u0444\u0430\u0439\u043b \u0434\u043e\u0441\u0442\u0438\u0433 800 \u0441\u0442\u0440\u043e\u043a \u2014 \u044d\u0442\u043e \u0443\u0436\u0435 \u0441\u043b\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c. Refactor \u043d\u0430 TypeScript \u0441 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 IPC-\u043a\u0430\u043d\u0430\u043b\u0430\u043c\u0438 \u2014 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0448\u0430\u0433. \u0421\u0435\u0439\u0447\u0430\u0441 IPC channel name \u2014 \u044d\u0442\u043e \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430, \u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0438 \u043b\u043e\u0432\u044f\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 runtime.<\/p>\n<p><strong>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <\/strong><a href=\"https:\/\/github.com\/sindresorhus\/electron-store\" rel=\"noopener noreferrer nofollow\"><strong>electron-store<\/strong><\/a><strong> \u0434\u043b\u044f session storage.<\/strong> \u0423 \u043c\u0435\u043d\u044f \u0441\u0432\u043e\u0439 <code><em>readSessionFile<\/em><\/code><em> \/ <\/em><code>writeSessionFile<\/code> \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u044f\u043c\u043e\u0439 <code>fs<\/code>. \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043d\u043e \u043d\u0435 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e \u2014 \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u0430 \u043f\u043e\u0442\u0435\u0440\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0440\u0438 \u0441\u0431\u043e\u0435 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u043f\u0438\u0441\u0438. electron-store \u0434\u0430\u0451\u0442 atomic writes \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438.<\/p>\n<p><strong>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 Linux \u0440\u0430\u043d\u044c\u0448\u0435.<\/strong> \u042f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043b \u043d\u0430 macOS \u0438 Windows, Linux \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c. \u0418 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b \u0447\u0442\u043e \u043d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 GTK-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\u0445 tray-\u0438\u043a\u043e\u043d\u043a\u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f (\u0441\u0442\u0430\u0440\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 Electron \u043d\u0430 Linux). \u0415\u0441\u043b\u0438 \u0431\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043b \u0440\u0430\u043d\u044c\u0448\u0435 \u2014 \u043c\u043e\u0433 \u0431\u044b \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, libappindicator).<\/p>\n<h3>\u0418\u0442\u043e\u0433<\/h3>\n<p>Vanilla Electron \u0434\u043b\u044f \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c. \u041d\u0435 \u0441\u0430\u043c\u044b\u043c \u043c\u043e\u0434\u043d\u044b\u043c, \u043d\u043e \u0441\u0430\u043c\u044b\u043c <strong>\u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u043c \u043f\u043e\u0434 \u0437\u0430\u0434\u0430\u0447\u0443<\/strong>. \u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0432\u044b\u0438\u0433\u0440\u044b\u0448 \u2014 \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u0430: 27 \u0441\u0442\u0440\u043e\u043a \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0432\u043c\u0435\u0441\u0442\u043e 200, \u043d\u0435\u0442 \u0441\u0431\u043e\u0440\u0449\u0438\u043a\u0430, \u0434\u0435\u0432-\u0446\u0438\u043a\u043b <code>electron .<\/code> \u0431\u0435\u0437 watcher&#8217;\u043e\u0432.<\/p>\n<p>\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u0440\u043e\u0438\u0433\u0440\u044b\u0448 \u2014 \u043f\u043e\u0442\u043e\u043b\u043e\u043a \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438. \u041a\u043e\u0433\u0434\u0430 \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440 \u0432\u044b\u0440\u0430\u0441\u0442\u0435\u0442 \u0434\u043e \u0443\u0440\u043e\u0432\u043d\u044f Telegram Desktop \u0441 \u043c\u0435\u0434\u0438\u0430-\u0432\u044c\u044e\u0435\u0440\u043e\u043c, \u0432\u0438\u0434\u0435\u043e-\u043f\u043b\u0435\u0435\u0440\u043e\u043c, advanced \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438 \u2014 vanilla \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u0435\u0442 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0434\u0451\u0442 \u0432\u0440\u0435\u043c\u044f \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u043d\u0430 TypeScript + \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a. \u041d\u043e \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0442\u043e\u0433\u0434\u0430, \u043d\u0435 \u0441\u0435\u0439\u0447\u0430\u0441.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0434\u0435\u043b\u0430\u0435\u0442\u0435 \u0434\u0435\u0441\u043a\u0442\u043e\u043f-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438 \u0434\u0443\u043c\u0430\u0435\u0442\u0435 &#171;\u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435 \u043d\u0430\u0434\u043e React&#187; \u2014 \u0437\u0430\u0434\u0430\u0439\u0442\u0435 \u0441\u0435\u0431\u0435 \u0432\u043e\u043f\u0440\u043e\u0441: <strong>\u0447\u0442\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e \u0443 \u0432\u0430\u0441 \u0432 UI?<\/strong> \u0415\u0441\u043b\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u043d\u0435\u0442 \u2014 vanilla \u0434\u0430\u0441\u0442 \u0432\u0430\u043c \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0443 \u0440\u0430\u0431\u043e\u0442\u044b \u0432 \u043a\u0430\u0440\u043c\u0430\u043d.<\/p>\n<hr\/>\n<p>\u042d\u0442\u043e \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0438\u0437 \u0441\u0435\u0440\u0438\u0438 \u043f\u0440\u043e ONEMIX. \u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445: <a href=\"#\" rel=\"noopener noreferrer nofollow\">\u0442\u0440\u0451\u0445\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 \u043a\u044d\u0448<\/a>, <a href=\"#\" rel=\"noopener noreferrer nofollow\">Double Ratchet E2E<\/a>, <a href=\"#\" rel=\"noopener noreferrer nofollow\">WebRTC \u0437\u0432\u043e\u043d\u043a\u0438<\/a>. \u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u2014 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0439 \u0432\u043e\u043f\u0440\u043e\u0441, \u0435\u0441\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432. \u0415\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0430 \u043a\u0430\u043a\u0430\u044f-\u0442\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0430\u044f \u0442\u0435\u043c\u0430 \u2014 \u043d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445, \u0432\u044b\u0431\u0435\u0440\u0443 \u043f\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u043f\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u043a\u0443\u0441\u043a\u0430\u043c \u043a\u043e\u0434\u0430, IPC-\u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435 \u0438\u043b\u0438 auto-updater&#8217;\u0443 \u2014 \u043f\u0438\u0448\u0438\u0442\u0435. \u041d\u0430 \u0441\u0430\u043c\u044b\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u0433\u043e\u0442\u043e\u0432 \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u0440\u0430\u0437\u0432\u0451\u0440\u043d\u0443\u0442\u043e.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1034184\/\">https:\/\/habr.com\/ru\/articles\/1034184\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0423\u0440\u043e\u0432\u0435\u043d\u044c: middle\/senior, \u043a\u0440\u043e\u0441\u0441-\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435\u043d\u043d\u0430\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0421\u0442\u0435\u043a: Electron 28, electron-builder, electron-updater, vanilla HTML\/JS \u0427\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438: \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f, IPC \u043c\u0435\u0436\u0434\u0443 \u043e\u043a\u043d\u0430\u043c\u0438, deep links \u043d\u0430 \u0442\u0440\u0451\u0445 \u041e\u0421, tray-first \u043f\u0430\u0442\u0442\u0435\u0440\u043d, auto-updater grace, custom \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u044b\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u042d\u0442\u043e \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0438\u0437 \u0441\u0435\u0440\u0438\u0438 \u043f\u0440\u043e \u0438\u043d\u0436\u0435\u043d\u0435\u0440\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0432 ONEMIX \u2014 \u043c\u043e\u0451\u043c \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0435 \u043d\u0430 React Native. \u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b \u0442\u0440\u0451\u0445\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 \u043a\u044d\u0448 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, Double Ratchet E2E \u0438 WebRTC \u0437\u0432\u043e\u043d\u043a\u0438 \u0441 trickle ICE. \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u043f\u0440\u043e \u0437\u0432\u043e\u043d\u043a\u0438 \u043d\u0430\u0431\u0440\u0430\u043b\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u043e\u0432, \u0438 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437 \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u043b\u0438 \u043f\u0440\u043e \u0434\u0435\u0441\u043a\u0442\u043e\u043f: &#171;\u0430 \u043a\u0430\u043a \u0443 \u0442\u0435\u0431\u044f \u0442\u0430\u043c \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u043e?&#187;.\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u0440\u043e desktop-\u0432\u0435\u0440\u0441\u0438\u044e. \u0421\u0440\u0430\u0437\u0443 \u0441\u043a\u0430\u0436\u0443: \u044f \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b React Native for Desktop, \u043d\u0435 Tauri, \u043d\u0435 React, \u043d\u0435 TypeScript. \u0427\u0438\u0441\u0442\u044b\u0439 Electron + vanilla HTML\/JS. \u042d\u0442\u043e \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0438 \u044f \u043e\u0431\u044a\u044f\u0441\u043d\u044e \u043f\u043e\u0447\u0435\u043c\u0443 \u043f\u043e\u0448\u0451\u043b \u044d\u0442\u0438\u043c \u043f\u0443\u0442\u0451\u043c, \u0447\u0442\u043e \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u0432\u044b\u0438\u0433\u0440\u0430\u043b, \u0438 \u0433\u0434\u0435 \u044d\u0442\u043e \u0431\u044c\u0451\u0442 \u043f\u043e \u0433\u043e\u043b\u043e\u0432\u0435.\u041f\u043e\u0447\u0435\u043c\u0443 vanilla Electron, \u0430 \u043d\u0435 RN-Desktop\u041a\u043e\u0433\u0434\u0430 \u044f \u043d\u0430\u0447\u0438\u043d\u0430\u043b \u0434\u0435\u043b\u0430\u0442\u044c \u0434\u0435\u0441\u043a\u0442\u043e\u043f, \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u043b \u0447\u0435\u0442\u044b\u0440\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430:React Native for Windows + macOS. \u042d\u0442\u043e \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 Microsoft \u0444\u043e\u0440\u043a RN \u0434\u043b\u044f Windows \u0438 \u0441\u0442\u0430\u0440\u044b\u0439 Facebook-\u0444\u043e\u0440\u043a \u0434\u043b\u044f macOS. \u0418\u0434\u0435\u044f \u0437\u0430\u043c\u0430\u043d\u0447\u0438\u0432\u0430\u044f \u2014 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u044c \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434. \u041d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0443 \u043c\u0435\u043d\u044f \u0431\u044b\u043b\u043e \u0434\u0432\u0430 \u0431\u043b\u043e\u043a\u0435\u0440\u0430. \u041f\u0435\u0440\u0432\u043e\u0435: \u043e\u0431\u0430 \u043f\u043e\u0440\u0442\u0430 \u0443\u0436\u0430\u0441\u043d\u043e \u043e\u0442\u0441\u0442\u0430\u044e\u0442 \u043e\u0442 mainline RN, \u043c\u043d\u043e\u0433\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 (react-native-reanimated, react-native-svg, expo-secure-store) \u043b\u0438\u0431\u043e \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f, \u043b\u0438\u0431\u043e \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u043c\u0443. \u0412\u0442\u043e\u0440\u043e\u0435: Linux-\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043d\u0435\u0442 \u0432 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435. \u0410 Linux \u044f \u0445\u043e\u0442\u0435\u043b.Tauri. \u0421\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439, \u043b\u0451\u0433\u043a\u0438\u0439, \u043d\u0430 Rust. \u042f \u0441\u0435\u0440\u044c\u0451\u0437\u043d\u043e \u0435\u0433\u043e \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u043b \u0438 \u0434\u0430\u0436\u0435 \u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b. \u041c\u0438\u043d\u0443\u0441 \u043e\u0434\u0438\u043d, \u043d\u043e \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u044b\u0439: WebView \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u041e\u0421 \u0440\u0430\u0437\u043d\u044b\u0439 (Edge WebView2 \u043d\u0430 Windows, WebKit \u043d\u0430 macOS, WebKitGTK \u043d\u0430 Linux). \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u0447\u0442\u043e \u0443\u0441\u043b\u043e\u0432\u043d\u044b\u0439 CSS Grid \u0443 \u0442\u0435\u0431\u044f \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 Windows, \u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f \u043d\u0430 Linux, \u0438 \u043f\u043e\u0434\u0432\u0438\u0441\u0430\u0435\u0442 \u043d\u0430 macOS. \u041e\u0442\u043b\u0430\u0436\u0438\u0432\u0430\u0442\u044c \u043c\u0435\u0436\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435\u043d\u043d\u044b\u0435 \u0431\u0430\u0433\u0438 \u0432 Tauri \u2014 \u044d\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0436\u0430\u043d\u0440 \u0441\u0442\u0440\u0430\u0434\u0430\u043d\u0438\u0439. \u0423 Electron \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c Chromium, \u0432\u0435\u0437\u0434\u0435 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439, \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433 \u043f\u0440\u0435\u0434\u0441\u043a\u0430\u0437\u0443\u0435\u043c\u044b\u0439.Electron + React (\u043a\u0430\u043a \u0434\u0435\u043b\u0430\u0435\u0442 Discord, Slack, WhatsApp Desktop). \u042d\u0442\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c. \u042f \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u043f\u043e \u043e\u0434\u043d\u043e\u0439 \u043f\u0440\u0438\u0447\u0438\u043d\u0435 \u2014 \u043f\u0435\u0440\u0435\u0443\u0441\u043b\u043e\u0436\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043c\u043e\u0438\u0445 \u0437\u0430\u0434\u0430\u0447. \u0423 \u043c\u0435\u043d\u044f \u043d\u0435\u0442 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0447\u0430\u0442\u043e\u0432 \u0438 \u0441\u043f\u0438\u0441\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u041d\u0435\u0442 state-\u043c\u0435\u043d\u0435\u0434\u0436\u043c\u0435\u043d\u0442\u0430 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 WebSocket + localStorage. \u0420\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u0431\u044d\u043a\u0435\u043d\u0434\u0435. \u0413\u043e\u0440\u043e\u0434\u0438\u0442\u044c webpack + babel + React + TypeScript \u0440\u0430\u0434\u0438 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 \u0441\u043f\u0438\u0441\u043a\u0430 \u0447\u0430\u0442\u043e\u0432 \u2014 \u044d\u0442\u043e \u0432\u0435\u0441 \u0440\u0430\u0434\u0438 \u0432\u0435\u0441\u0430. \u041d\u0430 vanilla \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0432 5 \u0440\u0430\u0437 \u043c\u0435\u043d\u044c\u0448\u0435 \u0431\u0438\u043b\u0434-\u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u0438 \u0432 3 \u0440\u0430\u0437\u0430 \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430.Vanilla Electron + HTML\/JS. \u0422\u043e \u0447\u0442\u043e \u044f \u0432 \u0438\u0442\u043e\u0433\u0435 \u0432\u044b\u0431\u0440\u0430\u043b. \u041e\u0434\u0438\u043d main.js \u0441 main process. \u041e\u0434\u0438\u043d preload.js. \u041f\u044f\u0442\u044c HTML \u0444\u0430\u0439\u043b\u043e\u0432 (index, call, settings, join, share-group). \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0441\u0431\u043e\u0440\u0449\u0438\u043a\u0430. electron . \u2014 \u0438 \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.\u0415\u0441\u043b\u0438 \u0432\u0430\u0448 \u0434\u0435\u0441\u043a\u0442\u043e\u043f \u2014 \u044d\u0442\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u044d\u043a\u0440\u0430\u043d\u043e\u0432, \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 reactivity \u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043a\u043e\u0434\u043e\u0432\u043e\u0439 \u0431\u0430\u0437\u043e\u0439, vanilla \u043d\u0435 \u043f\u043e\u0434\u043e\u0439\u0434\u0451\u0442. \u0411\u0435\u0440\u0438\u0442\u0435 React\/Vue\/Solid. \u041d\u043e \u0434\u043b\u044f \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430 \u0433\u0434\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0441\u043e\u0441\u0440\u0435\u0434\u043e\u0442\u043e\u0447\u0435\u043d\u0430 \u0432 \u0431\u044d\u043a\u0435\u043d\u0434\u0435 \u2014 \u044d\u0442\u043e \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c. \u0423 \u043c\u0435\u043d\u044f package.json \u0432 \u0434\u0435\u0441\u043a\u0442\u043e\u043f-\u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u2014 \u044d\u0442\u043e 27 \u0441\u0442\u0440\u043e\u043a \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 (\u0432\u043a\u043b\u044e\u0447\u0430\u044f electron-builder \u0438 electron-updater). \u0421\u0431\u043e\u0440\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432\u0435\u0441\u0438\u0442 50MB \u0432\u043c\u0435\u0441\u0442\u043e 250MB \u0443 \u0441\u0440\u0435\u0434\u043d\u0435\u0433\u043e Electron-\u043f\u0440\u043e\u0435\u043a\u0442\u0430.\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430: \u0442\u0440\u0438 \u043e\u043a\u043d\u0430 \u0438 main process\u0412 ONEMIX-\u0434\u0435\u0441\u043a\u0442\u043e\u043f\u0435 \u0442\u0440\u0438 \u0442\u0438\u043f\u0430 \u043e\u043a\u043e\u043d:Main window \u2014 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0441 UI \u0447\u0430\u0442\u043e\u0432 \u0438 WebSocket-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435\u043c \u043a \u0431\u044d\u043a\u0435\u043d\u0434\u0443. \u042d\u0442\u043e \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u043e\u043a\u043d\u043e, \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0438\u0434\u0451\u0442 \u0432\u0441\u044f \u0441\u0435\u0442\u0435\u0432\u0430\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c. WebSocket \u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0434\u0435\u0441\u044c.Call window \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0434\u043b\u044f \u0437\u0432\u043e\u043d\u043a\u043e\u0432. \u0421\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u043f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u0446\u0438\u0438 \u0437\u0432\u043e\u043d\u043a\u0430, \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438. \u0421\u043e\u0434\u0435\u0440\u0436\u0438\u0442 WebRTC PeerConnection, getUserMedia, \u0432\u0438\u0434\u0435\u043e-\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b.Settings window \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a. \u0421\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u043f\u0440\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a, \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0438.\u0418\u0434\u0435\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043e\u043a\u043e\u043d \u043d\u0435 \u043c\u043e\u044f \u2014 \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0435\u0442 Telegram Desktop, \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0435\u0442 Skype. \u0417\u0432\u043e\u043d\u043e\u043a \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u043c\u0438 \u043e\u043a\u043d\u0430\u043c\u0438 \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c:\u0417\u0432\u043e\u043d\u043e\u043a \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043a\u0440\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u043e\u0433\u0434\u0430 \u044e\u0437\u0435\u0440 \u0441\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e. \u0415\u0441\u043b\u0438 \u044e\u0437\u0435\u0440 \u043d\u0430 \u0437\u0432\u043e\u043d\u043a\u0435 \u0445\u043e\u0447\u0435\u0442 \u043e\u0442\u043a\u0440\u044b\u0442\u044c Excel\/\u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c \u2014 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0435\u043c\u0443 \u043c\u0435\u0448\u0430\u0435\u0442 \u0432 taskbar, \u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0437\u0432\u043e\u043d\u043a\u0430 \u043d\u0435\u0442.\u0417\u0432\u043e\u043d\u043e\u043a \u043c\u043e\u0436\u0435\u0442 (\u0438 \u0434\u043e\u043b\u0436\u0435\u043d) \u0431\u044b\u0442\u044c alwaysOnTop, \u0447\u0442\u043e\u0431\u044b \u0432\u0438\u0434\u0435\u043e \u0431\u044b\u043b\u043e \u0432\u0438\u0434\u043d\u043e \u043f\u043e\u0432\u0435\u0440\u0445 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u043e\u043a\u043e\u043d. \u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u2014 \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d.\u0417\u0432\u043e\u043d\u043e\u043a \u0438 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0436\u0438\u0432\u0443\u0442 \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u0436\u0438\u0437\u043d\u044f\u043c\u0438: \u0437\u0432\u043e\u043d\u043e\u043a \u043c\u043e\u0436\u0435\u0442 \u043e\u0431\u043e\u0440\u0432\u0430\u0442\u044c\u0441\u044f (\u0438 \u043e\u043a\u043d\u043e \u0437\u0430\u043a\u0440\u044b\u0442\u044c\u0441\u044f), \u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f. \u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u2014 \u0437\u0432\u043e\u043d\u043e\u043a \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c.function createCallWindow(callState) {  if (callWindow &amp;&amp; !callWindow.isDestroyed()) { callWindow.focus(); return; }  const isVideo = callState.callType === &#8216;video&#8217;;  const { screen } = require(&#8216;electron&#8217;);  const { width: sw, height: sh } = screen.getPrimaryDisplay().workAreaSize;  const winW = isVideo ? 480 : 360;  const winH = isVideo ? 700 : 560;  callWindow = new BrowserWindow({    width: winW, height: winH,    minWidth: 300, minHeight: 420,    x: Math.round((sw &#8212; winW) \/ 2),    y: Math.round((sh &#8212; winH) \/ 2),    alwaysOnTop: true,    frame: false,    titleBarStyle: &#8216;hidden&#8217;,    backgroundColor: &#8216;#000000&#8217;,    skipTaskbar: false,    webPreferences: {      preload: path.join(__dirname, &#8216;preload.js&#8217;),      contextIsolation: true,      nodeIntegration: false,    },    show: false,  });  callWindow.loadFile(path.join(__dirname, &#8216;src&#8217;, &#8216;call.html&#8217;), {    query: { state: JSON.stringify(callState) },  });}\u041f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 query URL \u2014 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0438 \u043d\u0430\u0434\u0451\u0436\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431. \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u0447\u0435\u0440\u0435\u0437 IPC \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0436\u0434\u0430\u0442\u044c ready-to-show \u0438 \u043f\u043e\u0442\u043e\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0441\u043b\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435, \u0447\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 race conditions.WebRTC \u0447\u0435\u0440\u0435\u0437 relay: \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u0417\u0432\u043e\u043d\u043e\u043a \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432 call window, \u043d\u043e WebSocket \u043a \u0431\u044d\u043a\u0435\u043d\u0434\u0443 \u0436\u0438\u0432\u0451\u0442 \u0432 main window. WebRTC \u0441\u0438\u0433\u043d\u0430\u043b\u044b (offer, answer, ICE candidates) \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0443\u0434\u0430-\u043e\u0431\u0440\u0430\u0442\u043d\u043e. \u041f\u0440\u044f\u043c\u043e\u0439 WebSocket \u0438\u0437 call window \u2014 \u043f\u043b\u043e\u0445\u0430\u044f \u0438\u0434\u0435\u044f: \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0434\u0432\u0430 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0445 WebSocket-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f, \u0433\u043e\u043d\u043a\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439, \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442\u044b \u043f\u0443\u0448\u0435\u0439.\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 relay \u0447\u0435\u0440\u0435\u0437 main process:[call window] \u2192 IPC \u2192 [main process] \u2192 IPC \u2192 [main window WS] \u2192 server   \u2191                                                              \u2502   \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 IPC \u2190\u2500\u2500\u2500 [main process] \u2190\u2500\u2500\u2500 IPC \u2190\u2500\u2500\u2500 WS message \u2500\u2500\u2500\u2500\u2518\u0412 call window (renderer):\/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u0438\u0433\u043d\u0430\u043b\u0430window.electronAPI.callWinSendSignal({ callId, signal });\/\/ \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0438\u0433\u043d\u0430\u043b\u0430window.electronAPI.onCallSignal((data) =&gt; {  if (data.type === &#8216;webrtc_answer&#8217;) pc.setRemoteDescription(data.sdp);  \/\/ &#8230; etc});\u0412 main process:\/\/ Forward signal from call window \u2192 main windowipcMain.on(&#8216;webrtc-signal&#8217;, (_, { callId, signal }) =&gt; {  if (mainWindow &amp;&amp; !mainWindow.isDestroyed())    mainWindow.webContents.send(&#8216;relay-webrtc-signal&#8217;, { callId, signal });});\/\/ Forward incoming WS message \u2192 call windowipcMain.handle(&#8216;send-to-call-window&#8217;, (_, message) =&gt; sendToCallWindowSafe(message));\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0433\u0440\u0430\u0431\u043b\u044f \u2014 call window \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0435\u0449\u0451 \u043d\u0435 \u0433\u043e\u0442\u043e\u0432 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u043e\u0433\u0434\u0430 \u0443\u0436\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043b\u0430\u0442\u044c webContents.send, \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0442\u0435\u0440\u044f\u044e\u0442\u0441\u044f. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 \u0431\u0443\u0444\u0435\u0440\u0438\u0437\u0430\u0446\u0438\u044f:let callWindowReady = false;let callWindowBuffer = [];function sendToCallWindowSafe(message) {  if (!callWindow || callWindow.isDestroyed()) return;  if (callWindowReady) callWindow.webContents.send(&#8216;call-signal&#8217;, message);  else callWindowBuffer.push(message);}\/\/ Call window \u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0442 \u043a\u043e\u0433\u0434\u0430 \u0433\u043e\u0442\u043e\u0432 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044fipcMain.on(&#8216;call-window-ready&#8217;, () =&gt; {  callWindowReady = true;  if (callWindow &amp;&amp; !callWindow.isDestroyed()) {    for (const msg of callWindowBuffer) callWindow.webContents.send(&#8216;call-signal&#8217;, msg);  }  callWindowBuffer = [];});call-window-ready \u0448\u043b\u0451\u0442\u0441\u044f \u0438\u0437 call window \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u043b\u043d\u043e\u0439 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 UI, \u043d\u0435 \u043f\u043e\u0441\u043b\u0435 ready-to-show (\u044d\u0442\u043e \u0440\u0430\u043d\u044c\u0448\u0435). \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0431\u0443\u0444\u0435\u0440 \u0441\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f, \u0438 \u0441\u0432\u044f\u0437\u044c \u0438\u0434\u0451\u0442 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e.\u042d\u0442\u043e\u0442 \u0431\u0443\u0444\u0435\u0440 \u2014 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u043d. \u0411\u0435\u0437 \u043d\u0435\u0433\u043e ~10% \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u043e\u0431\u0440\u044b\u0432\u0430\u043b\u0438\u0441\u044c \u0431\u044b \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0435, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u0435\u0440\u0432\u044b\u0439 offer \u043e\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u043b \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u0447\u0435\u043c \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440 \u0443\u0441\u043f\u0435\u0432\u0430\u043b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a.Deep links \u043d\u0430 \u0442\u0440\u0451\u0445 \u041e\u0421 \u2014 \u0442\u0440\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u043e\u0434\u0445\u043e\u0434\u0430\u0414\u0435\u0441\u043a\u0442\u043e\u043f-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0430\u043c \u0438\u0437 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430: onemixdesktop:\/\/chat\/abc123 \u0438\u043b\u0438 \u0438\u0437 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 itpaxlive.ru.\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u0430\u044f \u0432\u0435\u0437\u0434\u0435:if (process.defaultApp) {  if (process.argv.length &gt;= 2) {    app.setAsDefaultProtocolClient(&#8216;onemixdesktop&#8217;, process.execPath, [path.resolve(process.argv[1])]);  }} else {  app.setAsDefaultProtocolClient(&#8216;onemixdesktop&#8217;);}\u0410 \u0432\u043e\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u041e\u0421 \u0440\u0430\u0437\u043d\u0430\u044f.macOS: \u0435\u0441\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 event open-url, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0448\u043b\u0451\u0442 URL \u043a\u043e\u0433\u0434\u0430 \u044e\u0437\u0435\u0440 \u043a\u043b\u0438\u043a\u0430\u0435\u0442 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 onemixdesktop:\/\/&#8230;. \u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e \u0438\u043b\u0438 \u043d\u0435\u0442 \u2014 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u0442\u0441\u044f.app.on(&#8216;open-url&#8217;, (event, url) =&gt; {  event.preventDefault();  handleDeepLinkUrl(url);});Windows\/Linux: event open-url \u0442\u0443\u0442 \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u041a\u043e\u0433\u0434\u0430 \u044e\u0437\u0435\u0440 \u043a\u043b\u0438\u043a\u0430\u0435\u0442 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435, \u041e\u0421 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043d\u043e\u0432\u043e \u0441 URL \u0432 process.argv. \u0415\u0441\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e, \u0432\u0442\u043e\u0440\u043e\u0439 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0441\u044f \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u2014 \u043d\u0443\u0436\u043d\u0430 \u0437\u0430\u0449\u0438\u0442\u0430.const gotSingleLock = app.requestSingleInstanceLock();if (!gotSingleLock) {  app.quit();  \/\/ \u0423\u0436\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u0438\u0433\u043d\u0430\u043b} else {  app.on(&#8216;second-instance&#8217;, (event, argv) =&gt; {    const url = argv.find(a =&gt; a.startsWith(&#8216;onemixdesktop:\/\/&#8217;));    if (url) handleDeepLinkUrl(url);    if (mainWindow) { mainWindow.show(); mainWindow.focus(); }  });}\/\/ \u041f\u0440\u0438 cold start \u2014 URL \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0432 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u043c argvconst launchUrl = process.argv.find(a =&gt; a.startsWith(&#8216;onemixdesktop:\/\/&#8217;));if (launchUrl) _pendingDeepLink = launchUrl;\u0418 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0441\u043b\u043e\u0439 \u2014 \u043e\u043a\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0435\u0449\u0451 \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u043e \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0448\u0451\u043b deep link:let _pendingDeepLink = null;function handleDeepLinkUrl(url) {  if (mainWindow &amp;&amp; !mainWindow.isDestroyed()) {    mainWindow.show();    mainWindow.focus();    mainWindow.webContents.send(&#8216;deep-link&#8217;, url);  } else {    _pendingDeepLink = url;  }}\/\/ \u0412 createWindow \u2192 ready-to-showmainWindow.once(&#8216;ready-to-show&#8217;, () =&gt; {  mainWindow.show();  if (_pendingDeepLink) {    setTimeout(() =&gt; {      mainWindow.webContents.send(&#8216;deep-link&#8217;, _pendingDeepLink);      _pendingDeepLink = null;    }, 1500); \/\/ 1.5s \u2014 \u0434\u0430\u0451\u043c \u0432\u0440\u0435\u043c\u044f \u043d\u0430 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440\u0430  }});\u0422\u0440\u0438 \u041e\u0421, \u0442\u0440\u0438 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438 \u2014 \u0438 \u0432\u0441\u0435 \u043e\u043d\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e, \u0438\u043d\u0430\u0447\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u043a\u0430\u043a\u043e\u043c-\u0442\u043e \u0438\u0437 \u043d\u0438\u0445 \u043f\u043e\u043b\u043e\u043c\u0430\u044e\u0442\u0441\u044f.\u041f\u0435\u0440\u0435\u0445\u0432\u0430\u0442 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 \u0438 web requests\u0418\u0437 file:\/\/ (\u043e\u0442\u043a\u0443\u0434\u0430 \u0433\u0440\u0443\u0437\u0438\u0442\u0441\u044f HTML) \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043a https:\/\/itpaxlive.ru\/* \u0438\u0434\u0443\u0442 \u043a\u0430\u043a cross-origin. WebSocket \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043d\u043e &lt;img src=&#187;https:\/\/itpaxlive.ru\/avatar.jpg&#187;&gt;&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-479432","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/479432","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=479432"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/479432\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=479432"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=479432"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=479432"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}