[{"data":1,"prerenderedAt":15661},["ShallowReactive",2],{"navigation_docs":3,"-web-development-network-architecture-and-protocols-websocket-protocol":3728,"-web-development-network-architecture-and-protocols-websocket-protocol-surround":15656},[4,1707,1912,2366,2547,2673,2880,3002,3052,3109,3143,3269,3346,3360,3495,3512,3525,3671,3724],{"title":5,"icon":6,"path":7,"stem":8,"children":9},"C#","i-devicon-csharp","\u002Fcsharp","01.csharp",[10,13,60,90,120,202,219,253,379,404,457,650,1364,1654,1703],{"title":11,"path":7,"stem":12},"C# та .NET","01.csharp\u002Findex",{"title":14,"icon":15,"path":16,"stem":17,"children":18,"page":59},"Fundamentals","i-lucide-book-open","\u002Fcsharp\u002Ffundamentals","01.csharp\u002F01.fundamentals",[19,23,27,31,35,39,43,47,51,55],{"title":20,"path":21,"stem":22},"Вступ до екосистеми .NET","\u002Fcsharp\u002Ffundamentals\u002Fintroduction-to-ecosystem","01.csharp\u002F01.fundamentals\u002F01.introduction-to-ecosystem",{"title":24,"path":25,"stem":26},"Структура програми на C#","\u002Fcsharp\u002Ffundamentals\u002Fprogram-structure","01.csharp\u002F01.fundamentals\u002F02.program-structure",{"title":28,"path":29,"stem":30},"Змінні та Типи Даних","\u002Fcsharp\u002Ffundamentals\u002Fvariables-data-types","01.csharp\u002F01.fundamentals\u002F03.variables-data-types",{"title":32,"path":33,"stem":34},"Масиви","\u002Fcsharp\u002Ffundamentals\u002Farrays","01.csharp\u002F01.fundamentals\u002F04.arrays",{"title":36,"path":37,"stem":38},"Strings & Text Handling","\u002Fcsharp\u002Ffundamentals\u002Fstrings-text-handling","01.csharp\u002F01.fundamentals\u002F05.strings-text-handling",{"title":40,"path":41,"stem":42},"Дати і Час","\u002Fcsharp\u002Ffundamentals\u002Fdates-time-handling","01.csharp\u002F01.fundamentals\u002F06.dates-time-handling",{"title":44,"path":45,"stem":46},"Потік Керування","\u002Fcsharp\u002Ffundamentals\u002Fcontrol-flow","01.csharp\u002F01.fundamentals\u002F07.control-flow",{"title":48,"path":49,"stem":50},"Методи","\u002Fcsharp\u002Ffundamentals\u002Fmethods","01.csharp\u002F01.fundamentals\u002F08.methods",{"title":52,"path":53,"stem":54},"Основи Відлагодження","\u002Fcsharp\u002Ffundamentals\u002Fdebugging-basics","01.csharp\u002F01.fundamentals\u002F09.debugging-basics",{"title":56,"path":57,"stem":58},"Інтерактивна Консоль (Classic)","\u002Fcsharp\u002Ffundamentals\u002Finteractive-console","01.csharp\u002F01.fundamentals\u002F10.interactive-console",false,{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":59},"OOP","i-lucide-box","\u002Fcsharp\u002Foop","01.csharp\u002F02.oop",[66,70,74,78,82,86],{"title":67,"path":68,"stem":69},"Package Management (Управління Пакетами)","\u002Fcsharp\u002Foop\u002Fpackage-management","01.csharp\u002F02.oop\u002F01.package-management",{"title":71,"path":72,"stem":73},"Класи та Об'єкти","\u002Fcsharp\u002Foop\u002Fclasses-objects","01.csharp\u002F02.oop\u002F02.classes-objects",{"title":75,"path":76,"stem":77},"Властивості та Поля","\u002Fcsharp\u002Foop\u002Fproperties-fields","01.csharp\u002F02.oop\u002F03.properties-fields",{"title":79,"path":80,"stem":81},"Стовпи ООП","\u002Fcsharp\u002Foop\u002Foop-pillars","01.csharp\u002F02.oop\u002F04.oop-pillars",{"title":83,"path":84,"stem":85},"Advanced Types","\u002Fcsharp\u002Foop\u002Fadvanced-types","01.csharp\u002F02.oop\u002F05.advanced-types",{"title":87,"path":88,"stem":89},"Namespaces (Простори Імен)","\u002Fcsharp\u002Foop\u002Fnamespaces","01.csharp\u002F02.oop\u002F06.namespaces",{"title":91,"icon":92,"path":93,"stem":94,"children":95,"page":59},"Advanced Core","i-lucide-zap","\u002Fcsharp\u002Fadvanced-core","01.csharp\u002F03.advanced-core",[96,100,104,108,112,116],{"title":97,"path":98,"stem":99},"Generics (Узагальнення)","\u002Fcsharp\u002Fadvanced-core\u002Fgenerics","01.csharp\u002F03.advanced-core\u002F01.generics",{"title":101,"path":102,"stem":103},"Делегати, Події та Лямбда-вирази","\u002Fcsharp\u002Fadvanced-core\u002Fdelegates-events-lambdas","01.csharp\u002F03.advanced-core\u002F02.delegates-events-lambdas",{"title":105,"path":106,"stem":107},"Interfaces Deep Dive (Інтерфейси: Поглиблений Розгляд)","\u002Fcsharp\u002Fadvanced-core\u002Finterfaces-deep-dive","01.csharp\u002F03.advanced-core\u002F03.interfaces-deep-dive",{"title":109,"path":110,"stem":111},"Обробка Винятків","\u002Fcsharp\u002Fadvanced-core\u002Fexception-handling","01.csharp\u002F03.advanced-core\u002F04.exception-handling",{"title":113,"path":114,"stem":115},"Pattern Matching","\u002Fcsharp\u002Fadvanced-core\u002Fpattern-matching","01.csharp\u002F03.advanced-core\u002F05.pattern-matching",{"title":117,"path":118,"stem":119},"Додаткові Можливості C#","\u002Fcsharp\u002Fadvanced-core\u002Fadditional-features","01.csharp\u002F03.advanced-core\u002F06.additional-features",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":59},"Architecture Best Practices","i-lucide-building-2","\u002Fcsharp\u002Farchitecture-best-practices","01.csharp\u002F04.architecture-best-practices",[126,130,149,153,157,161,165,169],{"title":127,"path":128,"stem":129},"Software Design Principles (Частина 1)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fsoftware-design-principles","01.csharp\u002F04.architecture-best-practices\u002F01.software-design-principles",{"title":131,"icon":132,"path":133,"stem":134,"children":135,"page":59},"Design Patterns","i-lucide-folder","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns",[136],{"title":137,"icon":132,"path":138,"stem":139,"children":140,"page":59},"Creational","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational",[141,145],{"title":142,"path":143,"stem":144},"Singleton (Одинак)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational\u002Fsingleton","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational\u002F01.singleton",{"title":146,"path":147,"stem":148},"Builder (Будівельник)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational\u002Fbuilder","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational\u002F02.builder",{"title":150,"path":151,"stem":152},"Building Professional CLIs","\u002Fcsharp\u002Farchitecture-best-practices\u002Fbuilding-professional-clis","01.csharp\u002F04.architecture-best-practices\u002F03.building-professional-clis",{"title":154,"path":155,"stem":156},"Validation & Flow Control","\u002Fcsharp\u002Farchitecture-best-practices\u002Fvalidation-flow-control","01.csharp\u002F04.architecture-best-practices\u002F04.validation-flow-control",{"title":158,"path":159,"stem":160},"The Modern .NET Host (Microsoft.Extensions)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fmodern-dotnet-host","01.csharp\u002F04.architecture-best-practices\u002F05.modern-dotnet-host",{"title":162,"path":163,"stem":164},"Data Mapper: Repository та DAO патерни (Частина 1)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdata-mapper-part1","01.csharp\u002F04.architecture-best-practices\u002F06.data-mapper-part1",{"title":166,"path":167,"stem":168},"Data Mapper: Repository та DAO патерни (Частина 2)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdata-mapper-part2","01.csharp\u002F04.architecture-best-practices\u002F07.data-mapper-part2",{"title":170,"icon":132,"path":171,"stem":172,"children":173,"page":59},"Di Ioc","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc",[174,178,182,186,190,194,198],{"title":175,"path":176,"stem":177},"Проблема залежностей та Інверсія Контролю","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fthe-dependency-problem","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F01.the-dependency-problem",{"title":179,"path":180,"stem":181},"Будуємо власний Service Container","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fbuild-your-own-container","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F02.build-your-own-container",{"title":183,"path":184,"stem":185},"Service Locator: Паттерн та Анти-паттерн","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fservice-locator-pattern","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F03.service-locator-pattern",{"title":187,"path":188,"stem":189},"Паттерни Dependency Injection","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fdependency-injection-patterns","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F04.dependency-injection-patterns",{"title":191,"path":192,"stem":193},"Microsoft DI: IServiceCollection та IServiceProvider","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fmicrosoft-di-deep-dive","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F05.microsoft-di-deep-dive",{"title":195,"path":196,"stem":197},"Service Lifetimes та Scopes","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fservice-lifetimes-and-scopes","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F06.service-lifetimes-and-scopes",{"title":199,"path":200,"stem":201},"DI Анти-паттерни та Найкращі Практики","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fdi-anti-patterns-and-best-practices","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F07.di-anti-patterns-and-best-practices",{"title":203,"icon":132,"path":204,"stem":205,"children":206,"page":59},"Standard Library","\u002Fcsharp\u002Fstandard-library","01.csharp\u002F05.standard-library",[207,211,215],{"title":208,"path":209,"stem":210},"Collections (Колекції)","\u002Fcsharp\u002Fstandard-library\u002Fcollections","01.csharp\u002F05.standard-library\u002F01.collections",{"title":212,"path":213,"stem":214},"High Performance Types (Високопродуктивні Типи)","\u002Fcsharp\u002Fstandard-library\u002Fhigh-performance-types","01.csharp\u002F05.standard-library\u002F02.high-performance-types",{"title":216,"path":217,"stem":218},"LINQ (Language Integrated Query)","\u002Fcsharp\u002Fstandard-library\u002Flinq","01.csharp\u002F05.standard-library\u002F03.linq",{"title":220,"icon":221,"path":222,"stem":223,"children":224,"page":59},"System Internals Concurrency","i-lucide-server","\u002Fcsharp\u002Fsystem-internals-concurrency","01.csharp\u002F06.system-internals-concurrency",[225,229,233,237,241,245,249],{"title":226,"path":227,"stem":228},"Memory Management","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fmemory-management","01.csharp\u002F06.system-internals-concurrency\u002F01.memory-management",{"title":230,"path":231,"stem":232},"Reflection API: System.Type та Метадані","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Freflection-fundamentals","01.csharp\u002F06.system-internals-concurrency\u002F02.reflection-fundamentals",{"title":234,"path":235,"stem":236},"Attributes та Dynamic Language Runtime","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fattributes-dynamic","01.csharp\u002F06.system-internals-concurrency\u002F03.attributes-dynamic",{"title":238,"path":239,"stem":240},"Expression Trees: Швидка Альтернатива Рефлексії","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fexpression-trees-compiled","01.csharp\u002F06.system-internals-concurrency\u002F04.expression-trees-compiled",{"title":242,"path":243,"stem":244},"Source Generators: Compile-Time Code Generation","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fsource-generators","01.csharp\u002F06.system-internals-concurrency\u002F05.source-generators",{"title":246,"path":247,"stem":248},"Multithreading Fundamentals","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fmultithreading-fundamentals","01.csharp\u002F06.system-internals-concurrency\u002F06.multithreading-fundamentals",{"title":250,"path":251,"stem":252},"Synchronization Primitives","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fsynchronization-primitives","01.csharp\u002F06.system-internals-concurrency\u002F07.synchronization-primitives",{"title":254,"icon":255,"path":256,"stem":257,"children":258,"page":59},"System Programming Windows","i-lucide-cpu","\u002Fcsharp\u002Fsystem-programming-windows","01.csharp\u002F07.system-programming-windows",[259,263,267,271,275,279,283,287,291,295,299,303,307,311,315,319,323,327,331,335,339,343,347,351,355,359,363,367,371,375],{"title":260,"path":261,"stem":262},"Як Працює Операційна Система","\u002Fcsharp\u002Fsystem-programming-windows\u002Fhow-os-works","01.csharp\u002F07.system-programming-windows\u002F01.how-os-works",{"title":264,"path":265,"stem":266},"Процеси в .NET — API та Запуск","\u002Fcsharp\u002Fsystem-programming-windows\u002Fprocesses-in-dotnet","01.csharp\u002F07.system-programming-windows\u002F02.processes-in-dotnet",{"title":268,"path":269,"stem":270},"Процеси в .NET — IPC та Міжпроцесна Комунікація","\u002Fcsharp\u002Fsystem-programming-windows\u002F02a.processes-ipc","01.csharp\u002F07.system-programming-windows\u002F02a.processes-ipc",{"title":272,"path":273,"stem":274},"Application Domains та Збірки — AppDomain і AssemblyLoadContext","\u002Fcsharp\u002Fsystem-programming-windows\u002Fappdomains-assemblies","01.csharp\u002F07.system-programming-windows\u002F03.appdomains-assemblies",{"title":276,"path":277,"stem":278},"Application Domains та Збірки — Plug-in Система з Hot-Reload","\u002Fcsharp\u002Fsystem-programming-windows\u002F03a.appdomains-plugin-system","01.csharp\u002F07.system-programming-windows\u002F03a.appdomains-plugin-system",{"title":280,"path":281,"stem":282},"Потоки — Основи та API Thread","\u002Fcsharp\u002Fsystem-programming-windows\u002Fthread-fundamentals","01.csharp\u002F07.system-programming-windows\u002F04.thread-fundamentals",{"title":284,"path":285,"stem":286},"Потоки — Lifecycle, Пріоритети та Безпечне Завершення","\u002Fcsharp\u002Fsystem-programming-windows\u002F04a.thread-lifecycle-priorities","01.csharp\u002F07.system-programming-windows\u002F04a.thread-lifecycle-priorities",{"title":288,"path":289,"stem":290},"Проблеми Спільного Стану — Race Condition та Data Race","\u002Fcsharp\u002Fsystem-programming-windows\u002Fshared-state-problems","01.csharp\u002F07.system-programming-windows\u002F05.shared-state-problems",{"title":292,"path":293,"stem":294},"Проблеми Спільного Стану — Memory Model та volatile","\u002Fcsharp\u002Fsystem-programming-windows\u002F05a.shared-state-memory-model","01.csharp\u002F07.system-programming-windows\u002F05a.shared-state-memory-model",{"title":296,"path":297,"stem":298},"Синхронізація — Monitor, lock та еволюція примітивів","\u002Fcsharp\u002Fsystem-programming-windows\u002Fsynchronization-fundamentals","01.csharp\u002F07.system-programming-windows\u002F06.synchronization-fundamentals",{"title":300,"path":301,"stem":302},"Синхронізація — Наскрізний Приклад та Deadlock Detection","\u002Fcsharp\u002Fsystem-programming-windows\u002F06a.synchronization-walkthrough","01.csharp\u002F07.system-programming-windows\u002F06a.synchronization-walkthrough",{"title":304,"path":305,"stem":306},"Синхронізація — Mutex, Semaphore та Event-Based Primitives","\u002Fcsharp\u002Fsystem-programming-windows\u002Fsynchronization-advanced","01.csharp\u002F07.system-programming-windows\u002F07.synchronization-advanced",{"title":308,"path":309,"stem":310},"Синхронізація — Interlocked, Volatile та Lock-Free Структури","\u002Fcsharp\u002Fsystem-programming-windows\u002F07a.synchronization-advanced-walkthrough","01.csharp\u002F07.system-programming-windows\u002F07a.synchronization-advanced-walkthrough",{"title":312,"path":313,"stem":314},"Interlocked, CAS та Lock-Free Структури","\u002Fcsharp\u002Fsystem-programming-windows\u002Finterlocked-cas-lockfree","01.csharp\u002F07.system-programming-windows\u002F08.interlocked-cas-lockfree",{"title":316,"path":317,"stem":318},"Volatile, Memory Model та Spinning","\u002Fcsharp\u002Fsystem-programming-windows\u002F08a.volatile-memory-model","01.csharp\u002F07.system-programming-windows\u002F08a.volatile-memory-model",{"title":320,"path":321,"stem":322},"ThreadPool — Пул Потоків для Ефективного Виконання","\u002Fcsharp\u002Fsystem-programming-windows\u002Fthread-pool","01.csharp\u002F07.system-programming-windows\u002F09.thread-pool",{"title":324,"path":325,"stem":326},"ThreadPool — Просунуті Сценарії та Внутрішня Будова","\u002Fcsharp\u002Fsystem-programming-windows\u002F09a.thread-pool-advanced","01.csharp\u002F07.system-programming-windows\u002F09a.thread-pool-advanced",{"title":328,"path":329,"stem":330},"Concurrent та Immutable Collections","\u002Fcsharp\u002Fsystem-programming-windows\u002Fconcurrent-collections","01.csharp\u002F07.system-programming-windows\u002F10.concurrent-collections",{"title":332,"path":333,"stem":334},"TPL, Task та Композиція — Від Thread до Task","\u002Fcsharp\u002Fsystem-programming-windows\u002Ftpl-parallel-plinq","01.csharp\u002F07.system-programming-windows\u002F11.tpl-parallel-plinq",{"title":336,"path":337,"stem":338},"Parallel Class та PLINQ — Data Parallelism","\u002Fcsharp\u002Fsystem-programming-windows\u002F11a.tpl-parallel-plinq-advanced","01.csharp\u002F07.system-programming-windows\u002F11a.tpl-parallel-plinq-advanced",{"title":340,"path":341,"stem":342},"Async\u002FAwait — Фундамент Асинхронного Програмування","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-fundamentals","01.csharp\u002F07.system-programming-windows\u002F12.async-fundamentals",{"title":344,"path":345,"stem":346},"SynchronizationContext та ConfigureAwait — Контекст Виконання","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-context-configureawait","01.csharp\u002F07.system-programming-windows\u002F13.async-context-configureawait",{"title":348,"path":349,"stem":350},"Async — Просунуті Паттерни","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-advanced","01.csharp\u002F07.system-programming-windows\u002F14.async-advanced",{"title":352,"path":353,"stem":354},"System.Threading.Channels — Async Producer-Consumer","\u002Fcsharp\u002Fsystem-programming-windows\u002Fchannels","01.csharp\u002F07.system-programming-windows\u002F15.channels",{"title":356,"path":357,"stem":358},"Асинхронна Синхронізація","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-synchronization","01.csharp\u002F07.system-programming-windows\u002F16.async-synchronization",{"title":360,"path":361,"stem":362},"Unsafe Code та Вказівники","\u002Fcsharp\u002Fsystem-programming-windows\u002Funsafe-code","01.csharp\u002F07.system-programming-windows\u002F17.unsafe-code",{"title":364,"path":365,"stem":366},"P\u002FInvoke та Windows API — Міст між .NET та Native Code","\u002Fcsharp\u002Fsystem-programming-windows\u002Fpinvoke-winapi","01.csharp\u002F07.system-programming-windows\u002F18.pinvoke-winapi",{"title":368,"path":369,"stem":370},"Реєстр Windows — Центральна База Конфігурації Системи","\u002Fcsharp\u002Fsystem-programming-windows\u002Fwindows-registry","01.csharp\u002F07.system-programming-windows\u002F19.windows-registry",{"title":372,"path":373,"stem":374},"Windows Hooks, Hotkeys та Services — Глибока Інтеграція з ОС","\u002Fcsharp\u002Fsystem-programming-windows\u002Fwindows-hooks-services","01.csharp\u002F07.system-programming-windows\u002F20.windows-hooks-services",{"title":376,"path":377,"stem":378},"Системне Програмування C# (Windows) — 07.system-programming-windows","\u002Fcsharp\u002Fsystem-programming-windows\u002Fimplementation_plan","01.csharp\u002F07.system-programming-windows\u002Fimplementation_plan",{"title":380,"icon":132,"path":381,"stem":382,"children":383,"page":59},"Io","\u002Fcsharp\u002Fio","01.csharp\u002F08.io",[384,388,392,396,400],{"title":385,"path":386,"stem":387},"8.1.1. Основи роботи з файловою системою","\u002Fcsharp\u002Fio\u002Ffile-system-basics","01.csharp\u002F08.io\u002F01.file-system-basics",{"title":389,"path":390,"stem":391},"8.1.2. Потоки (Streams) та Серіалізація Даних","\u002Fcsharp\u002Fio\u002Fstreams-serialization","01.csharp\u002F08.io\u002F02.streams-serialization",{"title":393,"path":394,"stem":395},"8.2.1. JSON Serialization з System.Text.Json","\u002Fcsharp\u002Fio\u002Fjson-serialization","01.csharp\u002F08.io\u002F03.json-serialization",{"title":397,"path":398,"stem":399},"8.2.2. XML Serialization та LINQ to XML","\u002Fcsharp\u002Fio\u002Fxml-serialization","01.csharp\u002F08.io\u002F04.xml-serialization",{"title":401,"path":402,"stem":403},"8.2.3. Binary Serialization: MessagePack та Protocol Buffers","\u002Fcsharp\u002Fio\u002Fbinary-serialization","01.csharp\u002F08.io\u002F05.binary-serialization",{"title":405,"icon":132,"path":406,"stem":407,"children":408,"page":59},"Ado Net","\u002Fcsharp\u002Fado-net","01.csharp\u002F09.ado-net",[409,413,417,421,425,429,433,437,441,445,449,453],{"title":410,"path":411,"stem":412},"9.1. Введення в ADO.NET","\u002Fcsharp\u002Fado-net\u002Fintroduction-to-adonet","01.csharp\u002F09.ado-net\u002F01.introduction-to-adonet",{"title":414,"path":415,"stem":416},"9.2. Клас DbConnection — з'єднання з базою даних","\u002Fcsharp\u002Fado-net\u002Fconnection","01.csharp\u002F09.ado-net\u002F02.connection",{"title":418,"path":419,"stem":420},"9.3. Клас DbCommand — виконання SQL-запитів","\u002Fcsharp\u002Fado-net\u002Fcommand-and-queries","01.csharp\u002F09.ado-net\u002F03.command-and-queries",{"title":422,"path":423,"stem":424},"9.4. Клас DbDataReader — ефективне читання даних","\u002Fcsharp\u002Fado-net\u002Fdatareader","01.csharp\u002F09.ado-net\u002F04.datareader",{"title":426,"path":427,"stem":428},"9.5. Параметризовані запити та захист від SQL Injection","\u002Fcsharp\u002Fado-net\u002Fparameters-and-sql-injection","01.csharp\u002F09.ado-net\u002F05.parameters-and-sql-injection",{"title":430,"path":431,"stem":432},"9.6. Транзакції в ADO.NET","\u002Fcsharp\u002Fado-net\u002Ftransactions","01.csharp\u002F09.ado-net\u002F06.transactions",{"title":434,"path":435,"stem":436},"9.7. DbProviderFactory — провайдер-незалежний код","\u002Fcsharp\u002Fado-net\u002Fprovider-factory","01.csharp\u002F09.ado-net\u002F07.provider-factory",{"title":438,"path":439,"stem":440},"9.8. Асинхронний доступ до даних","\u002Fcsharp\u002Fado-net\u002Fasync-data-access","01.csharp\u002F09.ado-net\u002F08.async-data-access",{"title":442,"path":443,"stem":444},"9.9. Від'єднаний режим: DataSet, DataTable, DataRow","\u002Fcsharp\u002Fado-net\u002Fdisconnected-mode-dataset","01.csharp\u002F09.ado-net\u002F09.disconnected-mode-dataset",{"title":446,"path":447,"stem":448},"9.10. DataAdapter — міст між DataSet та базою даних","\u002Fcsharp\u002Fado-net\u002Fdata-adapter","01.csharp\u002F09.ado-net\u002F10.data-adapter",{"title":450,"path":451,"stem":452},"9.11. Data Mapper та Repository: Архітектура доступу до даних","\u002Fcsharp\u002Fado-net\u002Fdata-mapper-repository","01.csharp\u002F09.ado-net\u002F11.data-mapper-repository",{"title":454,"path":455,"stem":456},"9.12. Identity Map, Unit of Work та Specification Pattern","\u002Fcsharp\u002Fado-net\u002Fadvanced-patterns","01.csharp\u002F09.ado-net\u002F12.advanced-patterns",{"title":458,"icon":255,"path":459,"stem":460,"children":461,"page":59},"Ef Core","\u002Fcsharp\u002Fef-core","01.csharp\u002F10.ef-core",[462,466,470,474,478,482,486,490,494,498,502,506,510,514,518,522,526,532,538,542,546,550,554,558,562,566,570,574,578,582,586,590,594,598,602,606,610,614,618,622,626,630,634,638,642,646],{"title":463,"path":464,"stem":465},"Що таке ORM? Від SQL до об'єктів","\u002Fcsharp\u002Fef-core\u002Fwhat-is-orm","01.csharp\u002F10.ef-core\u002F01.what-is-orm",{"title":467,"path":468,"stem":469},"Перший проєкт — від нуля до CRUD","\u002Fcsharp\u002Fef-core\u002Ffirst-project","01.csharp\u002F10.ef-core\u002F02.first-project",{"title":471,"path":472,"stem":473},"DbContext — Серце EF Core","\u002Fcsharp\u002Fef-core\u002Fdbcontext-deep-dive","01.csharp\u002F10.ef-core\u002F03.dbcontext-deep-dive",{"title":475,"path":476,"stem":477},"Провайдери баз даних — Архітектура та Вибір СУБД","\u002Fcsharp\u002Fef-core\u002Fdatabase-providers","01.csharp\u002F10.ef-core\u002F04.database-providers",{"title":479,"path":480,"stem":481},"Конвенції EF Core — Магія без конфігурації","\u002Fcsharp\u002Fef-core\u002Fconventions","01.csharp\u002F10.ef-core\u002F05.conventions",{"title":483,"path":484,"stem":485},"Fluent API та Data Annotations — Явна конфігурація моделі","\u002Fcsharp\u002Fef-core\u002Ffluent-api-vs-annotations","01.csharp\u002F10.ef-core\u002F06.fluent-api-vs-annotations",{"title":487,"path":488,"stem":489},"Зв'язки — One-to-One та One-to-Many","\u002Fcsharp\u002Fef-core\u002Frelationships-basics","01.csharp\u002F10.ef-core\u002F07.relationships-basics",{"title":491,"path":492,"stem":493},"Зв'язки Advanced — Many-to-Many та Складні Сценарії","\u002Fcsharp\u002Fef-core\u002Frelationships-advanced","01.csharp\u002F10.ef-core\u002F08.relationships-advanced",{"title":495,"path":496,"stem":497},"Властивості — Типи, Конвертери, Компаратори (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fproperty-configuration-part1","01.csharp\u002F10.ef-core\u002F09.property-configuration-part1",{"title":499,"path":500,"stem":501},"Властивості — Value Comparers, Generators, Shadow Properties (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fproperty-configuration-part2","01.csharp\u002F10.ef-core\u002F09.property-configuration-part2",{"title":503,"path":504,"stem":505},"Складні типи — Owned Types та Complex Types (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fcomplex-types-owned-part1","01.csharp\u002F10.ef-core\u002F10.complex-types-owned-part1",{"title":507,"path":508,"stem":509},"Складні типи — Complex Types, Keyless Entities, Порівняння (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fcomplex-types-owned-part2","01.csharp\u002F10.ef-core\u002F10.complex-types-owned-part2",{"title":511,"path":512,"stem":513},"JSON Columns — Складні дані у JSON (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fjson-columns-part1","01.csharp\u002F10.ef-core\u002F11.json-columns-part1",{"title":515,"path":516,"stem":517},"JSON Columns — Value Comparers, Індекси, Провайдери (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fjson-columns-part2","01.csharp\u002F10.ef-core\u002F11.json-columns-part2",{"title":519,"path":520,"stem":521},"Успадкування — Абстрактні класи та TPH (Частина 1)","\u002Fcsharp\u002Fef-core\u002Finheritance-part1","01.csharp\u002F10.ef-core\u002F12.inheritance-part1",{"title":523,"path":524,"stem":525},"Успадкування — TPT, TPC та Порівняння Стратегій (Частина 2)","\u002Fcsharp\u002Fef-core\u002Finheritance-part2","01.csharp\u002F10.ef-core\u002F12.inheritance-part2",{"title":527,"path":528,"stem":529,"children":530},"Індекси, Обмеження та Схема (Частина 1)","\u002Fcsharp\u002Fef-core\u002Findexes-constraints-part1","01.csharp\u002F10.ef-core\u002F13.indexes-constraints-part1",[531],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"Індекси, Обмеження та Схема (Частина 2)","\u002Fcsharp\u002Fef-core\u002Findexes-constraints-part2","01.csharp\u002F10.ef-core\u002F13.indexes-constraints-part2",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541},"Seed Data — Початкові Дані (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fseeding-part1","01.csharp\u002F10.ef-core\u002F14.seeding-part1",{"title":543,"path":544,"stem":545},"Seed Data — SQL-скрипти, Bogus та Стратегії (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fseeding-part2","01.csharp\u002F10.ef-core\u002F14.seeding-part2",{"title":547,"path":548,"stem":549},"Global Query Filters — Глобальні Фільтри (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fglobal-query-filters-part1","01.csharp\u002F10.ef-core\u002F15.global-query-filters-part1",{"title":551,"path":552,"stem":553},"Global Query Filters — Підводні камені та Інтеграція (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fglobal-query-filters-part2","01.csharp\u002F10.ef-core\u002F15.global-query-filters-part2",{"title":555,"path":556,"stem":557},"LINQ-запити в EF Core (Частина 1)","\u002Fcsharp\u002Fef-core\u002Flinq-queries-part1","01.csharp\u002F10.ef-core\u002F16.linq-queries-part1",{"title":559,"path":560,"stem":561},"LINQ-запити в EF Core (Частина 2)","\u002Fcsharp\u002Fef-core\u002Flinq-queries-part2","01.csharp\u002F10.ef-core\u002F16.linq-queries-part2",{"title":563,"path":564,"stem":565},"Завантаження Пов'язаних Даних (Частина 1)","\u002Fcsharp\u002Fef-core\u002Floading-related-data-part1","01.csharp\u002F10.ef-core\u002F17.loading-related-data-part1",{"title":567,"path":568,"stem":569},"Завантаження Пов'язаних Даних (Частина 2)","\u002Fcsharp\u002Fef-core\u002Floading-related-data-part2","01.csharp\u002F10.ef-core\u002F17.loading-related-data-part2",{"title":571,"path":572,"stem":573},"Raw SQL, Views та Stored Procedures (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fraw-sql-part1","01.csharp\u002F10.ef-core\u002F18.raw-sql-part1",{"title":575,"path":576,"stem":577},"Raw SQL — Stored Procedures, DbFunction та Bulk Operations (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fraw-sql-part2","01.csharp\u002F10.ef-core\u002F18.raw-sql-part2",{"title":579,"path":580,"stem":581},"Продвинуті Запити — Compiled Queries, Bulk та Оптимізація (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fadvanced-queries-part1","01.csharp\u002F10.ef-core\u002F19.advanced-queries-part1",{"title":583,"path":584,"stem":585},"Продвинуті Запити — Query Tags, Bulk та Interceptors (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fadvanced-queries-part2","01.csharp\u002F10.ef-core\u002F19.advanced-queries-part2",{"title":587,"path":588,"stem":589},"Change Tracker — Відстеження Змін (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fchange-tracking-part1","01.csharp\u002F10.ef-core\u002F20.change-tracking-part1",{"title":591,"path":592,"stem":593},"Change Tracker — Графи Об'єктів та Disconnected (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fchange-tracking-part2","01.csharp\u002F10.ef-core\u002F20.change-tracking-part2",{"title":595,"path":596,"stem":597},"Збереження Даних та Транзакції (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fsaving-data-part1","01.csharp\u002F10.ef-core\u002F21.saving-data-part1",{"title":599,"path":600,"stem":601},"Збереження Даних — Concurrency та Outbox (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fsaving-data-part2","01.csharp\u002F10.ef-core\u002F21.saving-data-part2",{"title":603,"path":604,"stem":605},"Конкурентність та Блокування (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fconcurrency-part1","01.csharp\u002F10.ef-core\u002F22.concurrency-part1",{"title":607,"path":608,"stem":609},"Конкурентність — Дедлоки та Queue Processing (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fconcurrency-part2","01.csharp\u002F10.ef-core\u002F22.concurrency-part2",{"title":611,"path":612,"stem":613},"Міграції в EF Core — Основи (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fmigrations-basics-part1","01.csharp\u002F10.ef-core\u002F23.migrations-basics-part1",{"title":615,"path":616,"stem":617},"Міграції в EF Core — Основи (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fmigrations-basics-part2","01.csharp\u002F10.ef-core\u002F23.migrations-basics-part2",{"title":619,"path":620,"stem":621},"Міграції — Просунуті Сценарії (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fmigrations-advanced-part1","01.csharp\u002F10.ef-core\u002F24.migrations-advanced-part1",{"title":623,"path":624,"stem":625},"Міграції — Просунуті Сценарії (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fmigrations-advanced-part2","01.csharp\u002F10.ef-core\u002F24.migrations-advanced-part2",{"title":627,"path":628,"stem":629},"Управління Схемою та Database-First (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fschema-management-part1","01.csharp\u002F10.ef-core\u002F25.schema-management-part1",{"title":631,"path":632,"stem":633},"Управління Схемою та Database-First (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fschema-management-part2","01.csharp\u002F10.ef-core\u002F25.schema-management-part2",{"title":635,"path":636,"stem":637},"Продуктивність EF Core — Основи (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fperformance-fundamentals-part1","01.csharp\u002F10.ef-core\u002F26.performance-fundamentals-part1",{"title":639,"path":640,"stem":641},"Interceptors в EF Core (Частина 1)","\u002Fcsharp\u002Fef-core\u002Finterceptors-part1","01.csharp\u002F10.ef-core\u002F29.interceptors-part1",{"title":643,"path":644,"stem":645},"Interceptors в EF Core — Connection, Transaction та Materialization (Частина 2)","\u002Fcsharp\u002Fef-core\u002Finterceptors-part2","01.csharp\u002F10.ef-core\u002F29.interceptors-part2",{"title":647,"path":648,"stem":649},"План вивчення Entity Framework Core — Повний курс","\u002Fcsharp\u002Fef-core\u002Fimplementation_plan","01.csharp\u002F10.ef-core\u002Fimplementation_plan",{"title":651,"icon":652,"path":653,"stem":654,"children":655,"page":59},"ASP.NET","i-devicon-dotnetcore","\u002Fcsharp\u002Faspnet","01.csharp\u002F11.aspnet",[656,730,791,869,927,941,967,1057,1111,1182,1212,1289,1346],{"title":657,"icon":658,"path":659,"stem":660,"children":661,"page":59},"Minimal API","i-lucide-network","\u002Fcsharp\u002Faspnet\u002Fminimal-api","01.csharp\u002F11.aspnet\u002F01.minimal-api",[662,666,670,674,678,682,686,690,694,698,702,706,710,714,718,722,726],{"title":663,"path":664,"stem":665},"Вступ до ASP.NET та еволюція фреймворку","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fintroduction","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F01.introduction",{"title":667,"path":668,"stem":669},"Перший додаток на ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Ffirst-application","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F02.first-application",{"title":671,"path":672,"stem":673},"WebApplication, Builder та Dependency Injection","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fwebapplication-builder","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F03.webapplication-builder",{"title":675,"path":676,"stem":677},"Конвеєр запитів та Middleware","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frequest-pipeline-middleware","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F04.request-pipeline-middleware",{"title":679,"path":680,"stem":681},"Маршрутизація в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frouting-basics","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F05.routing-basics",{"title":683,"path":684,"stem":685},"Маршрутизація в ASP.NET Core: Розширені можливості","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frouting-advanced","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F06.routing-advanced",{"title":687,"path":688,"stem":689},"Статичні файли в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstatic-files","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F07.static-files",{"title":691,"path":692,"stem":693},"Статичні Активи: MapStaticAssets (ASP.NET Core 9.0)","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstatic-assets","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F08.static-assets",{"title":695,"path":696,"stem":697},"Конфігурація в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fconfiguration-fundamentals","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F09.configuration-fundamentals",{"title":699,"path":700,"stem":701},"Конфігурація: Паттерн Options","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fconfiguration-options","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F10.configuration-options",{"title":703,"path":704,"stem":705},"Логування в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Flogging-basics","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F11.logging-basics",{"title":707,"path":708,"stem":709},"Логування: Serilog та Middleware","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Flogging-advanced","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F12.logging-advanced",{"title":711,"path":712,"stem":713},"Управління станом: HttpContext.Items та Cookies","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstate-management","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F13.state-management",{"title":715,"path":716,"stem":717},"Стан сесії: Sessions","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fsession-state","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F14.session-state",{"title":719,"path":720,"stem":721},"Структура проєкту: від хаосу до архітектури","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fproject-structure","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F15.project-structure",{"title":723,"path":724,"stem":725},"Scalar у Minimal API: повний проєкт і Fluent OpenAPI","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fscalar-openapi-fluent","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F16.scalar-openapi-fluent",{"title":727,"path":728,"stem":729},"Swagger \u002F Swashbuckle у Minimal API: окремий класичний шлях","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fswagger-swashbuckle","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F17.swagger-swashbuckle",{"title":731,"icon":658,"path":732,"stem":733,"children":734,"page":59},"API","\u002Fcsharp\u002Faspnet\u002Fapi","01.csharp\u002F11.aspnet\u002F02.api",[735,739,743,747,751,755,759,763,767,771,775,779,783,787],{"title":736,"path":737,"stem":738},"Що таке API. Клієнт-серверна архітектура","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fwhat-is-api","01.csharp\u002F11.aspnet\u002F02.api\u002F01.what-is-api",{"title":740,"path":741,"stem":742},"Формати даних: JSON, XML, TOML та бінарні формати","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fdata-formats","01.csharp\u002F11.aspnet\u002F02.api\u002F02.data-formats",{"title":744,"path":745,"stem":746},"Парадигми API та концепція REST","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-paradigms-rest","01.csharp\u002F11.aspnet\u002F02.api\u002F03.api-paradigms-rest",{"title":748,"path":749,"stem":750},"HTTP-методи, статус-коди та заголовки","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fhttp-methods-status-codes","01.csharp\u002F11.aspnet\u002F02.api\u002F04.http-methods-status-codes",{"title":752,"path":753,"stem":754},"Організація HTTP API за принципами REST","\u002Fcsharp\u002Faspnet\u002Fapi\u002Frest-organizing","01.csharp\u002F11.aspnet\u002F02.api\u002F05.rest-organizing",{"title":756,"path":757,"stem":758},"Номенклатура URL та CRUD-операції","\u002Fcsharp\u002Faspnet\u002Fapi\u002Furl-nomenclature-crud","01.csharp\u002F11.aspnet\u002F02.api\u002F06.url-nomenclature-crud",{"title":760,"path":761,"stem":762},"Правила дизайну: іменування та стандарти","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-naming","01.csharp\u002F11.aspnet\u002F02.api\u002F07.api-design-naming",{"title":764,"path":765,"stem":766},"Валідація, ліміти та обробка помилок","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-validation","01.csharp\u002F11.aspnet\u002F02.api\u002F08.api-design-validation",{"title":768,"path":769,"stem":770},"Обробка помилок у Minimal API","\u002Fcsharp\u002Faspnet\u002Fapi\u002Ferror-handling-http","01.csharp\u002F11.aspnet\u002F02.api\u002F09.error-handling-http",{"title":772,"path":773,"stem":774},"Ідемпотентність та синхронізація стану","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fidempotency-sync","01.csharp\u002F11.aspnet\u002F02.api\u002F10.idempotency-sync",{"title":776,"path":777,"stem":778},"Пагінація та організація списків","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fpagination-lists","01.csharp\u002F11.aspnet\u002F02.api\u002F11.pagination-lists",{"title":780,"path":781,"stem":782},"Безпека API, кешування та інтернаціоналізація","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fsecurity-auth","01.csharp\u002F11.aspnet\u002F02.api\u002F12.security-auth",{"title":784,"path":785,"stem":786},"Процес проєктування API та документування","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-process","01.csharp\u002F11.aspnet\u002F02.api\u002F13.api-design-process",{"title":788,"path":789,"stem":790},"OpenAPI: контракт, специфікація та документація API","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fopenapi","01.csharp\u002F11.aspnet\u002F02.api\u002F14.openapi",{"title":792,"icon":793,"path":794,"stem":795,"children":796,"page":59},"Auth","i-lucide-shield-check","\u002Fcsharp\u002Faspnet\u002Fauth","01.csharp\u002F11.aspnet\u002F03.auth",[797,801,805,809,813,817,821,825,829,833,837,841,845,849,853,857,861,865],{"title":798,"path":799,"stem":800},"Основи аутентифікації та авторизації","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fauth-fundamentals","01.csharp\u002F11.aspnet\u002F03.auth\u002F01.auth-fundamentals",{"title":802,"path":803,"stem":804},"JWT-аутентифікація","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fjwt-authentication","01.csharp\u002F11.aspnet\u002F03.auth\u002F02.jwt-authentication",{"title":806,"path":807,"stem":808},"Авторизація: ролі, політики та resource-based доступ","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fauthorization-policies","01.csharp\u002F11.aspnet\u002F03.auth\u002F03.authorization-policies",{"title":810,"path":811,"stem":812},"Cookie-аутентифікація та ASP.NET Core Identity","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fcookie-auth-identity","01.csharp\u002F11.aspnet\u002F03.auth\u002F04.cookie-auth-identity",{"title":814,"path":815,"stem":816},"JWT + Refresh Tokens (HttpOnly Cookie)","\u002Fcsharp\u002Faspnet\u002Fauth\u002F04b.identity-auth-jwt","01.csharp\u002F11.aspnet\u002F03.auth\u002F04b.identity-auth-jwt",{"title":818,"path":819,"stem":820},"Identity: Підтвердження Email та Скидання Пароля","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-email-confirmation","01.csharp\u002F11.aspnet\u002F03.auth\u002F05.identity-email-confirmation",{"title":822,"path":823,"stem":824},"Identity: Двофакторна Аутентифікація (2FA)","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-two-factor","01.csharp\u002F11.aspnet\u002F03.auth\u002F06.identity-two-factor",{"title":826,"path":827,"stem":828},"Identity: Внутрішня Архітектура та Кастомізація","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-internals","01.csharp\u002F11.aspnet\u002F03.auth\u002F07.identity-internals",{"title":830,"path":831,"stem":832},"OAuth 2.0 та зовнішні провайдери","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foauth-external-providers","01.csharp\u002F11.aspnet\u002F03.auth\u002F08.oauth-external-providers",{"title":834,"path":835,"stem":836},"Безпека на практиці: CORS, HTTPS та захист від атак","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fsecurity-hardening","01.csharp\u002F11.aspnet\u002F03.auth\u002F09.security-hardening",{"title":838,"path":839,"stem":840},"Теорія OAuth 2.0: Поняття, Аналогії та Флоу","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foauth-theory","01.csharp\u002F11.aspnet\u002F03.auth\u002F10.oauth-theory",{"title":842,"path":843,"stem":844},"OIDC, OAuth 2.0 та Keycloak в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foidc-keycloak","01.csharp\u002F11.aspnet\u002F03.auth\u002F10.oidc-keycloak",{"title":846,"path":847,"stem":848},"API Keys аутентифікація в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fapi-keys","01.csharp\u002F11.aspnet\u002F03.auth\u002F11.api-keys",{"title":850,"path":851,"stem":852},"Rate Limiting та Throttling в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frate-limiting","01.csharp\u002F11.aspnet\u002F03.auth\u002F12.rate-limiting",{"title":854,"path":855,"stem":856},"Refresh Token Rotation в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frefresh-token-rotation","01.csharp\u002F11.aspnet\u002F03.auth\u002F13.refresh-token-rotation",{"title":858,"path":859,"stem":860},"Certificate Authentication та mTLS в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fcertificate-auth","01.csharp\u002F11.aspnet\u002F03.auth\u002F14.certificate-auth",{"title":862,"path":863,"stem":864},"RBAC, ABAC та ReBAC в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frbac-abac-rebac","01.csharp\u002F11.aspnet\u002F03.auth\u002F15.rbac-abac-rebac",{"title":866,"path":867,"stem":868},"Multi-tenancy та ізоляція даних в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fmulti-tenancy","01.csharp\u002F11.aspnet\u002F03.auth\u002F16.multi-tenancy",{"title":870,"icon":871,"path":872,"stem":873,"children":874,"page":59},"Нотифікації","i-lucide-bell","\u002Fcsharp\u002Faspnet\u002Fnotifications","01.csharp\u002F11.aspnet\u002F04.notifications",[875,879,883,887,891,895,899,903,907,911,915,919,923],{"title":876,"path":877,"stem":878},"In-App нотифікації через базу даних","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fin-app-database-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F01.in-app-database-notifications",{"title":880,"path":881,"stem":882},"Polling: Регулярний запит оновлень","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fpolling","01.csharp\u002F11.aspnet\u002F04.notifications\u002F02.polling",{"title":884,"path":885,"stem":886},"Server-Sent Events: Однострімовий push від сервера","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fserver-sent-events","01.csharp\u002F11.aspnet\u002F04.notifications\u002F03.server-sent-events",{"title":888,"path":889,"stem":890},"WebSockets: Двостороннє з'єднання в реальному часі","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fwebsockets","01.csharp\u002F11.aspnet\u002F04.notifications\u002F04.websockets",{"title":892,"path":893,"stem":894},"SignalR: Абстракція над транспортами реального часу","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fsignalr","01.csharp\u002F11.aspnet\u002F04.notifications\u002F05.signalr",{"title":896,"path":897,"stem":898},"Background Services: Фонові задачі в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fbackground-services","01.csharp\u002F11.aspnet\u002F04.notifications\u002F06.background-services",{"title":900,"path":901,"stem":902},"Web Push нотифікації","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fweb-push","01.csharp\u002F11.aspnet\u002F04.notifications\u002F07.web-push",{"title":904,"path":905,"stem":906},"Email нотифікації","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Femail-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F08.email-notifications",{"title":908,"path":909,"stem":910},"Порівняння підходів: Як вибрати правильну технологію нотифікацій","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fchoosing-the-right-approach","01.csharp\u002F11.aspnet\u002F04.notifications\u002F09.choosing-the-right-approach",{"title":912,"path":913,"stem":914},"Hangfire: Надійне планування фонових задач","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire","01.csharp\u002F11.aspnet\u002F04.notifications\u002F10.hangfire",{"title":916,"path":917,"stem":918},"Практика: Конвертація зображень у WebP через Hangfire","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire-image-webp","01.csharp\u002F11.aspnet\u002F04.notifications\u002F11.hangfire-image-webp",{"title":920,"path":921,"stem":922},"Практика: Підготовка відео до HLS-стрімінгу через Hangfire","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire-video-hls","01.csharp\u002F11.aspnet\u002F04.notifications\u002F12.hangfire-video-hls",{"title":924,"path":925,"stem":926},"Telegram-нотифікації: від одного повідомлення до масових розсилок і мульти-канального підходу","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Ftelegram-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F13.telegram-notifications",{"title":928,"icon":929,"path":930,"stem":931,"children":932,"page":59},"Інтернаціоналізація","i-lucide-languages","\u002Fcsharp\u002Faspnet\u002Fi18n","01.csharp\u002F11.aspnet\u002F05.i18n",[933,937],{"title":934,"path":935,"stem":936},"Інтернаціоналізація (i18n) у Minimal API: від A до Я","\u002Fcsharp\u002Faspnet\u002Fi18n\u002Finternationalization","01.csharp\u002F11.aspnet\u002F05.i18n\u002F01.internationalization",{"title":938,"path":939,"stem":940},"Humanizer: людиномовні рядки у .NET","\u002Fcsharp\u002Faspnet\u002Fi18n\u002Fhumanizer","01.csharp\u002F11.aspnet\u002F05.i18n\u002F02.humanizer",{"title":942,"icon":943,"path":944,"stem":945,"children":946,"page":59},"Кешування","i-lucide-layers","\u002Fcsharp\u002Faspnet\u002Fcaching","01.csharp\u002F11.aspnet\u002F06.caching",[947,951,955,959,963],{"title":948,"path":949,"stem":950},"Огляд кешування: чотири рівні і коли що обирати","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fcaching","01.csharp\u002F11.aspnet\u002F06.caching\u002F01.caching",{"title":952,"path":953,"stem":954},"IMemoryCache: кеш в оперативній пам'яті","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fmemory-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F02.memory-cache",{"title":956,"path":957,"stem":958},"IDistributedCache і Redis: розподілений кеш","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fdistributed-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F03.distributed-cache",{"title":960,"path":961,"stem":962},"Response Cache: HTTP-кешування через Cache-Control","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fresponse-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F04.response-cache",{"title":964,"path":965,"stem":966},"Output Cache: серверний кеш HTTP-відповідей (.NET 7+)","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Foutput-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F05.output-cache",{"title":968,"icon":969,"path":970,"stem":971,"children":972,"page":59},"Тестування","i-lucide-test-tube","\u002Fcsharp\u002Faspnet\u002Ftesting","01.csharp\u002F11.aspnet\u002F07.testing",[973,977,981,985,989,993,997,1001,1005,1009,1013,1017,1021,1025,1029,1033,1037,1041,1045,1049,1053],{"title":974,"path":975,"stem":976},"Що таке тестування? Від інтуїції до науки","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwhat-is-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F01.what-is-testing",{"title":978,"path":979,"stem":980},"Піраміда тестування — Стратегія, а не Догма","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-pyramid","01.csharp\u002F11.aspnet\u002F07.testing\u002F02.testing-pyramid",{"title":982,"path":983,"stem":984},"Дві Школи Тестування — Лондон проти Детройту","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-schools","01.csharp\u002F11.aspnet\u002F07.testing\u002F03.testing-schools",{"title":986,"path":987,"stem":988},"TDD та BDD — Тести як Дизайн-інструмент","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftdd-and-bdd","01.csharp\u002F11.aspnet\u002F07.testing\u002F04.tdd-and-bdd",{"title":990,"path":991,"stem":992},"Що саме тестувати — Техніки аналізу та Циклomatична складність","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwhat-to-test","01.csharp\u002F11.aspnet\u002F07.testing\u002F05.what-to-test",{"title":994,"path":995,"stem":996},"Тестові Фреймворки — Навіщо вони і що всередині","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftest-frameworks","01.csharp\u002F11.aspnet\u002F07.testing\u002F06.test-frameworks",{"title":998,"path":999,"stem":1000},"xUnit — Факти, Теорії та Lifecycle тестів","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fxunit-basics","01.csharp\u002F11.aspnet\u002F07.testing\u002F07.xunit-basics",{"title":1002,"path":1003,"stem":1004},"xUnit Advanced — Fixtures, Кастомізація та Розширення","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fxunit-advanced","01.csharp\u002F11.aspnet\u002F07.testing\u002F08.xunit-advanced",{"title":1006,"path":1007,"stem":1008},"Moq — Глибоке занурення в мокування","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fmocking-with-moq","01.csharp\u002F11.aspnet\u002F07.testing\u002F09.mocking-with-moq",{"title":1010,"path":1011,"stem":1012},"Тестування Баз Даних — EF Core, SQLite та Testcontainers","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fdatabase-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F10.database-testing",{"title":1014,"path":1015,"stem":1016},"Integration Testing — Частина 1 [Теорія та WebApplicationFactory]","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fintegration-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F11.integration-testing",{"title":1018,"path":1019,"stem":1020},"Інтеграційне тестування — Практика","\u002Fcsharp\u002Faspnet\u002Ftesting\u002F11a.integration-testing-practice","01.csharp\u002F11.aspnet\u002F07.testing\u002F11a.integration-testing-practice",{"title":1022,"path":1023,"stem":1024},"Integration Testing — Частина 2 [Просунуті Сценарії та Testcontainers]","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fintegration-testing-advanced","01.csharp\u002F11.aspnet\u002F07.testing\u002F12.integration-testing-advanced",{"title":1026,"path":1027,"stem":1028},"Професійний Postman: Колекції, Змінні та GitHub Інтеграція","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fpostman-professional","01.csharp\u002F11.aspnet\u002F07.testing\u002F13.postman-professional",{"title":1030,"path":1031,"stem":1032},"HttpClient у Тестах Частина 1: Архітектура та MockHttpMessageHandler","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fhttpclient-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F14.httpclient-testing",{"title":1034,"path":1035,"stem":1036},"HttpClient у Тестах Частина 2: WireMock.Net та Resilience","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwiremock-net","01.csharp\u002F11.aspnet\u002F07.testing\u002F15.wiremock-net",{"title":1038,"path":1039,"stem":1040},"Патерни та Анти-патерни Тестування: Test Smells","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-patterns","01.csharp\u002F11.aspnet\u002F07.testing\u002F16.testing-patterns",{"title":1042,"path":1043,"stem":1044},"Просунуті інструменти: Time, Snapshots та Властивості","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fadvanced-testing-tools","01.csharp\u002F11.aspnet\u002F07.testing\u002F17.advanced-testing-tools",{"title":1046,"path":1047,"stem":1048},"Тестування Архітектури з NetArchTest","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Farchitecture-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F18.architecture-testing",{"title":1050,"path":1051,"stem":1052},"Тестування Продуктивності: BenchmarkDotNet, NBomber та k6","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fperformance-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F19.performance-testing",{"title":1054,"path":1055,"stem":1056},"Залишок плану для курсу \"Тестування ASP.NET Minimal API\"","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fremaining_plan","01.csharp\u002F11.aspnet\u002F07.testing\u002Fremaining_plan",{"title":1058,"icon":1059,"path":1060,"stem":1061,"children":1062,"page":59},"Платежі","i-lucide-credit-card","\u002Fcsharp\u002Faspnet\u002Fpayments","01.csharp\u002F11.aspnet\u002F08.payments",[1063,1067,1071,1075,1079,1083,1087,1091,1095,1099,1103,1107],{"title":1064,"path":1065,"stem":1066},"Основи платіжної інфраструктури","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-fundamentals","01.csharp\u002F11.aspnet\u002F08.payments\u002F01.payment-fundamentals",{"title":1068,"path":1069,"stem":1070},"Методи оплати в Україні","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-methods-ukraine","01.csharp\u002F11.aspnet\u002F08.payments\u002F02.payment-methods-ukraine",{"title":1072,"path":1073,"stem":1074},"PCI DSS та безпека платежів","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpci-dss-security","01.csharp\u002F11.aspnet\u002F08.payments\u002F03.pci-dss-security",{"title":1076,"path":1077,"stem":1078},"Архітектура платіжної підсистеми","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-architecture","01.csharp\u002F11.aspnet\u002F08.payments\u002F04.payment-architecture",{"title":1080,"path":1081,"stem":1082},"Інтеграція LiqPay (ПриватБанк)","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fliqpay-integration","01.csharp\u002F11.aspnet\u002F08.payments\u002F05.liqpay-integration",{"title":1084,"path":1085,"stem":1086},"Інтеграція Monobank Acquiring API","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fmonobank-acquiring","01.csharp\u002F11.aspnet\u002F08.payments\u002F06.monobank-acquiring",{"title":1088,"path":1089,"stem":1090},"Інтеграція Stripe","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fstripe-integration","01.csharp\u002F11.aspnet\u002F08.payments\u002F07.stripe-integration",{"title":1092,"path":1093,"stem":1094},"Webhooks — глибоке занурення","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fwebhooks-deep-dive","01.csharp\u002F11.aspnet\u002F08.payments\u002F08.webhooks-deep-dive",{"title":1096,"path":1097,"stem":1098},"Підписки та рекурентні платежі","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fsubscriptions-recurring","01.csharp\u002F11.aspnet\u002F08.payments\u002F09.subscriptions-recurring",{"title":1100,"path":1101,"stem":1102},"Повернення коштів та диспути","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Frefunds-disputes","01.csharp\u002F11.aspnet\u002F08.payments\u002F10.refunds-disputes",{"title":1104,"path":1105,"stem":1106},"Тестування платіжних інтеграцій","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Ftesting-payments","01.csharp\u002F11.aspnet\u002F08.payments\u002F11.testing-payments",{"title":1108,"path":1109,"stem":1110},"Чекліст виходу в Production","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fproduction-checklist","01.csharp\u002F11.aspnet\u002F08.payments\u002F12.production-checklist",{"title":1112,"icon":1113,"items":1114,"path":1127,"stem":1128,"children":1129,"page":59},"Популярні бібліотеки","lucide:box",[1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126],"01.fluent-validation","02.mapster","03.erroror-result-pattern","04.serilog","05.mediatr","06.polly","07.health-checks","08.feature-management","09.fluent-email","10.quest-pdf","11.bogus","12.humanizer-guard","\u002Fcsharp\u002Faspnet\u002Flibraries","01.csharp\u002F11.aspnet\u002F09.libraries",[1130,1134,1138,1142,1146,1150,1154,1158,1162,1166,1170,1174,1178],{"title":1131,"path":1132,"stem":1133},"Валідація з FluentValidation в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffluent-validation","01.csharp\u002F11.aspnet\u002F09.libraries\u002F01.fluent-validation",{"title":1135,"path":1136,"stem":1137},"Маппінг об","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fmapster","01.csharp\u002F11.aspnet\u002F09.libraries\u002F02.mapster",{"title":1139,"path":1140,"stem":1141},"Обробка помилок з ErrorOr та Result Pattern в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ferroror-result-pattern","01.csharp\u002F11.aspnet\u002F09.libraries\u002F03.erroror-result-pattern",{"title":1143,"path":1144,"stem":1145},"Структуроване логування з Serilog в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fserilog","01.csharp\u002F11.aspnet\u002F09.libraries\u002F04.serilog",{"title":1147,"path":1148,"stem":1149},"CQRS та Mediator з MediatR в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fmediatr","01.csharp\u002F11.aspnet\u002F09.libraries\u002F05.mediatr",{"title":1151,"path":1152,"stem":1153},"Відмовостійкість з Polly в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fpolly","01.csharp\u002F11.aspnet\u002F09.libraries\u002F06.polly",{"title":1155,"path":1156,"stem":1157},"Health Checks в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fhealth-checks","01.csharp\u002F11.aspnet\u002F09.libraries\u002F07.health-checks",{"title":1159,"path":1160,"stem":1161},"Feature Management та Feature Flags в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffeature-management","01.csharp\u002F11.aspnet\u002F09.libraries\u002F08.feature-management",{"title":1163,"path":1164,"stem":1165},"Відправка Email з FluentEmail в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffluent-email","01.csharp\u002F11.aspnet\u002F09.libraries\u002F09.fluent-email",{"title":1167,"path":1168,"stem":1169},"Генерація PDF з QuestPDF в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fquest-pdf","01.csharp\u002F11.aspnet\u002F09.libraries\u002F10.quest-pdf",{"title":1171,"path":1172,"stem":1173},"Генерація тестових даних з Bogus в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fbogus","01.csharp\u002F11.aspnet\u002F09.libraries\u002F11.bogus",{"title":1175,"path":1176,"stem":1177},"Humanizer та Guard Clauses в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fhumanizer-guard","01.csharp\u002F11.aspnet\u002F09.libraries\u002F12.humanizer-guard",{"title":1179,"path":1180,"stem":1181},"План модуля 10.libraries — Популярні бібліотеки ASP.NET","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fplan","01.csharp\u002F11.aspnet\u002F09.libraries\u002Fplan",{"title":1183,"icon":1184,"path":1185,"stem":1186,"children":1187,"page":59},"Razor Pages","i-lucide-layout-template","\u002Fcsharp\u002Faspnet\u002Frazor-pages","01.csharp\u002F11.aspnet\u002F10.razor-pages",[1188,1192,1196,1200,1204,1208],{"title":1189,"path":1190,"stem":1191},"Від Minimal API до Razor Pages: концептуальний перехід","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Ffrom-minimal-api","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F01.from-minimal-api",{"title":1193,"path":1194,"stem":1195},"PageModel: логіка сторінки Razor Pages","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fpage-model","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F02.page-model",{"title":1197,"path":1198,"stem":1199},"Razor синтаксис: шаблонізатор у .cshtml","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Frazor-syntax","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F03.razor-syntax",{"title":1201,"path":1202,"stem":1203},"Tag Helpers: типізований HTML","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Ftag-helpers","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F04.tag-helpers",{"title":1205,"path":1206,"stem":1207},"Форми і валідація: повний цикл обробки даних","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fforms-validation","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F05.forms-validation",{"title":1209,"path":1210,"stem":1211},"Практичний проєкт: TaskManager на Razor Pages","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fproject-task-manager","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F06.project-task-manager",{"title":1213,"path":1214,"stem":1215,"children":1216,"page":59},"ASP.NET Core MVC","\u002Fcsharp\u002Faspnet\u002Fmvc","01.csharp\u002F11.aspnet\u002F11.mvc",[1217,1221,1225,1229,1233,1237,1241,1245,1249,1253,1257,1261,1265,1269,1273,1277,1281,1285],{"title":1218,"path":1219,"stem":1220},"Патерн MVC: архітектура, що змінила веб","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmvc-pattern","01.csharp\u002F11.aspnet\u002F11.mvc\u002F01.mvc-pattern",{"title":1222,"path":1223,"stem":1224},"Від Razor Pages до MVC: концептуальний перехід","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffrom-razor-pages","01.csharp\u002F11.aspnet\u002F11.mvc\u002F02.from-razor-pages",{"title":1226,"path":1227,"stem":1228},"Controllers та Actions: серце MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fcontrollers-actions","01.csharp\u002F11.aspnet\u002F11.mvc\u002F03.controllers-actions",{"title":1230,"path":1231,"stem":1232},"Маршрутизація в MVC: Convention vs Attribute Routing","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Frouting-mvc","01.csharp\u002F11.aspnet\u002F11.mvc\u002F04.routing-mvc",{"title":1234,"path":1235,"stem":1236},"Model Binding: від HTTP до C#","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmodel-binding","01.csharp\u002F11.aspnet\u002F11.mvc\u002F05.model-binding",{"title":1238,"path":1239,"stem":1240},"Views, ViewData, ViewBag, TempData і ViewModel","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fviews-viewdata-tempdata","01.csharp\u002F11.aspnet\u002F11.mvc\u002F06.views-viewdata-tempdata",{"title":1242,"path":1243,"stem":1244},"Filters: аспектно-орієнтоване програмування в MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffilters","01.csharp\u002F11.aspnet\u002F11.mvc\u002F07.filters",{"title":1246,"path":1247,"stem":1248},"Areas: структурування великих застосунків","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fareas","01.csharp\u002F11.aspnet\u002F11.mvc\u002F08.areas",{"title":1250,"path":1251,"stem":1252},"View Components: повторювані незалежні блоки UI","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fview-components","01.csharp\u002F11.aspnet\u002F11.mvc\u002F09.view-components",{"title":1254,"path":1255,"stem":1256},"Display та Editor Templates","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fdisplay-editor-templates","01.csharp\u002F11.aspnet\u002F11.mvc\u002F10.display-editor-templates",{"title":1258,"path":1259,"stem":1260},"Валідація: IValidatableObject та FluentValidation","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fvalidation-advanced","01.csharp\u002F11.aspnet\u002F11.mvc\u002F11.validation-advanced",{"title":1262,"path":1263,"stem":1264},"HTMX: інтерактивність через HTML-атрибути","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fhtmx","01.csharp\u002F11.aspnet\u002F11.mvc\u002F12.htmx",{"title":1266,"path":1267,"stem":1268},"HTMX у ASP.NET Core MVC: серверна інтеграція","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fajax-htmx-mvc","01.csharp\u002F11.aspnet\u002F11.mvc\u002F13.ajax-htmx-mvc",{"title":1270,"path":1271,"stem":1272},"Практичний проєкт: Каталог товарів з HTMX","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fhtmx-project","01.csharp\u002F11.aspnet\u002F11.mvc\u002F14.htmx-project",{"title":1274,"path":1275,"stem":1276},"Завантаження та обробка файлів","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffile-upload","01.csharp\u002F11.aspnet\u002F11.mvc\u002F15.file-upload",{"title":1278,"path":1279,"stem":1280},"Глобалізація та Локалізація MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fglobalization-localization","01.csharp\u002F11.aspnet\u002F11.mvc\u002F16.globalization-localization",{"title":1282,"path":1283,"stem":1284},"Підсумковий проєкт: Блог-платформа","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmvc-project","01.csharp\u002F11.aspnet\u002F11.mvc\u002F17.mvc-project",{"title":1286,"path":1287,"stem":1288},"План курсу: ASP.NET Core MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fplan","01.csharp\u002F11.aspnet\u002F11.mvc\u002Fplan",{"title":1290,"path":1291,"stem":1292,"children":1293,"page":59},"Web Api","\u002Fcsharp\u002Faspnet\u002Fweb-api","01.csharp\u002F11.aspnet\u002F12.web-api",[1294,1298,1302,1306,1310,1314,1318,1322,1326,1330,1334,1338,1342],{"title":1295,"path":1296,"stem":1297},"Від Minimal API до Controller-based API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Ffrom-minimal-api-to-controllers","01.csharp\u002F11.aspnet\u002F12.web-api\u002F01.from-minimal-api-to-controllers",{"title":1299,"path":1300,"stem":1301},"ControllerBase, ActionResult\u003CT> та Response Types","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fcontroller-base-actionresult","01.csharp\u002F11.aspnet\u002F12.web-api\u002F02.controller-base-actionresult",{"title":1303,"path":1304,"stem":1305},"Content Negotiation - JSON, XML та власні форматери","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fcontent-negotiation","01.csharp\u002F11.aspnet\u002F12.web-api\u002F03.content-negotiation",{"title":1307,"path":1308,"stem":1309},"Версіонування API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fapi-versioning","01.csharp\u002F11.aspnet\u002F12.web-api\u002F04.api-versioning",{"title":1311,"path":1312,"stem":1313},"ProblemDetails та структурована обробка помилок","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fproblemdetails-error-handling","01.csharp\u002F11.aspnet\u002F12.web-api\u002F05.problemdetails-error-handling",{"title":1315,"path":1316,"stem":1317},"Фільтри у Web API контексті","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Ffilters-for-api","01.csharp\u002F11.aspnet\u002F12.web-api\u002F06.filters-for-api",{"title":1319,"path":1320,"stem":1321},"Пагінація, фільтрація та сортування","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fpagination-filtering-sorting","01.csharp\u002F11.aspnet\u002F12.web-api\u002F07.pagination-filtering-sorting",{"title":1323,"path":1324,"stem":1325},"HATEOAS та Resource Expansion","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fhateoas-resource-expansion","01.csharp\u002F11.aspnet\u002F12.web-api\u002F08.hateoas-resource-expansion",{"title":1327,"path":1328,"stem":1329},"Гібридна архітектура - Minimal API + Controllers","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fminimal-api-vs-controllers-hybrid","01.csharp\u002F11.aspnet\u002F12.web-api\u002F09.minimal-api-vs-controllers-hybrid",{"title":1331,"path":1332,"stem":1333},"Документація API - Swashbuckle, NSwag та генерація клієнтів","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fapi-documentation-generation","01.csharp\u002F11.aspnet\u002F12.web-api\u002F10.api-documentation-generation",{"title":1335,"path":1336,"stem":1337},"Health Checks та моніторинг API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fhealth-checks-monitoring","01.csharp\u002F11.aspnet\u002F12.web-api\u002F11.health-checks-monitoring",{"title":1339,"path":1340,"stem":1341},"Підсумковий проєкт - Production-Ready REST API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fweb-api-project","01.csharp\u002F11.aspnet\u002F12.web-api\u002F12.web-api-project",{"title":1343,"path":1344,"stem":1345},"План курсу: ASP.NET Core Web API (Controllers)","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fplan","01.csharp\u002F11.aspnet\u002F12.web-api\u002Fplan",{"title":1347,"icon":1348,"path":1349,"stem":1350,"children":1351,"page":59},"Моніторинг","i-lucide-activity","\u002Fcsharp\u002Faspnet\u002Fmonitoring","01.csharp\u002F11.aspnet\u002F13.monitoring",[1352,1356,1360],{"title":1353,"path":1354,"stem":1355},"Спостережуваність: від console.log до production-систем","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fobservability-intro","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F01.observability-intro",{"title":1357,"path":1358,"stem":1359},"Health Checks: перший рівень observability","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fhealth-checks","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F02.health-checks",{"title":1361,"path":1362,"stem":1363},"Вбудовані метрики .NET 10 та System.Diagnostics.Metrics","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fdotnet-metrics","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F03.dotnet-metrics",{"title":1365,"icon":1366,"path":1367,"stem":1368,"children":1369,"page":59},"Desktop UI","i-lucide-app-window","\u002Fcsharp\u002Fdesktop-ui","01.csharp\u002F12.desktop-ui",[1370,1374,1378,1382,1386,1390,1394,1398,1402,1406,1410,1414,1418,1422,1426,1430,1434,1438,1442,1446,1450,1454,1458,1462,1466,1470,1474,1478,1482,1486,1490,1494,1498,1502,1506,1510,1514,1518,1522,1526,1530,1534,1538,1542,1546,1550,1554,1558,1562,1566,1570,1574,1578,1582,1586,1590,1594,1598,1602,1606,1610,1614,1618,1622,1626,1630,1634,1638,1642,1646,1650],{"title":1371,"path":1372,"stem":1373},"Що таке десктопна розробка?","\u002Fcsharp\u002Fdesktop-ui\u002Fwhat-is-desktop-dev","01.csharp\u002F12.desktop-ui\u002F01.what-is-desktop-dev",{"title":1375,"path":1376,"stem":1377},"Архітектура WPF — як влаштований графічний інтерфейс","\u002Fcsharp\u002Fdesktop-ui\u002Fwpf-architecture","01.csharp\u002F12.desktop-ui\u002F02.wpf-architecture",{"title":1379,"path":1380,"stem":1381},"Перший WPF-проєкт — від нуля до вікна","\u002Fcsharp\u002Fdesktop-ui\u002Ffirst-wpf-app","01.csharp\u002F12.desktop-ui\u002F03.first-wpf-app",{"title":1383,"path":1384,"stem":1385},"Перший Avalonia-проєкт: WPF для всіх платформ","\u002Fcsharp\u002Fdesktop-ui\u002F03a.first-avalonia-app","01.csharp\u002F12.desktop-ui\u002F03a.first-avalonia-app",{"title":1387,"path":1388,"stem":1389},"XAML: декларативний інтерфейс","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-basics","01.csharp\u002F12.desktop-ui\u002F04.xaml-basics",{"title":1391,"path":1392,"stem":1393},"Fluent UI у WPF — сучасний дизайн Windows 11","\u002Fcsharp\u002Fdesktop-ui\u002F04a.wpf-fluent-ui","01.csharp\u002F12.desktop-ui\u002F04a.wpf-fluent-ui",{"title":1395,"path":1396,"stem":1397},"WPF UI — сучасна бібліотека Fluent контролів","\u002Fcsharp\u002Fdesktop-ui\u002F04b.wpf-ui-library","01.csharp\u002F12.desktop-ui\u002F04b.wpf-ui-library",{"title":1399,"path":1400,"stem":1401},"HandyControl — велика бібліотека UI контролів для WPF","\u002Fcsharp\u002Fdesktop-ui\u002F04c.handycontrol-library","01.csharp\u002F12.desktop-ui\u002F04c.handycontrol-library",{"title":1403,"path":1404,"stem":1405},"Простори імен та ресурси XAML","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-namespaces-resources","01.csharp\u002F12.desktop-ui\u002F05.xaml-namespaces-resources",{"title":1407,"path":1408,"stem":1409},"XAML в Avalonia: ключові відмінності від WPF","\u002Fcsharp\u002Fdesktop-ui\u002F05a.avalonia-xaml-differences","01.csharp\u002F12.desktop-ui\u002F05a.avalonia-xaml-differences",{"title":1411,"path":1412,"stem":1413},"Розширення розмітки XAML (Markup Extensions)","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-markup-extensions","01.csharp\u002F12.desktop-ui\u002F06.xaml-markup-extensions",{"title":1415,"path":1416,"stem":1417},"Панелі Layout: StackPanel, WrapPanel, DockPanel","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-panels-part1","01.csharp\u002F12.desktop-ui\u002F07.layout-panels-part1",{"title":1419,"path":1420,"stem":1421},"Grid, Canvas, UniformGrid","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-panels-part2","01.csharp\u002F12.desktop-ui\u002F07.layout-panels-part2",{"title":1423,"path":1424,"stem":1425},"Просунуті техніки Layout","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-advanced","01.csharp\u002F12.desktop-ui\u002F08.layout-advanced",{"title":1427,"path":1428,"stem":1429},"Адаптивний Layout та найкращі практики","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-responsive","01.csharp\u002F12.desktop-ui\u002F09.layout-responsive",{"title":1431,"path":1432,"stem":1433},"Layout в Avalonia: відмінності та нові можливості","\u002Fcsharp\u002Fdesktop-ui\u002F09a.layout-avalonia","01.csharp\u002F12.desktop-ui\u002F09a.layout-avalonia",{"title":1435,"path":1436,"stem":1437},"Button, Image, ProgressBar та інші базові контроли","\u002Fcsharp\u002Fdesktop-ui\u002Fbasic-controls","01.csharp\u002F12.desktop-ui\u002F10.basic-controls",{"title":1439,"path":1440,"stem":1441},"Контроли в Avalonia: відмінності від WPF","\u002Fcsharp\u002Fdesktop-ui\u002F10a.controls-avalonia","01.csharp\u002F12.desktop-ui\u002F10a.controls-avalonia",{"title":1443,"path":1444,"stem":1445},"Текстові контроли — TextBlock, TextBox, RichTextBox","\u002Fcsharp\u002Fdesktop-ui\u002Ftext-controls","01.csharp\u002F12.desktop-ui\u002F11.text-controls",{"title":1447,"path":1448,"stem":1449},"Контроли вибору — CheckBox, RadioButton, ComboBox, ListBox, DatePicker","\u002Fcsharp\u002Fdesktop-ui\u002Fselection-controls","01.csharp\u002F12.desktop-ui\u002F12.selection-controls",{"title":1451,"path":1452,"stem":1453},"Content Model — GroupBox, Expander, TabControl, StatusBar","\u002Fcsharp\u002Fdesktop-ui\u002Fcontent-controls","01.csharp\u002F12.desktop-ui\u002F13.content-controls",{"title":1455,"path":1456,"stem":1457},"UI\u002FUX принципи десктопних застосунків","\u002Fcsharp\u002Fdesktop-ui\u002F13a.ui-ux-principles","01.csharp\u002F12.desktop-ui\u002F13a.ui-ux-principles",{"title":1459,"path":1460,"stem":1461},"Dependency Properties — Концепція та Value Resolution","\u002Fcsharp\u002Fdesktop-ui\u002Fdependency-properties-part1","01.csharp\u002F12.desktop-ui\u002F14.dependency-properties-part1",{"title":1463,"path":1464,"stem":1465},"Avalonia Property System — StyledProperty та DirectProperty","\u002Fcsharp\u002Fdesktop-ui\u002F14a.avalonia-property-system","01.csharp\u002F12.desktop-ui\u002F14a.avalonia-property-system",{"title":1467,"path":1468,"stem":1469},"Attached Properties — Властивості без меж","\u002Fcsharp\u002Fdesktop-ui\u002Fattached-properties","01.csharp\u002F12.desktop-ui\u002F15.attached-properties",{"title":1471,"path":1472,"stem":1473},"Routed Events — Маршрутизація подій у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Frouted-events","01.csharp\u002F12.desktop-ui\u002F16.routed-events",{"title":1475,"path":1476,"stem":1477},"Data Binding — Від Code-Behind до Декларативності","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-basics-part1","01.csharp\u002F12.desktop-ui\u002F17.data-binding-basics-part1",{"title":1479,"path":1480,"stem":1481},"INotifyPropertyChanged — Живе оновлення UI","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-basics-part2","01.csharp\u002F12.desktop-ui\u002F17.data-binding-basics-part2",{"title":1483,"path":1484,"stem":1485},"Compiled Bindings в Avalonia — Безпека на етапі компіляції","\u002Fcsharp\u002Fdesktop-ui\u002F17a.avalonia-compiled-bindings","01.csharp\u002F12.desktop-ui\u002F17a.avalonia-compiled-bindings",{"title":1487,"path":1488,"stem":1489},"Просунутий Data Binding — ElementName, RelativeSource, MultiBinding","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-advanced","01.csharp\u002F12.desktop-ui\u002F18.data-binding-advanced",{"title":1491,"path":1492,"stem":1493},"Value Converters — Перетворення типів даних у Data Binding","\u002Fcsharp\u002Fdesktop-ui\u002Fvalue-converters","01.csharp\u002F12.desktop-ui\u002F19.value-converters",{"title":1495,"path":1496,"stem":1497},"Data Templates — Візуалізація об'єктів у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-templates","01.csharp\u002F12.desktop-ui\u002F20.data-templates",{"title":1499,"path":1500,"stem":1501},"Collections Binding Part 1 — ObservableCollection та ItemsControl","\u002Fcsharp\u002Fdesktop-ui\u002Fcollections-binding-part1","01.csharp\u002F12.desktop-ui\u002F21.collections-binding-part1",{"title":1503,"path":1504,"stem":1505},"Collections Binding Part 2 — ICollectionView, Filtering, Sorting та Virtualization","\u002Fcsharp\u002Fdesktop-ui\u002Fcollections-binding-part2","01.csharp\u002F12.desktop-ui\u002F21.collections-binding-part2",{"title":1507,"path":1508,"stem":1509},"MVVM Pattern — Від Spaghetti Code до архітектури","\u002Fcsharp\u002Fdesktop-ui\u002Fmvvm-pattern","01.csharp\u002F12.desktop-ui\u002F22.mvvm-pattern",{"title":1511,"path":1512,"stem":1513},"ViewModel Implementation — Від BaseViewModel до валідації","\u002Fcsharp\u002Fdesktop-ui\u002Fviewmodel-implementation","01.csharp\u002F12.desktop-ui\u002F23.viewmodel-implementation",{"title":1515,"path":1516,"stem":1517},"Commands — Від event handlers до декларативних команд","\u002Fcsharp\u002Fdesktop-ui\u002Fcommands","01.csharp\u002F12.desktop-ui\u002F24.commands",{"title":1519,"path":1520,"stem":1521},"MVVM Toolkit — MVVM без boilerplate через Source Generators","\u002Fcsharp\u002Fdesktop-ui\u002Fmvvm-toolkit","01.csharp\u002F12.desktop-ui\u002F25.mvvm-toolkit",{"title":1523,"path":1524,"stem":1525},"Messenger Pattern — Комунікація між ViewModel без прямих посилань","\u002Fcsharp\u002Fdesktop-ui\u002Fmessenger-pattern","01.csharp\u002F12.desktop-ui\u002F26.messenger-pattern",{"title":1527,"path":1528,"stem":1529},"Стилі WPF — CSS для десктопу","\u002Fcsharp\u002Fdesktop-ui\u002Fstyles-basics","01.csharp\u002F12.desktop-ui\u002F27.styles-basics",{"title":1531,"path":1532,"stem":1533},"CSS-like стилі Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002F27a.avalonia-css-styling","01.csharp\u002F12.desktop-ui\u002F27a.avalonia-css-styling",{"title":1535,"path":1536,"stem":1537},"Control Templates — Частина 1. Концепція та TemplateBinding","\u002Fcsharp\u002Fdesktop-ui\u002Fcontrol-templates-part1","01.csharp\u002F12.desktop-ui\u002F28.control-templates-part1",{"title":1539,"path":1540,"stem":1541},"Control Templates — Частина 2. Named Parts та ContentPresenter","\u002Fcsharp\u002Fdesktop-ui\u002Fcontrol-templates-part2","01.csharp\u002F12.desktop-ui\u002F28.control-templates-part2",{"title":1543,"path":1544,"stem":1545},"Control Themes в Avalonia — нова ера стилізації","\u002Fcsharp\u002Fdesktop-ui\u002F28a.avalonia-control-themes","01.csharp\u002F12.desktop-ui\u002F28a.avalonia-control-themes",{"title":1547,"path":1548,"stem":1549},"Triggers та Visual State Manager у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Ftriggers-visual-states","01.csharp\u002F12.desktop-ui\u002F29.triggers-visual-states",{"title":1551,"path":1552,"stem":1553},"Pseudo-classes в Avalonia — замість WPF Triggers","\u002Fcsharp\u002Fdesktop-ui\u002F29a.avalonia-pseudo-classes","01.csharp\u002F12.desktop-ui\u002F29a.avalonia-pseudo-classes",{"title":1555,"path":1556,"stem":1557},"Теми та ресурсні словники у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fresources-themes","01.csharp\u002F12.desktop-ui\u002F30.resources-themes",{"title":1559,"path":1560,"stem":1561},"Avalonia Themes — Fluent Design та система тематизації","\u002Fcsharp\u002Fdesktop-ui\u002F30a.avalonia-themes-fluent","01.csharp\u002F12.desktop-ui\u002F30a.avalonia-themes-fluent",{"title":1563,"path":1564,"stem":1565},"Контроли колекцій — глибоке занурення","\u002Fcsharp\u002Fdesktop-ui\u002Fcollection-controls","01.csharp\u002F12.desktop-ui\u002F31.collection-controls",{"title":1567,"path":1568,"stem":1569},"DataGrid — колонки та базове відображення","\u002Fcsharp\u002Fdesktop-ui\u002Fdatagrid-part1","01.csharp\u002F12.desktop-ui\u002F32.datagrid-part1",{"title":1571,"path":1572,"stem":1573},"DataGrid — сортування, фільтрація, редагування","\u002Fcsharp\u002Fdesktop-ui\u002Fdatagrid-part2","01.csharp\u002F12.desktop-ui\u002F32.datagrid-part2",{"title":1575,"path":1576,"stem":1577},"TreeView та GridView","\u002Fcsharp\u002Fdesktop-ui\u002Ftreeview-listview","01.csharp\u002F12.desktop-ui\u002F33.treeview-listview",{"title":1579,"path":1580,"stem":1581},"Меню, Toolbar, ContextMenu, StatusBar","\u002Fcsharp\u002Fdesktop-ui\u002Fmenus-toolbars","01.csharp\u002F12.desktop-ui\u002F34.menus-toolbars",{"title":1583,"path":1584,"stem":1585},"Навігація та керування вікнами. Частина 1: вікна та сторінки","\u002Fcsharp\u002Fdesktop-ui\u002Fnavigation-windows-part1","01.csharp\u002F12.desktop-ui\u002F35.navigation-windows-part1",{"title":1587,"path":1588,"stem":1589},"Навігація та керування вікнами. Частина 2: MVVM-навігація","\u002Fcsharp\u002Fdesktop-ui\u002Fnavigation-windows-part2","01.csharp\u002F12.desktop-ui\u002F35.navigation-windows-part2",{"title":1591,"path":1592,"stem":1593},"Avalonia — Навігація та діалоги","\u002Fcsharp\u002Fdesktop-ui\u002F35a.avalonia-navigation-dialogs","01.csharp\u002F12.desktop-ui\u002F35a.avalonia-navigation-dialogs",{"title":1595,"path":1596,"stem":1597},"Діалоги та File Pickers у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fdialogs-file-pickers","01.csharp\u002F12.desktop-ui\u002F36.dialogs-file-pickers",{"title":1599,"path":1600,"stem":1601},"UserControl: компонентний підхід у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fuser-controls","01.csharp\u002F12.desktop-ui\u002F37.user-controls",{"title":1603,"path":1604,"stem":1605},"Custom Controls: Lookless Controls у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fcustom-controls","01.csharp\u002F12.desktop-ui\u002F38.custom-controls",{"title":1607,"path":1608,"stem":1609},"Avalonia TemplatedControl — Lookless Controls","\u002Fcsharp\u002Fdesktop-ui\u002F38a.avalonia-templated-controls","01.csharp\u002F12.desktop-ui\u002F38a.avalonia-templated-controls",{"title":1611,"path":1612,"stem":1613},"Анімації у WPF: Storyboard та Easing Functions","\u002Fcsharp\u002Fdesktop-ui\u002Fanimations-transitions","01.csharp\u002F12.desktop-ui\u002F39.animations-transitions",{"title":1615,"path":1616,"stem":1617},"Анімації в Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002F39a.avalonia-animations","01.csharp\u002F12.desktop-ui\u002F39a.avalonia-animations",{"title":1619,"path":1620,"stem":1621},"2D Графіка та Мультимедіа у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fmedia-graphics","01.csharp\u002F12.desktop-ui\u002F40.media-graphics",{"title":1623,"path":1624,"stem":1625},"Dependency Injection у WPF та Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002Fdi-integration","01.csharp\u002F12.desktop-ui\u002F41.di-integration",{"title":1627,"path":1628,"stem":1629},"SQLite та EF Core у десктопних додатках","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-persistence-part1","01.csharp\u002F12.desktop-ui\u002F42.data-persistence-part1",{"title":1631,"path":1632,"stem":1633},"Repository Pattern та Unit of Work","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-persistence-part2","01.csharp\u002F12.desktop-ui\u002F43.data-persistence-part2",{"title":1635,"path":1636,"stem":1637},"Тестування ViewModels","\u002Fcsharp\u002Fdesktop-ui\u002Fviewmodel-testing","01.csharp\u002F12.desktop-ui\u002F44.viewmodel-testing",{"title":1639,"path":1640,"stem":1641},"Avalonia Headless Testing — тестування UI без вікон","\u002Fcsharp\u002Fdesktop-ui\u002F44a.avalonia-headless-testing","01.csharp\u002F12.desktop-ui\u002F44a.avalonia-headless-testing",{"title":1643,"path":1644,"stem":1645},"Кросплатформна розробка з Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002Favalonia-cross-platform","01.csharp\u002F12.desktop-ui\u002F45.avalonia-cross-platform",{"title":1647,"path":1648,"stem":1649},"Пакування та розгортання Avalonia додатків","\u002Fcsharp\u002Fdesktop-ui\u002Favalonia-packaging-deployment","01.csharp\u002F12.desktop-ui\u002F46.avalonia-packaging-deployment",{"title":1651,"path":1652,"stem":1653},"Розгортання WPF застосунків","\u002Fcsharp\u002Fdesktop-ui\u002Fwpf-packaging-deployment","01.csharp\u002F12.desktop-ui\u002F47.wpf-packaging-deployment",{"title":1655,"icon":658,"path":1656,"stem":1657,"children":1658,"page":59},"Network Programming","\u002Fcsharp\u002Fnetwork-programming","01.csharp\u002F13.network-programming",[1659,1663,1667,1671,1675,1679,1683,1687,1691,1695,1699],{"title":1660,"path":1661,"stem":1662},"Основи комп'ютерних мереж","\u002Fcsharp\u002Fnetwork-programming\u002Ffoundations","01.csharp\u002F13.network-programming\u002F01.foundations",{"title":1664,"path":1665,"stem":1666},"Модель OSI та стек TCP\u002FIP","\u002Fcsharp\u002Fnetwork-programming\u002Fosi-model","01.csharp\u002F13.network-programming\u002F02.osi-model",{"title":1668,"path":1669,"stem":1670},"IP-протокол та адресація","\u002Fcsharp\u002Fnetwork-programming\u002Fip-addressing","01.csharp\u002F13.network-programming\u002F03.ip-addressing",{"title":1672,"path":1673,"stem":1674},"UDP — протокол без з'єднання","\u002Fcsharp\u002Fnetwork-programming\u002Fudp","01.csharp\u002F13.network-programming\u002F05.udp",{"title":1676,"path":1677,"stem":1678},"UDP Broadcast та Multicast","\u002Fcsharp\u002Fnetwork-programming\u002Fudp-broadcast-multicast","01.csharp\u002F13.network-programming\u002F06.udp-broadcast-multicast",{"title":1680,"path":1681,"stem":1682},"HTTP — протокол вебу","\u002Fcsharp\u002Fnetwork-programming\u002Fhttp-fundamentals","01.csharp\u002F13.network-programming\u002F07.http-fundamentals",{"title":1684,"path":1685,"stem":1686},"HttpListener — вбудований HTTP-сервер .NET","\u002Fcsharp\u002Fnetwork-programming\u002F07a.http-listener","01.csharp\u002F13.network-programming\u002F07a.http-listener",{"title":1688,"path":1689,"stem":1690},"HTTP Advanced — cookies, аутентифікація та HTTPS","\u002Fcsharp\u002Fnetwork-programming\u002Fhttp-advanced","01.csharp\u002F13.network-programming\u002F08.http-advanced",{"title":1692,"path":1693,"stem":1694},"SMTP та протоколи електронної пошти","\u002Fcsharp\u002Fnetwork-programming\u002Fsmtp","01.csharp\u002F13.network-programming\u002F09.smtp",{"title":1696,"path":1697,"stem":1698},"WebSocket — повнодуплексний протокол реального часу","\u002Fcsharp\u002Fnetwork-programming\u002Fwebsockets","01.csharp\u002F13.network-programming\u002F10.websockets",{"title":1700,"path":1701,"stem":1702},"TLS\u002FSSL — криптографічний захист мережевих з'єднань","\u002Fcsharp\u002Fnetwork-programming\u002Ftls-ssl","01.csharp\u002F13.network-programming\u002F11.tls-ssl",{"title":1704,"path":1705,"stem":1706},"C# & .NET: The Ultimate Roadmap","\u002Fcsharp\u002Froadmap","01.csharp\u002Froadmap",{"title":1708,"icon":1709,"path":1710,"stem":1711,"children":1712,"page":59},"C++","i-devicon-cplusplus","\u002Fcpp","02.cpp",[1713,1717,1721,1725,1729,1733,1737,1741,1745,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908],{"title":1714,"path":1715,"stem":1716},"Вступ у програмування та алгоритми","\u002Fcpp\u002Fintro-algorithms","02.cpp\u002F01.intro-algorithms",{"title":1718,"path":1719,"stem":1720},"Code Style: угоди про оформлення коду","\u002Fcpp\u002Fcode-style","02.cpp\u002F02.code-style",{"title":1722,"path":1723,"stem":1724},"Середовище розробки та перший проєкт","\u002Fcpp\u002Fide-setup","02.cpp\u002F03.ide-setup",{"title":1726,"path":1727,"stem":1728},"Вивід даних на екран","\u002Fcpp\u002Fdata-output","02.cpp\u002F04.data-output",{"title":1730,"path":1731,"stem":1732},"Типи даних, змінні та константи","\u002Fcpp\u002Fdata-types-variables","02.cpp\u002F05.data-types-variables",{"title":1734,"path":1735,"stem":1736},"Ввід даних з клавіатури","\u002Fcpp\u002Fdata-input","02.cpp\u002F06.data-input",{"title":1738,"path":1739,"stem":1740},"Оператори, перетворення типів та логічні операції","\u002Fcpp\u002Foperators-type-conversion","02.cpp\u002F07.operators-type-conversion",{"title":1742,"path":1743,"stem":1744},"Цикли","\u002Fcpp\u002Floops","02.cpp\u002F08.loops",{"title":32,"path":1746,"stem":1747},"\u002Fcpp\u002Farrays","02.cpp\u002F09.arrays",{"title":1749,"path":1750,"stem":1751},"Алгоритми сортування та аналіз складності","\u002Fcpp\u002Fsorting","02.cpp\u002F10.sorting",{"title":1753,"path":1754,"stem":1755},"Алгоритми пошуку","\u002Fcpp\u002Fsearching","02.cpp\u002F11.searching",{"title":1757,"path":1758,"stem":1759},"Функції: основи","\u002Fcpp\u002Ffunctions-basics","02.cpp\u002F12.functions-basics",{"title":1761,"path":1762,"stem":1763},"Функції: прототипи, область видимості та додаткові можливості","\u002Fcpp\u002Ffunctions-scope","02.cpp\u002F13.functions-scope",{"title":1765,"path":1766,"stem":1767},"Функції: перевантаження та шаблони","\u002Fcpp\u002Ffunctions-overloading-templates","02.cpp\u002F14.functions-overloading-templates",{"title":1769,"path":1770,"stem":1771},"Вказівники: основи","\u002Fcpp\u002Fpointers-basics","02.cpp\u002F15.pointers-basics",{"title":1773,"path":1774,"stem":1775},"Посилання (References)","\u002Fcpp\u002Freferences","02.cpp\u002F16.references",{"title":1777,"path":1778,"stem":1779},"Вказівники, const і масиви","\u002Fcpp\u002Fpointers-const-arrays","02.cpp\u002F17.pointers-const-arrays",{"title":1781,"path":1782,"stem":1783},"Адресна арифметика","\u002Fcpp\u002Fpointer-arithmetic","02.cpp\u002F18.pointer-arithmetic",{"title":1785,"path":1786,"stem":1787},"Динамічна пам'ять","\u002Fcpp\u002Fdynamic-memory","02.cpp\u002F19.dynamic-memory",{"title":1789,"path":1790,"stem":1791},"Вказівники типу void","\u002Fcpp\u002Fvoid-pointers","02.cpp\u002F20.void-pointers",{"title":1793,"path":1794,"stem":1795},"Вказівники на вказівники","\u002Fcpp\u002Fpointers-to-pointers","02.cpp\u002F21.pointers-to-pointers",{"title":1797,"path":1798,"stem":1799},"Оператор доступу до членів через вказівник (->)","\u002Fcpp\u002Fmember-access-operator","02.cpp\u002F22.member-access-operator",{"title":1801,"path":1802,"stem":1803},"Цикл for-each (Range-based for)","\u002Fcpp\u002Fforeach-loop","02.cpp\u002F23.foreach-loop",{"title":1805,"path":1806,"stem":1807},"Вказівники на функції","\u002Fcpp\u002Ffunction-pointers","02.cpp\u002F24.function-pointers",{"title":1809,"path":1810,"stem":1811},"Лямбда-вирази","\u002Fcpp\u002Flambdas","02.cpp\u002F25.lambdas",{"title":1813,"path":1814,"stem":1815},"Лямбда-захоплення","\u002Fcpp\u002Flambda-captures","02.cpp\u002F26.lambda-captures",{"title":1817,"path":1818,"stem":1819},"Еліпсис","\u002Fcpp\u002Fellipsis","02.cpp\u002F27.ellipsis",{"title":1821,"path":1822,"stem":1823},"Безпечні альтернативи еліпсису","\u002Fcpp\u002F27a.ellipsis","02.cpp\u002F27a.ellipsis",{"title":1825,"path":1826,"stem":1827},"Аргументи командного рядка","\u002Fcpp\u002Fcommand-line-arguments","02.cpp\u002F28.command-line-arguments",{"title":1829,"path":1830,"stem":1831},"Перерахування (enum)","\u002Fcpp\u002Fenum","02.cpp\u002F29.enum",{"title":1833,"path":1834,"stem":1835},"Класи-перерахування (enum class)","\u002Fcpp\u002Fenum-class","02.cpp\u002F30.enum-class",{"title":1837,"path":1838,"stem":1839},"Псевдоніми типів (typedef і using)","\u002Fcpp\u002Ftype-aliases","02.cpp\u002F31.type-aliases",{"title":1841,"path":1842,"stem":1843},"Системи числення та двійкова арифметика","\u002Fcpp\u002Fnumber-systems","02.cpp\u002F32.number-systems",{"title":1845,"path":1846,"stem":1847},"Структури (struct): агрегування даних","\u002Fcpp\u002Fstruct","02.cpp\u002F33.struct",{"title":1849,"path":1850,"stem":1851},"Структури у функціях","\u002Fcpp\u002Fstruct-functions","02.cpp\u002F34.struct-functions",{"title":1853,"path":1854,"stem":1855},"Масиви структур і вкладені структури","\u002Fcpp\u002Fstruct-arrays","02.cpp\u002F35.struct-arrays",{"title":1857,"path":1858,"stem":1859},"Патерни struct та межі застосування","\u002Fcpp\u002Fstruct-patterns","02.cpp\u002F36.struct-patterns",{"title":1861,"path":1862,"stem":1863},"Символи та таблиця ASCII","\u002Fcpp\u002Fascii-characters","02.cpp\u002F37.ascii-characters",{"title":1865,"path":1866,"stem":1867},"Unicode та кодування UTF","\u002Fcpp\u002Funicode-utf","02.cpp\u002F38.unicode-utf",{"title":1869,"path":1870,"stem":1871},"C-style рядки","\u002Fcpp\u002Fc-strings","02.cpp\u002F39.c-strings",{"title":1873,"path":1874,"stem":1875},"Вступ до std::string","\u002Fcpp\u002Fstd-string-intro","02.cpp\u002F40.std-string-intro",{"title":1877,"path":1878,"stem":1879},"Довжина, ємність та доступ до символів std::string","\u002Fcpp\u002Fstd-string-capacity-access","02.cpp\u002F41.std-string-capacity-access",{"title":1881,"path":1882,"stem":1883},"Модифікація std::string: присвоювання, додавання, вставка, видалення та заміна","\u002Fcpp\u002Fstd-string-modification","02.cpp\u002F42.std-string-modification",{"title":1885,"path":1886,"stem":1887},"Пошук у std::string: find, npos та практичні патерни","\u002Fcpp\u002Fstd-string-search","02.cpp\u002F43.std-string-search",{"title":1889,"path":1890,"stem":1891},"std::string_view: невласницький погляд на рядок без копіювання","\u002Fcpp\u002Fstd-string-view","02.cpp\u002F44.std-string-view",{"title":1893,"path":1894,"stem":1895},"Об'єднання (union): один блок пам'яті, кілька інтерпретацій","\u002Fcpp\u002Funion","02.cpp\u002F45.union",{"title":1897,"path":1898,"stem":1899},"Організація коду: файли, препроцесор, простори імен","\u002Fcpp\u002Fmultifile-programs","02.cpp\u002F46.multifile-programs",{"title":1901,"path":1902,"stem":1903},"Робота з файлами: C-стиль (stdio.h)","\u002Fcpp\u002Fc-style-files","02.cpp\u002F47.c-style-files",{"title":1905,"path":1906,"stem":1907},"Робота з файлами: C++-стиль (fstream)","\u002Fcpp\u002Fcpp-style-files","02.cpp\u002F48.cpp-style-files",{"title":1909,"path":1910,"stem":1911},"План навчання: Курс C++ — Продовження (Статті 29–60+)","\u002Fcpp\u002Fcurriculum-plan","02.cpp\u002Fcurriculum-plan",{"title":1913,"icon":1914,"path":1915,"stem":1916,"children":1917,"page":59},"JavaScript","i-devicon-javascript","\u002Fjavascript","03.javascript",[1918,1944,1998,2020,2324,2362],{"title":1919,"icon":1920,"path":1921,"stem":1922,"children":1923,"page":59},"Events","i-lucide-mouse-pointer-click","\u002Fjavascript\u002Fevents","03.javascript\u002F01.events",[1924,1928,1932,1936,1940],{"title":1925,"path":1926,"stem":1927},"Вступ до подій браузера","\u002Fjavascript\u002Fevents\u002Fintro","03.javascript\u002F01.events\u002F01.intro",{"title":1929,"path":1930,"stem":1931},"Бульбашковий механізм (Bubbling) та занурення (Capturing)","\u002Fjavascript\u002Fevents\u002Fbubbling-capturing","03.javascript\u002F01.events\u002F02.bubbling-capturing",{"title":1933,"path":1934,"stem":1935},"Делегування подій (Event Delegation)","\u002Fjavascript\u002Fevents\u002Fdelegate-events","03.javascript\u002F01.events\u002F03.delegate-events",{"title":1937,"path":1938,"stem":1939},"Типові дії браузера та preventDefault()","\u002Fjavascript\u002Fevents\u002Fprevent-default","03.javascript\u002F01.events\u002F04.prevent-default",{"title":1941,"path":1942,"stem":1943},"Запуск користувацьких подій (Custom Events)","\u002Fjavascript\u002Fevents\u002Fcustom-events","03.javascript\u002F01.events\u002F05.custom-events",{"title":1945,"icon":1946,"path":1947,"stem":1948,"children":1949,"page":59},"Network","i-lucide-globe","\u002Fjavascript\u002Fnetwork","03.javascript\u002F02.network",[1950,1954,1958,1962,1966,1970,1974,1978,1982,1986,1990,1994],{"title":1951,"path":1952,"stem":1953},"Fetch API - Сучасний підхід до HTTP-запитів","\u002Fjavascript\u002Fnetwork\u002F01-fetch-api","03.javascript\u002F02.network\u002F01-fetch-api",{"title":1955,"path":1956,"stem":1957},"FormData - Робота з формами та файлами","\u002Fjavascript\u002Fnetwork\u002F02-formdata","03.javascript\u002F02.network\u002F02-formdata",{"title":1959,"path":1960,"stem":1961},"Відстеження прогресу завантаження","\u002Fjavascript\u002Fnetwork\u002F03-download-progress","03.javascript\u002F02.network\u002F03-download-progress",{"title":1963,"path":1964,"stem":1965},"Переривання fetch-запитів","\u002Fjavascript\u002Fnetwork\u002F04-abort-requests","03.javascript\u002F02.network\u002F04-abort-requests",{"title":1967,"path":1968,"stem":1969},"CORS - Запити між різними джерелами","\u002Fjavascript\u002Fnetwork\u002F05-cors","03.javascript\u002F02.network\u002F05-cors",{"title":1971,"path":1972,"stem":1973},"Fetch API - Повний довідник опцій","\u002Fjavascript\u002Fnetwork\u002F06-fetch-options","03.javascript\u002F02.network\u002F06-fetch-options",{"title":1975,"path":1976,"stem":1977},"URL Objects - Робота з посиланнями","\u002Fjavascript\u002Fnetwork\u002F07-url-objects","03.javascript\u002F02.network\u002F07-url-objects",{"title":1979,"path":1980,"stem":1981},"XMLHttpRequest - AJAX та низькорівневі запити","\u002Fjavascript\u002Fnetwork\u002F08-xmlhttprequest","03.javascript\u002F02.network\u002F08-xmlhttprequest",{"title":1983,"path":1984,"stem":1985},"Відновлюване завантаження файлів","\u002Fjavascript\u002Fnetwork\u002F09-resumable-upload","03.javascript\u002F02.network\u002F09-resumable-upload",{"title":1987,"path":1988,"stem":1989},"Cookies, document.cookie та світ після \"Cookiepocalypse\"","\u002Fjavascript\u002Fnetwork\u002F10-cookies","03.javascript\u002F02.network\u002F10-cookies",{"title":1991,"path":1992,"stem":1993},"js-cookie: Керування Cookies без Болю","\u002Fjavascript\u002Fnetwork\u002F11-js-cookie","03.javascript\u002F02.network\u002F11-js-cookie",{"title":1995,"path":1996,"stem":1997},"Axios: Потужний HTTP-клієнт для JavaScript","\u002Fjavascript\u002Fnetwork\u002F12-axios","03.javascript\u002F02.network\u002F12-axios",{"title":1999,"icon":2000,"path":2001,"stem":2002,"children":2003,"page":59},"Bom","i-lucide-monitor","\u002Fjavascript\u002Fbom","03.javascript\u002F03.bom",[2004,2008,2012,2016],{"title":2005,"path":2006,"stem":2007},"LocalStorage, SessionStorage та patterns збереження даних","\u002Fjavascript\u002Fbom\u002F01-localstorage","03.javascript\u002F03.bom\u002F01-localstorage",{"title":2009,"path":2010,"stem":2011},"Location Object - Керування адресою сторінки","\u002Fjavascript\u002Fbom\u002F02-location-object","03.javascript\u002F03.bom\u002F02-location-object",{"title":2013,"path":2014,"stem":2015},"History API - Керування історією браузера","\u002Fjavascript\u002Fbom\u002F03-history-api","03.javascript\u002F03.bom\u002F03-history-api",{"title":2017,"path":2018,"stem":2019},"Navigator Object - Ідентифікація та Можливості Пристрою","\u002Fjavascript\u002Fbom\u002F04-navigator-object","03.javascript\u002F03.bom\u002F04-navigator-object",{"title":2021,"icon":2022,"path":2023,"stem":2024,"children":2025},"React","i-devicon-react","\u002Fjavascript\u002Freact","03.javascript\u002F04.react\u002Findex",[2026,2027,2031,2035,2039,2043,2106,2141,2293],{"title":2021,"path":2023,"stem":2024},{"title":2028,"path":2029,"stem":2030},"Робота з Формами в React","\u002Fjavascript\u002Freact\u002Freact-forms","03.javascript\u002F04.react\u002F01.react-forms",{"title":2032,"path":2033,"stem":2034},"React Hook Form: Професійна Робота з Формами","\u002Fjavascript\u002Freact\u002Freact-hook-form","03.javascript\u002F04.react\u002F02.react-hook-form",{"title":2036,"path":2037,"stem":2038},"React Hook Form: Глибоке Розуміння Архітектури та Оптимізації","\u002Fjavascript\u002Freact\u002Freact-hook-form-new","03.javascript\u002F04.react\u002F02.react-hook-form-new",{"title":2040,"path":2041,"stem":2042},"Axios та React: Професійна Архітектура Запитів","\u002Fjavascript\u002Freact\u002Fdata-fetching-axios","03.javascript\u002F04.react\u002F03.data-fetching-axios",{"title":2044,"icon":132,"path":2045,"stem":2046,"children":2047},"Tanstack Query","\u002Fjavascript\u002Freact\u002Ftanstack-query","03.javascript\u002F04.react\u002F04.tanstack-query\u002Findex",[2048,2050,2054,2058,2062,2066,2070,2074,2078,2082,2086,2090,2094,2098,2102],{"title":2049,"path":2045,"stem":2046},"TanStack Query: Майстерність Керування Станом Сервера",{"title":2051,"path":2052,"stem":2053},"Парадигма Server State: Чому useEffect недостатньо","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-state-paradigm","03.javascript\u002F04.react\u002F04.tanstack-query\u002F01.server-state-paradigm",{"title":2055,"path":2056,"stem":2057},"Встановлення та Налаштування: Фундамент","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Finstallation-and-devtools","03.javascript\u002F04.react\u002F04.tanstack-query\u002F02.installation-and-devtools",{"title":2059,"path":2060,"stem":2061},"Основи Запитів та Магія Ключів","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fquery-basics-and-keys","03.javascript\u002F04.react\u002F04.tanstack-query\u002F03.query-basics-and-keys",{"title":2063,"path":2064,"stem":2065},"Синхронізація Даних: Життєвий Цикл Запиту","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fdata-synchronization","03.javascript\u002F04.react\u002F04.tanstack-query\u002F04.data-synchronization",{"title":2067,"path":2068,"stem":2069},"Мутації та Інвалідація: Зміна Даних","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fmutations-and-invalidation","03.javascript\u002F04.react\u002F04.tanstack-query\u002F05.mutations-and-invalidation",{"title":2071,"path":2072,"stem":2073},"Оптимістичні Оновлення: Швидше за Світло","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Foptimistic-updates","03.javascript\u002F04.react\u002F04.tanstack-query\u002F06.optimistic-updates",{"title":2075,"path":2076,"stem":2077},"Пагінація та Infinite Scroll","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fpagination-and-load-more","03.javascript\u002F04.react\u002F04.tanstack-query\u002F07.pagination-and-load-more",{"title":2079,"path":2080,"stem":2081},"Просунуті Патерни та Оптимізація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F04.tanstack-query\u002F08.advanced-patterns",{"title":2083,"path":2084,"stem":2085},"Архітектура та Best Practices","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Farchitecture-and-best-practices","03.javascript\u002F04.react\u002F04.tanstack-query\u002F09.architecture-and-best-practices",{"title":2087,"path":2088,"stem":2089},"Server-Side Rendering (SSR) та Гідратація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-side-rendering","03.javascript\u002F04.react\u002F04.tanstack-query\u002F10.server-side-rendering",{"title":2091,"path":2092,"stem":2093},"Стратегії Тестування","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Ftesting-strategies","03.javascript\u002F04.react\u002F04.tanstack-query\u002F11.testing-strategies",{"title":2095,"path":2096,"stem":2097},"Аутентифікація та Обробка Помилок","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fauthentication-and-errors","03.javascript\u002F04.react\u002F04.tanstack-query\u002F12.authentication-and-errors",{"title":2099,"path":2100,"stem":2101},"React Suspense та Майбутнє","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Freact-suspense","03.javascript\u002F04.react\u002F04.tanstack-query\u002F13.react-suspense",{"title":2103,"path":2104,"stem":2105},"Глибоке Занурення в Продуктивність","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fperformance-deep-dive","03.javascript\u002F04.react\u002F04.tanstack-query\u002F14.performance-deep-dive",{"title":2107,"icon":2022,"path":2108,"stem":2109,"children":2110},"React Router","\u002Fjavascript\u002Freact\u002Freact-router","03.javascript\u002F04.react\u002F05.react-router\u002Findex",[2111,2113,2117,2121,2125,2129,2133,2137],{"title":2112,"path":2108,"stem":2109},"React Router: Навігаційна система сучасного вебу",{"title":2114,"path":2115,"stem":2116},"Налаштування та Базовий Роутинг","\u002Fjavascript\u002Freact\u002Freact-router\u002Fsetup-and-basic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F01.setup-and-basic-routing",{"title":2118,"path":2119,"stem":2120},"Динамічна Навігація","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnavigation-and-links","03.javascript\u002F04.react\u002F05.react-router\u002F02.navigation-and-links",{"title":2122,"path":2123,"stem":2124},"Вкладені Маршрути та Макети","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnested-routes-and-layouts","03.javascript\u002F04.react\u002F05.react-router\u002F03.nested-routes-and-layouts",{"title":2126,"path":2127,"stem":2128},"Динамічні Маршрути та Параметри","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdynamic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F04.dynamic-routing",{"title":2130,"path":2131,"stem":2132},"Data APIs: Loaders та Actions","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdata-loading","03.javascript\u002F04.react\u002F05.react-router\u002F05.data-loading",{"title":2134,"path":2135,"stem":2136},"Просунуті Патерни","\u002Fjavascript\u002Freact\u002Freact-router\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F05.react-router\u002F06.advanced-patterns",{"title":2138,"path":2139,"stem":2140},"Legacy Routing: Компонентний підхід","\u002Fjavascript\u002Freact\u002Freact-router\u002Flegacy-routing","03.javascript\u002F04.react\u002F05.react-router\u002F07.legacy-routing",{"title":2142,"icon":132,"path":2143,"stem":2144,"children":2145},"Redux","\u002Fjavascript\u002Freact\u002Fredux","03.javascript\u002F04.react\u002F06.redux\u002Findex",[2146,2148,2164,2193,2202,2223,2239,2268],{"title":2147,"path":2143,"stem":2144},"Redux: Еволюція управління станом",{"title":14,"icon":15,"path":2149,"stem":2150,"children":2151,"page":59},"\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals",[2152,2156,2160],{"title":2153,"path":2154,"stem":2155},"Вступ до State Management","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fintro-state-management","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F01.intro-state-management",{"title":2157,"path":2158,"stem":2159},"Філософія Redux та Три Принципи","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fredux-philosophy","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F02.redux-philosophy",{"title":2161,"path":2162,"stem":2163},"Чисті функції та Іммутабельність","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fpure-functions-immutability","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F03.pure-functions-immutability",{"title":2165,"icon":132,"path":2166,"stem":2167,"children":2168,"page":59},"Classic Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux",[2169,2173,2177,2181,2185,2189],{"title":2170,"path":2171,"stem":2172},"Створення Store (Classic Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fstore-setup","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F01.store-setup",{"title":2174,"path":2175,"stem":2176},"Actions, Constants та Action Creators","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Factions-constants","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F02.actions-constants",{"title":2178,"path":2179,"stem":2180},"Логіка Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freducers","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F03.reducers",{"title":2182,"path":2183,"stem":2184},"Комбінування Reducers (Root Reducer)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fdata-flow","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F04.data-flow",{"title":2186,"path":2187,"stem":2188},"Підключення до React (React-Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freact-redux-connection","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F05.react-redux-connection",{"title":2190,"path":2191,"stem":2192},"Middleware та Асинхронність (Redux Thunk)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fmiddleware-thunk","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F06.middleware-thunk",{"title":2194,"icon":132,"path":2195,"stem":2196,"children":2197,"page":59},"Transition To Rtk","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk",[2198],{"title":2199,"path":2200,"stem":2201},"Проблеми класичного Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk\u002Fproblems-with-classic","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk\u002F01.problems-with-classic",{"title":2203,"icon":132,"path":2204,"stem":2205,"children":2206,"page":59},"Redux Toolkit","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit",[2207,2211,2215,2219],{"title":2208,"path":2209,"stem":2210},"Налаштування Store з configureStore","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fconfigure-store","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F01.configure-store",{"title":2212,"path":2213,"stem":2214},"createSlice: Революція в Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fcreate-slice","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F02.create-slice",{"title":2216,"path":2217,"stem":2218},"Асинхронність з createAsyncThunk","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fasync-thunks","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F03.async-thunks",{"title":2220,"path":2221,"stem":2222},"04. Entity Adapter: Керування нормалізованим станом","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fentity-adapter","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F04.entity-adapter",{"title":2224,"icon":92,"path":2225,"stem":2226,"children":2227,"page":59},"Advanced","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced",[2228,2232,2236],{"title":2229,"path":2230,"stem":2231},"Мемоізація та Селектори: Повний Гайд по Reselect","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Fselectors-reselect","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F01.selectors-reselect",{"title":2233,"path":2234,"stem":2235},"RTK Query: Архітектура Серверного Кешу","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Frtk-query-intro","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F02.rtk-query-intro",{"title":2083,"path":2237,"stem":2238},"\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Farchitecture-best-practices","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F03.architecture-best-practices",{"title":2240,"icon":132,"path":2241,"stem":2242,"children":2243,"page":59},"Project Kanban","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban",[2244,2248,2252,2256,2260,2264],{"title":2245,"path":2246,"stem":2247},"Проєкт: Kanban Board (Trello Clone)","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fproject-overview","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F01.project-overview",{"title":2249,"path":2250,"stem":2251},"Налаштування та Типізація","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fsetup-and-types","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F02.setup-and-types",{"title":2253,"path":2254,"stem":2255},"Board Slice: Серце Дошки","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fboard-slice","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F03.board-slice",{"title":2257,"path":2258,"stem":2259},"Логіка Drag & Drop","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fdrag-and-drop-logic","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F04.drag-and-drop-logic",{"title":2261,"path":2262,"stem":2263},"Інтеграція з RTK Query","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Frtk-query-integration","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F05.rtk-query-integration",{"title":2265,"path":2266,"stem":2267},"Optimistic Updates","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Foptimistic-updates","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F06.optimistic-updates",{"title":2269,"icon":132,"path":2270,"stem":2271,"children":2272,"page":59},"Testing","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting","03.javascript\u002F04.react\u002F06.redux\u002F07.testing",[2273,2277,2281,2285,2289],{"title":2274,"path":2275,"stem":2276},"Тестування Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Fintro-testing","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F01.intro-testing",{"title":2278,"path":2279,"stem":2280},"Тестування Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-reducers","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F02.testing-reducers",{"title":2282,"path":2283,"stem":2284},"Тестування Селекторів","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-selectors","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F03.testing-selectors",{"title":2286,"path":2287,"stem":2288},"Тестування Компонентів (Integration)","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-components","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F04.testing-components",{"title":2290,"path":2291,"stem":2292},"Тестування Async Thunks","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-thunks","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F05.testing-thunks",{"title":2294,"icon":132,"path":2295,"stem":2296,"children":2297},"Ui Libraries","\u002Fjavascript\u002Freact\u002Fui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002Findex",[2298,2300,2304,2308,2312,2316,2320],{"title":2299,"path":2295,"stem":2296},"UI Бібліотеки в React",{"title":2301,"path":2302,"stem":2303},"Вступ до UI Бібліотек: Навіщо Винаходити Велосипед Двічі?","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fintroduction-to-ui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002F01.introduction-to-ui-libraries",{"title":2305,"path":2306,"stem":2307},"Філософія shadcn\u002Fui: \"Not a Component Library\"","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-philosophy","03.javascript\u002F04.react\u002F07.ui-libraries\u002F02.shadcn-philosophy",{"title":2309,"path":2310,"stem":2311},"Установка та Налаштування shadcn\u002Fui","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-installation","03.javascript\u002F04.react\u002F07.ui-libraries\u002F03.shadcn-installation",{"title":2313,"path":2314,"stem":2315},"Базові Компоненти shadcn\u002Fui: Фундамент Інтерфейсу","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-basics","03.javascript\u002F04.react\u002F07.ui-libraries\u002F04.shadcn-components-basics",{"title":2317,"path":2318,"stem":2319},"Компоненти Форм: Побудова Інтерактивних Form","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-forms","03.javascript\u002F04.react\u002F07.ui-libraries\u002F05.shadcn-components-forms",{"title":2321,"path":2322,"stem":2323},"Складні Компоненти: Dialog, Dropdown, Table та Command","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-advanced","03.javascript\u002F04.react\u002F07.ui-libraries\u002F06.shadcn-components-advanced",{"title":2325,"icon":2326,"path":2327,"stem":2328,"children":2329,"page":59},"TypeScript","i-devicon-typescript","\u002Fjavascript\u002Ftypescript","03.javascript\u002F05.typescript",[2330,2334,2338,2342,2346,2350,2354,2358],{"title":2331,"path":2332,"stem":2333},"TypeScript: Броня для вашого коду","\u002Fjavascript\u002Ftypescript\u002Fintro-and-basic-types","03.javascript\u002F05.typescript\u002F01.intro-and-basic-types",{"title":2335,"path":2336,"stem":2337},"Майстерність Моделювання Даних: Інтерфейси та Просунуті Типи","\u002Fjavascript\u002Ftypescript\u002Finterfaces-and-advanced-types","03.javascript\u002F05.typescript\u002F02.interfaces-and-advanced-types",{"title":2339,"path":2340,"stem":2341},"Алхімія Типів: Generics та Utility Types","\u002Fjavascript\u002Ftypescript\u002Fgenerics-and-utilities","03.javascript\u002F05.typescript\u002F03.generics-and-utilities",{"title":2343,"path":2344,"stem":2345},"Архітектура та Шаблони: Класи в TypeScript","\u002Fjavascript\u002Ftypescript\u002Fclasses-and-oop","03.javascript\u002F05.typescript\u002F04.classes-and-oop",{"title":2347,"path":2348,"stem":2349},"Продакшн та Екосистема: Advanced Config & Workflow","\u002Fjavascript\u002Ftypescript\u002Fadvanced-patterns-and-config","03.javascript\u002F05.typescript\u002F05.advanced-patterns-and-config",{"title":2351,"path":2352,"stem":2353},"TypeScript у світі React","\u002Fjavascript\u002Ftypescript\u002Freact-basics","03.javascript\u002F05.typescript\u002F06.react-basics",{"title":2355,"path":2356,"stem":2357},"React + TypeScript: Продвинуті патерни","\u002Fjavascript\u002Ftypescript\u002Freact-advanced","03.javascript\u002F05.typescript\u002F07.react-advanced",{"title":2359,"path":2360,"stem":2361},"React + TypeScript: Екосистема та бібліотеки","\u002Fjavascript\u002Ftypescript\u002Freact-ecosystem","03.javascript\u002F05.typescript\u002F08.react-ecosystem",{"title":2363,"path":2364,"stem":2365},"Atomic Design","\u002Fjavascript\u002Fatomic-design","03.javascript\u002F2.atomic-design",{"title":2367,"icon":2368,"path":2369,"stem":2370,"children":2371,"page":59},"Java","i-devicon-java","\u002Fjava","04.java",[2372,2375,2378,2382,2386,2390,2394],{"title":162,"path":2373,"stem":2374},"\u002Fjava\u002Fdata-mapper-part1","04.java\u002F01.data-mapper-part1",{"title":166,"path":2376,"stem":2377},"\u002Fjava\u002Fdata-mapper-part2","04.java\u002F02.data-mapper-part2",{"title":2379,"path":2380,"stem":2381},"Service Layer: Організація бізнес-логіки","\u002Fjava\u002Fservice-layer","04.java\u002F03.service-layer",{"title":2383,"path":2384,"stem":2385},"Rich Domain Model та State Pattern","\u002Fjava\u002Frich-domain-model","04.java\u002F04.rich-domain-model",{"title":2387,"path":2388,"stem":2389},"Патерни для складної бізнес-логіки","\u002Fjava\u002Fbusiness-logic-patterns","04.java\u002F05.business-logic-patterns",{"title":2391,"path":2392,"stem":2393},"Обробка помилок та валідація","\u002Fjava\u002Ferror-handling-validation","04.java\u002F06.error-handling-validation",{"title":2395,"path":2396,"stem":2397,"children":2398,"page":59},"Проектування баз даних","\u002Fjava\u002Fpr2","04.java\u002Fpr2",[2399,2403,2407,2411,2415,2419,2423,2427,2431,2435,2439,2443,2447,2451,2455,2459,2463,2467,2471,2475,2479,2483,2487,2491,2495,2499,2503,2507,2511,2515,2519,2523,2527,2531,2535,2539,2543],{"title":2400,"path":2401,"stem":2402},"Концептуальне моделювання: Мистецтво розуміння предметної області","\u002Fjava\u002Fpr2\u002Fconceptual-modeling","04.java\u002Fpr2\u002F01.conceptual-modeling",{"title":2404,"path":2405,"stem":2406},"Логічне моделювання: Від бізнес-ідей до структур даних","\u002Fjava\u002Fpr2\u002Flogical-modeling","04.java\u002Fpr2\u002F02.logical-modeling",{"title":2408,"path":2409,"stem":2410},"Нормалізація: Гігієна даних та боротьба з аномаліями","\u002Fjava\u002Fpr2\u002Fnormalization","04.java\u002Fpr2\u002F03.normalization",{"title":2412,"path":2413,"stem":2414},"Фізична схема: Від абстракції до DDL","\u002Fjava\u002Fpr2\u002Fphysical-schema","04.java\u002Fpr2\u002F04.physical-schema",{"title":2416,"path":2417,"stem":2418},"Архітектурна класифікація таблиць","\u002Fjava\u002Fpr2\u002Ftable-classification","04.java\u002Fpr2\u002F05.table-classification",{"title":2420,"path":2421,"stem":2422},"Database Migrations: Версіонування схеми з Flyway","\u002Fjava\u002Fpr2\u002Fdatabase-migrations","04.java\u002Fpr2\u002F06.database-migrations",{"title":2424,"path":2425,"stem":2426},"А що, якби це була не реляційна БД?","\u002Fjava\u002Fpr2\u002Fbeyond-relational","04.java\u002Fpr2\u002F07.beyond-relational",{"title":2428,"path":2429,"stem":2430},"Object-Relational Impedance Mismatch: Два світи, що не хочуть дружити","\u002Fjava\u002Fpr2\u002Fimpedance-mismatch","04.java\u002Fpr2\u002F09.impedance-mismatch",{"title":2432,"path":2433,"stem":2434},"JDBC: Перший контакт із базою даних","\u002Fjava\u002Fpr2\u002Fjdbc-fundamentals","04.java\u002Fpr2\u002F10.jdbc-fundamentals",{"title":2436,"path":2437,"stem":2438},"Якість коду: Spotless, SpotBugs та SonarQube","\u002Fjava\u002Fpr2\u002F10a.code-quality","04.java\u002Fpr2\u002F10a.code-quality",{"title":2440,"path":2441,"stem":2442},"Connection Pool: Патерн Object Pool для JDBC-з'єднань","\u002Fjava\u002Fpr2\u002Fconnection-pool","04.java\u002Fpr2\u002F11.connection-pool",{"title":2444,"path":2445,"stem":2446},"Row Data Gateway: Об'єкт як обгортка рядка таблиці","\u002Fjava\u002Fpr2\u002Frow-data-gateway","04.java\u002Fpr2\u002F12.row-data-gateway",{"title":2448,"path":2449,"stem":2450},"Table Data Gateway: Фасад таблиці як архітектурний відступ","\u002Fjava\u002Fpr2\u002Ftable-data-gateway","04.java\u002Fpr2\u002F13.table-data-gateway",{"title":2452,"path":2453,"stem":2454},"Repository + Data Mapper: Правильна шарова архітектура з JDBC","\u002Fjava\u002Fpr2\u002Frepository-data-mapper","04.java\u002Fpr2\u002F14.repository-data-mapper",{"title":2456,"path":2457,"stem":2458},"Identity Map: Кешування сутностей у рамках сесії","\u002Fjava\u002Fpr2\u002Fidentity-map","04.java\u002Fpr2\u002F15.identity-map",{"title":2460,"path":2461,"stem":2462},"Unit of Work: Відстеження змін і координація JDBC-транзакцій","\u002Fjava\u002Fpr2\u002Funit-of-work","04.java\u002Fpr2\u002F16.unit-of-work",{"title":2464,"path":2465,"stem":2466},"Strategy: Замінювані SQL-стратегії для підтримки різних СУБД","\u002Fjava\u002Fpr2\u002Fstrategy-sql","04.java\u002Fpr2\u002F17.strategy-sql",{"title":2468,"path":2469,"stem":2470},"Proxy: Lazy Loading для One-To-Many колекцій","\u002Fjava\u002Fpr2\u002Fproxy-lazy-loading","04.java\u002Fpr2\u002F18.proxy-lazy-loading",{"title":2472,"path":2473,"stem":2474},"Generic Repository через Java Reflection: анотації та динамічний SQL","\u002Fjava\u002Fpr2\u002Fgeneric-repository-reflection","04.java\u002Fpr2\u002F19.generic-repository-reflection",{"title":2476,"path":2477,"stem":2478},"Specification Pattern: Композиція бізнес-правил для складних запитів","\u002Fjava\u002Fpr2\u002Fspecification-pattern","04.java\u002Fpr2\u002F20.specification-pattern",{"title":2480,"path":2481,"stem":2482},"Розширені можливості Specification Pattern: підзапити, агрегації та гібридний підхід","\u002Fjava\u002Fpr2\u002F20a.advanced-specifications","04.java\u002Fpr2\u002F20a.advanced-specifications",{"title":2484,"path":2485,"stem":2486},"Асинхронність у JDBC: Від блокуючих викликів до CompletableFuture","\u002Fjava\u002Fpr2\u002Fasynchronous-jdbc","04.java\u002Fpr2\u002F21.asynchronous-jdbc",{"title":2488,"path":2489,"stem":2490},"Інтеграційне тестування JDBC-репозиторіїв: Embedded H2 та патерн AAA","\u002Fjava\u002Fpr2\u002Fintegration-testing-h2","04.java\u002Fpr2\u002F22.integration-testing-h2",{"title":2492,"path":2493,"stem":2494},"Testcontainers: Тестування з реальною PostgreSQL у Docker-контейнерах","\u002Fjava\u002Fpr2\u002Fintegration-testing-testcontainers","04.java\u002Fpr2\u002F23.integration-testing-testcontainers",{"title":2496,"path":2497,"stem":2498},"Google Guice: Впровадження залежностей у JavaFX-проєкті","\u002Fjava\u002Fpr2\u002Fdependency-injection-guice","04.java\u002Fpr2\u002F24.dependency-injection-guice",{"title":2500,"path":2501,"stem":2502},"JavaFX: Основи побудови графічних інтерфейсів","\u002Fjava\u002Fpr2\u002Fjavafx-fundamentals","04.java\u002Fpr2\u002F25.javafx-fundamentals",{"title":2504,"path":2505,"stem":2506},"Properties та Bindings: Реактивність у JavaFX","\u002Fjava\u002Fpr2\u002Fjavafx-properties-bindings","04.java\u002Fpr2\u002F26.javafx-properties-bindings",{"title":2508,"path":2509,"stem":2510},"MVC vs MVP vs MVVM: Еволюція архітектурних патернів UI","\u002Fjava\u002Fpr2\u002Fui-architecture-patterns","04.java\u002Fpr2\u002F27.ui-architecture-patterns",{"title":2512,"path":2513,"stem":2514},"MVVM на практиці: Побудова ViewModel","\u002Fjava\u002Fpr2\u002Fmvvm-viewmodel-implementation","04.java\u002Fpr2\u002F28.mvvm-viewmodel-implementation",{"title":2516,"path":2517,"stem":2518},"View та Controller: Зв'язування з ViewModel через FXML","\u002Fjava\u002Fpr2\u002Fmvvm-view-controller","04.java\u002Fpr2\u002F29.mvvm-view-controller",{"title":2520,"path":2521,"stem":2522},"Інтеграція MVVM з Guice: Автоматична ін'єкція залежностей","\u002Fjava\u002Fpr2\u002Fmvvm-guice-integration","04.java\u002Fpr2\u002F30.mvvm-guice-integration",{"title":2524,"path":2525,"stem":2526},"Валідація та обробка помилок у MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-validation-error-handling","04.java\u002Fpr2\u002F31.mvvm-validation-error-handling",{"title":2528,"path":2529,"stem":2530},"Навігація та управління екранами у JavaFX MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-navigation-screen-management","04.java\u002Fpr2\u002F32.mvvm-navigation-screen-management",{"title":2532,"path":2533,"stem":2534},"Тестування JavaFX MVVM-додатків","\u002Fjava\u002Fpr2\u002Fmvvm-testing","04.java\u002Fpr2\u002F33.mvvm-testing",{"title":2536,"path":2537,"stem":2538},"Стилізація та теми у JavaFX: CSS та User Experience","\u002Fjava\u002Fpr2\u002Fjavafx-styling-themes","04.java\u002Fpr2\u002F34.javafx-styling-themes",{"title":2540,"path":2541,"stem":2542},"AtlantaFX: Сучасні теми для JavaFX додатків","\u002Fjava\u002Fpr2\u002Fatlantafx-modern-themes","04.java\u002Fpr2\u002F35.atlantafx-modern-themes",{"title":2544,"path":2545,"stem":2546},"Пакування та розповсюдження JavaFX-додатків","\u002Fjava\u002Fpr2\u002Fjar-packaging-distribution","04.java\u002Fpr2\u002F36.jar-packaging-distribution",{"title":2548,"icon":2549,"path":2550,"stem":2551,"children":2552,"page":59},"Python","i-devicon-python","\u002Fpython","05.python",[2553,2557,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2669],{"title":2554,"path":2555,"stem":2556},"Модулі, Пакети та Віртуальні Середовища","\u002Fpython\u002Fmodules-packages-venv","05.python\u002F00.modules-packages-venv",{"title":71,"path":2558,"stem":2559},"\u002Fpython\u002Fclasses-objects","05.python\u002F01.classes-objects",{"title":2561,"path":2562,"stem":2563},"Інкапсуляція, Керування Доступом та Властивості","\u002Fpython\u002Fencapsulation","05.python\u002F02.encapsulation",{"title":2565,"path":2566,"stem":2567},"Наслідування, MRO та суперсила super()","\u002Fpython\u002Finheritance-mro","05.python\u002F03.inheritance-mro",{"title":2569,"path":2570,"stem":2571},"Абстракція — ABC проти Статичних Протоколів (PEP 544)","\u002Fpython\u002Fabstraction-protocols","05.python\u002F04.abstraction-protocols",{"title":2573,"path":2574,"stem":2575},"Магічні методи (Dunder) та Емуляція протоколів","\u002Fpython\u002Fdunder-methods","05.python\u002F05.dunder-methods",{"title":2577,"path":2578,"stem":2579},"Декоратори та Керування життєвим циклом методів","\u002Fpython\u002Fdecorators-static-class","05.python\u002F06.decorators-static-class",{"title":2581,"path":2582,"stem":2583},"Дескриптори — Магія доступу до атрибутів","\u002Fpython\u002Fdescriptors","05.python\u002F07.descriptors",{"title":2585,"path":2586,"stem":2587},"Метакласи — Динамічне створення класів під капотом CPython","\u002Fpython\u002Fmetaclasses","05.python\u002F08.metaclasses",{"title":2589,"path":2590,"stem":2591},"Dataclasses, NamedTuple та сучасні контейнери Python","\u002Fpython\u002Fmodern-containers","05.python\u002F09.modern-containers",{"title":2593,"path":2594,"stem":2595},"GIL та модель конкурентності CPython — фундамент перед потоками і процесами","\u002Fpython\u002Fgil-concurrency-intro","05.python\u002F11.gil-concurrency-intro",{"title":2597,"path":2598,"stem":2599},"Threading — конкурентність для I\u002FO-bound задач","\u002Fpython\u002Fthreading","05.python\u002F12.threading",{"title":2601,"path":2602,"stem":2603},"Multiprocessing — справжній паралелізм для CPU-bound задач","\u002Fpython\u002Fmultiprocessing","05.python\u002F13.multiprocessing",{"title":2605,"path":2606,"stem":2607},"asyncio — кооперативна конкурентність та event loop","\u002Fpython\u002Fasyncio","05.python\u002F14.asyncio",{"title":2609,"icon":92,"path":2610,"stem":2611,"children":2612,"page":59},"FastAPI","\u002Fpython\u002Ffastapi","05.python\u002Ffastapi",[2613,2617,2621,2625,2629,2633,2637,2641,2645,2649,2653,2657,2661,2665],{"title":2614,"path":2615,"stem":2616},"Глибокий Typing та Pydantic v2 — від анотацій до валідації","\u002Fpython\u002Ffastapi\u002Ftyping-pydantic","05.python\u002Ffastapi\u002F15.typing-pydantic",{"title":2618,"path":2619,"stem":2620},"WSGI, ASGI та Python Web-екосистема","\u002Fpython\u002Ffastapi\u002Fwsgi-asgi-ecosystem","05.python\u002Ffastapi\u002F16.wsgi-asgi-ecosystem",{"title":2622,"path":2623,"stem":2624},"FastAPI: Перший додаток, Uvicorn та OpenAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-intro","05.python\u002Ffastapi\u002F17.fastapi-intro",{"title":2626,"path":2627,"stem":2628},"Маршрутизація, параметри запитів та APIRouter","\u002Fpython\u002Ffastapi\u002Ffastapi-routing-params","05.python\u002Ffastapi\u002F18.fastapi-routing-params",{"title":2630,"path":2631,"stem":2632},"Pydantic v2 у FastAPI — схеми, валідація та серіалізація","\u002Fpython\u002Ffastapi\u002Ffastapi-pydantic-schemas","05.python\u002Ffastapi\u002F19.fastapi-pydantic-schemas",{"title":2634,"path":2635,"stem":2636},"Dependency Injection — серце архітектури FastAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-dependency-injection","05.python\u002Ffastapi\u002F20.fastapi-dependency-injection",{"title":2638,"path":2639,"stem":2640},"Обробка помилок, Middleware та CORS у FastAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-errors-middleware","05.python\u002Ffastapi\u002F21.fastapi-errors-middleware",{"title":2642,"path":2643,"stem":2644},"SQLAlchemy 2.0 — ORM, Core та Async Engine","\u002Fpython\u002Ffastapi\u002Fsqlalchemy-orm","05.python\u002Ffastapi\u002F22.sqlalchemy-orm",{"title":2646,"path":2647,"stem":2648},"Alembic — міграції бази даних","\u002Fpython\u002Ffastapi\u002Falembic-migrations","05.python\u002Ffastapi\u002F23.alembic-migrations",{"title":2650,"path":2651,"stem":2652},"Repository Pattern та Clean Architecture у FastAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-repository-clean-arch","05.python\u002Ffastapi\u002F24.fastapi-repository-clean-arch",{"title":2654,"path":2655,"stem":2656},"Автентифікація та авторизація — JWT, OAuth2, RBAC","\u002Fpython\u002Ffastapi\u002Ffastapi-auth-security","05.python\u002Ffastapi\u002F25.fastapi-auth-security",{"title":2658,"path":2659,"stem":2660},"Тестування FastAPI — pytest, httpx, fixtures","\u002Fpython\u002Ffastapi\u002Ffastapi-testing","05.python\u002Ffastapi\u002F26.fastapi-testing",{"title":2662,"path":2663,"stem":2664},"Async на повну — Background Tasks, WebSockets, SSE та Celery","\u002Fpython\u002Ffastapi\u002Ffastapi-async-advanced","05.python\u002Ffastapi\u002F27.fastapi-async-advanced",{"title":2666,"path":2667,"stem":2668},"Deployment — Docker, CI\u002FCD, Monitoring","\u002Fpython\u002Ffastapi\u002Ffastapi-deployment","05.python\u002Ffastapi\u002F28.fastapi-deployment",{"title":2670,"path":2671,"stem":2672},"[object Object]","\u002Fpython\u002Foop-plan","05.python\u002Foop-plan",{"title":2674,"icon":2675,"path":2676,"stem":2677,"children":2678,"page":59},"Бази даних","i-lucide-database","\u002Fdatabases","06.databases",[2679,2709,2732,2769,2798,2816,2850,2862,2871],{"title":2680,"icon":2681,"path":2682,"stem":2683,"children":2684,"page":59},"Intro","i-lucide-play","\u002Fdatabases\u002Fintro","06.databases\u002F01.intro",[2685,2689,2693,2697,2701,2705],{"title":2686,"path":2687,"stem":2688},"Введення в теорію баз даних","\u002Fdatabases\u002Fintro\u002Fintroduction-to-databases","06.databases\u002F01.intro\u002F01.introduction-to-databases",{"title":2690,"path":2691,"stem":2692},"Реляційна модель даних","\u002Fdatabases\u002Fintro\u002Frelational-model-theory","06.databases\u002F01.intro\u002F02.relational-model-theory",{"title":2694,"path":2695,"stem":2696},"ER-моделювання","\u002Fdatabases\u002Fintro\u002Fer-modeling","06.databases\u002F01.intro\u002F03.er-modeling",{"title":2698,"path":2699,"stem":2700},"Логічне проектування БД","\u002Fdatabases\u002Fintro\u002Flogical-schema","06.databases\u002F01.intro\u002F04.logical-schema",{"title":2702,"path":2703,"stem":2704},"Класифікація таблиць","\u002Fdatabases\u002Fintro\u002Ftable-classification","06.databases\u002F01.intro\u002F05.table-classification",{"title":2706,"path":2707,"stem":2708},"PlantUML для баз даних","\u002Fdatabases\u002Fintro\u002Fplantuml-diagrams","06.databases\u002F01.intro\u002F06.plantuml-diagrams",{"title":2710,"icon":2675,"path":2711,"stem":2712,"children":2713,"page":59},"MS SQL Server Start","\u002Fdatabases\u002Fms-sql-server-start","06.databases\u002F02.ms-sql-server-start",[2714,2718,2724,2728],{"title":2715,"path":2716,"stem":2717},"Типи даних у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fdata-types","06.databases\u002F02.ms-sql-server-start\u002F01.data-types",{"title":2719,"path":2720,"stem":2721,"children":2722},"Індекси у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-indexes","06.databases\u002F02.ms-sql-server-start\u002F02.sql-indexes",[2723],{"title":2719,"path":2720,"stem":2721},{"title":2725,"path":2726,"stem":2727},"Системні бази даних MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsystem-databases","06.databases\u002F02.ms-sql-server-start\u002F03.system-databases",{"title":2729,"path":2730,"stem":2731},"Огляд мови SQL та запитів","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-queries-overview","06.databases\u002F02.ms-sql-server-start\u002F04.sql-queries-overview",{"title":2733,"icon":2675,"path":2734,"stem":2735,"children":2736,"page":59},"SQL","\u002Fdatabases\u002Fsql","06.databases\u002F03.sql",[2737,2741,2745,2749,2753,2757,2761,2765],{"title":2738,"path":2739,"stem":2740},"Налаштування демонстраційної бази даних","\u002Fdatabases\u002Fsql\u002Fsample-database-setup","06.databases\u002F03.sql\u002F00.sample-database-setup",{"title":2742,"path":2743,"stem":2744},"DDL - Створення таблиць (CREATE TABLE)","\u002Fdatabases\u002Fsql\u002Fddl-create-table","06.databases\u002F03.sql\u002F01.ddl-create-table",{"title":2746,"path":2747,"stem":2748},"DDL - Зміна та видалення таблиць (ALTER, DROP)","\u002Fdatabases\u002Fsql\u002Fddl-alter-drop-table","06.databases\u002F03.sql\u002F02.ddl-alter-drop-table",{"title":2750,"path":2751,"stem":2752},"SELECT запити - Основи","\u002Fdatabases\u002Fsql\u002Fselect-queries-fundamentals","06.databases\u002F03.sql\u002F03.select-queries-fundamentals",{"title":2754,"path":2755,"stem":2756},"SELECT запити - Розширені можливості","\u002Fdatabases\u002Fsql\u002Fselect-queries-advanced","06.databases\u002F03.sql\u002F04.select-queries-advanced",{"title":2758,"path":2759,"stem":2760},"INSERT запити - Додавання даних","\u002Fdatabases\u002Fsql\u002Finsert-queries","06.databases\u002F03.sql\u002F05.insert-queries",{"title":2762,"path":2763,"stem":2764},"UPDATE та DELETE запити","\u002Fdatabases\u002Fsql\u002Fupdate-delete-queries","06.databases\u002F03.sql\u002F06.update-delete-queries",{"title":2766,"path":2767,"stem":2768},"Транзакції в SQL","\u002Fdatabases\u002Fsql\u002Ftransactions","06.databases\u002F03.sql\u002F07.transactions",{"title":2770,"icon":2675,"path":2771,"stem":2772,"children":2773,"page":59},"Multi Table Databases","\u002Fdatabases\u002Fmulti-table-databases","06.databases\u002F04.multi-table-databases",[2774,2778,2782,2786,2790,2794],{"title":2775,"path":2776,"stem":2777},"Зв'язки та нормалізація БД","\u002Fdatabases\u002Fmulti-table-databases\u002Frelationships-and-normalization","06.databases\u002F04.multi-table-databases\u002F00.relationships-and-normalization",{"title":2779,"path":2780,"stem":2781},"INNER JOIN - З'єднання таблиць","\u002Fdatabases\u002Fmulti-table-databases\u002Finner-join","06.databases\u002F04.multi-table-databases\u002F01.inner-join",{"title":2783,"path":2784,"stem":2785},"OUTER JOINs - LEFT, RIGHT, FULL","\u002Fdatabases\u002Fmulti-table-databases\u002Fouter-joins","06.databases\u002F04.multi-table-databases\u002F02.outer-joins",{"title":2787,"path":2788,"stem":2789},"CROSS та SELF JOINs","\u002Fdatabases\u002Fmulti-table-databases\u002Fcross-self-joins","06.databases\u002F04.multi-table-databases\u002F03.cross-self-joins",{"title":2791,"path":2792,"stem":2793},"Підзапити (Subqueries)","\u002Fdatabases\u002Fmulti-table-databases\u002Fsubqueries","06.databases\u002F04.multi-table-databases\u002F04.subqueries",{"title":2795,"path":2796,"stem":2797},"Агрегації з JOIN","\u002Fdatabases\u002Fmulti-table-databases\u002Faggregations-with-joins","06.databases\u002F04.multi-table-databases\u002F05.aggregations-with-joins",{"title":2799,"icon":2800,"path":2801,"stem":2802,"children":2803,"page":59},"Aggregate Functions","i-lucide-calculator","\u002Fdatabases\u002Faggregate-functions","06.databases\u002F05.aggregate-functions",[2804,2808,2812],{"title":2805,"path":2806,"stem":2807},"Функції агрегування в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fintroduction-aggregate-functions","06.databases\u002F05.aggregate-functions\u002F01.introduction-aggregate-functions",{"title":2809,"path":2810,"stem":2811},"Групування даних в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fgrouping-data","06.databases\u002F05.aggregate-functions\u002F02.grouping-data",{"title":2813,"path":2814,"stem":2815},"Підзапити з агрегатними функціями","\u002Fdatabases\u002Faggregate-functions\u002Fsubqueries-aggregates","06.databases\u002F05.aggregate-functions\u002F03.subqueries-aggregates",{"title":2817,"icon":2818,"path":2819,"stem":2820,"children":2821,"page":59},"Тригери та зберігаємі процедури","i-lucide-database-zap","\u002Fdatabases\u002Ftriggers-stored-procedures","06.databases\u002F07.triggers-stored-procedures",[2822,2826,2830,2834,2838,2842,2846],{"title":2823,"path":2824,"stem":2825},"DML-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fdml-triggers","06.databases\u002F07.triggers-stored-procedures\u002F01.dml-triggers",{"title":2827,"path":2828,"stem":2829},"DDL-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fddl-triggers","06.databases\u002F07.triggers-stored-procedures\u002F02.ddl-triggers",{"title":2831,"path":2832,"stem":2833},"Transact-SQL розширення","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransact-sql-extensions","06.databases\u002F07.triggers-stored-procedures\u002F03.transact-sql-extensions",{"title":2835,"path":2836,"stem":2837},"Транзакції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransactions","06.databases\u002F07.triggers-stored-procedures\u002F04.transactions",{"title":2839,"path":2840,"stem":2841},"Зберігаємі процедури","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fstored-procedures","06.databases\u002F07.triggers-stored-procedures\u002F05.stored-procedures",{"title":2843,"path":2844,"stem":2845},"Користувацькі функції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fuser-defined-functions","06.databases\u002F07.triggers-stored-procedures\u002F06.user-defined-functions",{"title":2847,"path":2848,"stem":2849},"Безпека баз даних","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fsecurity","06.databases\u002F07.triggers-stored-procedures\u002F08.security",{"title":2847,"icon":793,"path":2851,"stem":2852,"children":2853,"page":59},"\u002Fdatabases\u002Fsecurity","06.databases\u002F08.security",[2854,2858],{"title":2855,"path":2856,"stem":2857},"Вступ до безпеки баз даних","\u002Fdatabases\u002Fsecurity\u002Fintroduction","06.databases\u002F08.security\u002F01.introduction",{"title":2859,"path":2860,"stem":2861},"Системні представлення та метадані","\u002Fdatabases\u002Fsecurity\u002Fsystem-views","06.databases\u002F08.security\u002F02.system-views",{"title":2863,"icon":2864,"path":2865,"stem":2866,"children":2867,"page":59},"Резервне копіювання та відновлення","i-lucide-database-backup","\u002Fdatabases\u002Fbackup-recovery","06.databases\u002F09.backup-recovery",[2868],{"title":2863,"path":2869,"stem":2870},"\u002Fdatabases\u002Fbackup-recovery\u002Fbackup-restore","06.databases\u002F09.backup-recovery\u002F01.backup-restore",{"title":2872,"icon":2873,"path":2874,"stem":2875,"children":2876,"page":59},"Повнотекстовий пошук","i-lucide-search","\u002Fdatabases\u002Ffull-text-search","06.databases\u002F10.full-text-search",[2877],{"title":2872,"path":2878,"stem":2879},"\u002Fdatabases\u002Ffull-text-search\u002Ffull-text-search","06.databases\u002F10.full-text-search\u002F01.full-text-search",{"title":2881,"icon":2882,"path":2883,"stem":2884,"children":2885,"page":59},"Tools","i-lucide-wrench","\u002Ftools","07.tools",[2886,2962],{"title":2887,"icon":2888,"path":2889,"stem":2890,"children":2891},"Docker","i-simple-icons-docker","\u002Ftools\u002Fdocker","07.tools\u002F01.docker\u002Findex",[2892,2894,2898,2902,2906,2910,2914,2918,2922,2926,2930,2934,2938,2942,2946,2950,2954,2958],{"title":2893,"path":2889,"stem":2890},"Docker: від нуля до production",{"title":2895,"path":2896,"stem":2897},"Контейнеризація — від проблеми до рішення","\u002Ftools\u002Fdocker\u002Fcontainerization-concept","07.tools\u002F01.docker\u002F01.containerization-concept",{"title":2899,"path":2900,"stem":2901},"Docker — що це і навіщо?","\u002Ftools\u002Fdocker\u002Fdocker-what-and-why","07.tools\u002F01.docker\u002F02.docker-what-and-why",{"title":2903,"path":2904,"stem":2905},"Архітектура Docker Engine","\u002Ftools\u002Fdocker\u002Fdocker-architecture","07.tools\u002F01.docker\u002F03.docker-architecture",{"title":2907,"path":2908,"stem":2909},"Встановлення Docker","\u002Ftools\u002Fdocker\u002Finstallation","07.tools\u002F01.docker\u002F04.installation",{"title":2911,"path":2912,"stem":2913},"Перший контейнер — docker run","\u002Ftools\u002Fdocker\u002Ffirst-container","07.tools\u002F01.docker\u002F05.first-container",{"title":2915,"path":2916,"stem":2917},"Життєвий цикл контейнера","\u002Ftools\u002Fdocker\u002Fcontainer-lifecycle","07.tools\u002F01.docker\u002F06.container-lifecycle",{"title":2919,"path":2920,"stem":2921},"Docker Images — фундаментальні концепції","\u002Ftools\u002Fdocker\u002Fdocker-images-fundamentals","07.tools\u002F01.docker\u002F07.docker-images-fundamentals",{"title":2923,"path":2924,"stem":2925},"Dockerfile — основи","\u002Ftools\u002Fdocker\u002Fdockerfile-basics","07.tools\u002F01.docker\u002F08.dockerfile-basics",{"title":2927,"path":2928,"stem":2929},"Dockerfile — просунуті техніки","\u002Ftools\u002Fdocker\u002Fdockerfile-advanced","07.tools\u002F01.docker\u002F09.dockerfile-advanced",{"title":2931,"path":2932,"stem":2933},"Build Context та кешування шарів","\u002Ftools\u002Fdocker\u002Fbuild-context-and-cache","07.tools\u002F01.docker\u002F10.build-context-and-cache",{"title":2935,"path":2936,"stem":2937},"Реєстри Docker-образів","\u002Ftools\u002Fdocker\u002Fimage-registries","07.tools\u002F01.docker\u002F11.image-registries",{"title":2939,"path":2940,"stem":2941},"Контейнеризація .NET додатків","\u002Ftools\u002Fdocker\u002Fdotnet-containerization","07.tools\u002F01.docker\u002F12.dotnet-containerization",{"title":2943,"path":2944,"stem":2945},"Томи та збереження даних","\u002Ftools\u002Fdocker\u002Fvolumes-and-data","07.tools\u002F01.docker\u002F13.volumes-and-data",{"title":2947,"path":2948,"stem":2949},"Основи мережі в Docker","\u002Ftools\u002Fdocker\u002Fnetworking-basics","07.tools\u002F01.docker\u002F14.networking-basics",{"title":2951,"path":2952,"stem":2953},"Змінні оточення та конфігурація","\u002Ftools\u002Fdocker\u002Fenvironment-and-configuration","07.tools\u002F01.docker\u002F15.environment-and-configuration",{"title":2955,"path":2956,"stem":2957},"Docker Compose — оркестрація контейнерів","\u002Ftools\u002Fdocker\u002Fdocker-compose-basics","07.tools\u002F01.docker\u002F16.docker-compose-basics",{"title":2959,"path":2960,"stem":2961},"Docker Compose — Multi-Service застосунки","\u002Ftools\u002Fdocker\u002Fcompose-multi-service","07.tools\u002F01.docker\u002F17.compose-multi-service",{"title":2963,"icon":2964,"path":2965,"stem":2966,"children":2967},"Kubernetes","simple-icons:kubernetes","\u002Ftools\u002Fkubernetes","07.tools\u002F02.kubernetes\u002Findex",[2968,2970,2974,2978,2982,2986,2990,2994,2998],{"title":2969,"path":2965,"stem":2966},"Kubernetes: від розробки до production",{"title":2971,"path":2972,"stem":2973},"Kubernetes — коли Docker Compose більше не вистачає","\u002Ftools\u002Fkubernetes\u002Fwhy-kubernetes","07.tools\u002F02.kubernetes\u002F01.why-kubernetes",{"title":2975,"path":2976,"stem":2977},"Архітектура Kubernetes — анатомія кластера","\u002Ftools\u002Fkubernetes\u002Fkubernetes-architecture","07.tools\u002F02.kubernetes\u002F02.kubernetes-architecture",{"title":2979,"path":2980,"stem":2981},"Локальне середовище — minikube, kind та k3s","\u002Ftools\u002Fkubernetes\u002Flocal-environment","07.tools\u002F02.kubernetes\u002F03.local-environment",{"title":2983,"path":2984,"stem":2985},"Pod — атомарна одиниця Kubernetes","\u002Ftools\u002Fkubernetes\u002Fpods-and-containers","07.tools\u002F02.kubernetes\u002F04.pods-and-containers",{"title":2987,"path":2988,"stem":2989},"Патерни використання Pod","\u002Ftools\u002Fkubernetes\u002Fpod-patterns","07.tools\u002F02.kubernetes\u002F05.pod-patterns",{"title":2991,"path":2992,"stem":2993},"Deployment — декларативне управління Pod","\u002Ftools\u002Fkubernetes\u002Fdeployment-basics","07.tools\u002F02.kubernetes\u002F06.deployment-basics",{"title":2995,"path":2996,"stem":2997},"Rolling Updates та управління життєвим циклом Deployment","\u002Ftools\u002Fkubernetes\u002Fdeployment-rolling-updates","07.tools\u002F02.kubernetes\u002F07.deployment-rolling-updates",{"title":2999,"path":3000,"stem":3001},"Service — мережева абстракція для Pod","\u002Ftools\u002Fkubernetes\u002Fservices-networking","07.tools\u002F02.kubernetes\u002F08.services-networking",{"title":3003,"icon":3004,"path":3005,"stem":3006,"children":3007,"page":59},"Software Engineering","i-lucide-code-2","\u002Fsoftware-engineering","09.software-engineering",[3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048],{"title":3009,"path":3010,"stem":3011},"1. Аналіз предметної області. Експертні знання та складність","\u002Fsoftware-engineering\u002Fintro-subdomains","09.software-engineering\u002F01.intro-subdomains",{"title":3013,"path":3014,"stem":3015},"2. Обмежені контексти. Інтеграція обмежених контекстів","\u002Fsoftware-engineering\u002Fintegrating-limited-contexts","09.software-engineering\u002F02.integrating-limited-contexts",{"title":3017,"path":3018,"stem":3019},"3. Реалізація простої бізнес-логіки","\u002Fsoftware-engineering\u002Fsimple","09.software-engineering\u002F03.simple",{"title":3021,"path":3022,"stem":3023},"4. Опрацювання складної бізнес-логіки","\u002Fsoftware-engineering\u002Fcomplex-business-logic","09.software-engineering\u002F04.complex-business-logic",{"title":3025,"path":3026,"stem":3027},"5. Моделювання фактора часу. Подієво-орієнтована архітектура.","\u002Fsoftware-engineering\u002Fmodelling-the-time-factor","09.software-engineering\u002F05.modelling-the-time-factor",{"title":3029,"path":3030,"stem":3031},"6. Архітектурні патерни","\u002Fsoftware-engineering\u002Farchitectural-patterns","09.software-engineering\u002F06.architectural-patterns",{"title":3033,"path":3034,"stem":3035},"Паттерни взаємодії","\u002Fsoftware-engineering\u002Fpatterns-of-interaction","09.software-engineering\u002F07.patterns-of-interaction",{"title":3037,"path":3038,"stem":3039},"Евристика проєктування","\u002Fsoftware-engineering\u002Fdesign-heuristics","09.software-engineering\u002F08.design-heuristics",{"title":3041,"path":3042,"stem":3043},"Еволюція проєктних рішень","\u002Fsoftware-engineering\u002Fevolution-of-design-solutions","09.software-engineering\u002F09.evolution-of-design-solutions",{"title":3045,"path":3046,"stem":3047},"EventStorming","\u002Fsoftware-engineering\u002Feventstorming","09.software-engineering\u002F10.eventstorming",{"title":3049,"path":3050,"stem":3051},"DDD на практиці","\u002Fsoftware-engineering\u002Fddd-in-practice","09.software-engineering\u002F11.ddd-in-practice",{"title":3053,"icon":943,"path":3054,"stem":3055,"children":3056,"page":59},"DDD","\u002Fddd","10.ddd",[3057,3061,3065,3069,3073,3077,3081,3085,3089,3093,3097,3101,3105],{"title":3058,"path":3059,"stem":3060},"Аналіз предметної області","\u002Fddd\u002Fdomain-analysis","10.ddd\u002F01.domain-analysis",{"title":3062,"path":3063,"stem":3064},"Експертні знання про предметну область","\u002Fddd\u002Fdomain-expert-knowledge","10.ddd\u002F02.domain-expert-knowledge",{"title":3066,"path":3067,"stem":3068},"Як осмислити складність предметної області","\u002Fddd\u002Fmanaging-domain-complexity","10.ddd\u002F03.managing-domain-complexity",{"title":3070,"path":3071,"stem":3072},"Інтеграція обмежених контекстів","\u002Fddd\u002Fbounded-context-integration","10.ddd\u002F04.bounded-context-integration",{"title":3074,"path":3075,"stem":3076},"Реалізація простої бізнес-логіки","\u002Fddd\u002Fsimple-business-logic","10.ddd\u002F05.simple-business-logic",{"title":3078,"path":3079,"stem":3080},"Обробка складної бізнес-логіки","\u002Fddd\u002Fcomplex-business-logic","10.ddd\u002F06.complex-business-logic",{"title":3082,"path":3083,"stem":3084},"Моделювання фактора часу","\u002Fddd\u002Ftime-modeling","10.ddd\u002F07.time-modeling",{"title":3086,"path":3087,"stem":3088},"Глава 8. Архітектурні Патерни","\u002Fddd\u002Farchitectural-patterns","10.ddd\u002F08.architectural-patterns",{"title":3090,"path":3091,"stem":3092},"Глава 9. Патерни Взаємодії","\u002Fddd\u002Finteraction-patterns","10.ddd\u002F09.interaction-patterns",{"title":3094,"path":3095,"stem":3096},"Глава 10. Проектні Евристики","\u002Fddd\u002Fdesign-heuristics","10.ddd\u002F10.design-heuristics",{"title":3098,"path":3099,"stem":3100},"Глава 11. Еволюція Проектних Рішень","\u002Fddd\u002Fevolution-of-design-decisions","10.ddd\u002F11.evolution-of-design-decisions",{"title":3102,"path":3103,"stem":3104},"Глава 12. EventStorming","\u002Fddd\u002Fevent-storming","10.ddd\u002F12.event-storming",{"title":3106,"path":3107,"stem":3108},"Глава 13. DDD на Практиці","\u002Fddd\u002Fddd-in-practice","10.ddd\u002F13.ddd-in-practice",{"title":3110,"icon":3111,"path":3112,"stem":3113,"children":3114,"page":59},"Media Streaming","i-lucide-video","\u002Fmedia-streaming","11.media-streaming",[3115,3119,3123,3127,3131,3135,3139],{"title":3116,"path":3117,"stem":3118},"01. Магія Стрімінгу: Що відбувається, коли ви натискаєте \"Play\"","\u002Fmedia-streaming\u002Fintroduction","11.media-streaming\u002F01.introduction",{"title":3120,"path":3121,"stem":3122},"02. Анатомія Медіа: Кодеки, Контейнери та Стиснення","\u002Fmedia-streaming\u002Faudio-video-anatomy","11.media-streaming\u002F02.audio-video-anatomy",{"title":3124,"path":3125,"stem":3126},"03. The Gym: FFmpeg Deep Dive","\u002Fmedia-streaming\u002Fffmpeg-gym","11.media-streaming\u002F03.ffmpeg-gym",{"title":3128,"path":3129,"stem":3130},"04. HLS Protocol: HTTP Live Streaming у Деталях","\u002Fmedia-streaming\u002Fhls-protocol","11.media-streaming\u002F04.hls-protocol",{"title":3132,"path":3133,"stem":3134},"05. DASH Protocol: Відкритий Стандарт","\u002Fmedia-streaming\u002Fdash-protocol","11.media-streaming\u002F05.dash-protocol",{"title":3136,"path":3137,"stem":3138},"06. Масштабування: CDN та Adaptive Bitrate","\u002Fmedia-streaming\u002Fcdn-and-adaptive-bitrate","11.media-streaming\u002F06.cdn-and-adaptive-bitrate",{"title":3140,"path":3141,"stem":3142},"07. Війна із Затримкою (Latency)","\u002Fmedia-streaming\u002Frealtime-latency","11.media-streaming\u002F07.realtime-latency",{"title":3144,"icon":3145,"path":3146,"stem":3147,"children":3148,"page":59},"HTML & CSS","i-devicon-html5","\u002Fhtml-css","12.html-css",[3149,3153,3157,3161,3165,3169,3173,3177,3181,3185,3189,3193,3197,3201,3205,3209,3213,3217,3221,3225,3229,3233,3237,3241,3245,3249,3253,3257,3261,3265],{"title":3150,"path":3151,"stem":3152},"Вступ до HTML. Структура документа","\u002Fhtml-css\u002Fintro-html-structure","12.html-css\u002F01.intro-html-structure",{"title":3154,"path":3155,"stem":3156},"Форматування тексту в HTML","\u002Fhtml-css\u002Fhtml-text-formatting","12.html-css\u002F02.html-text-formatting",{"title":3158,"path":3159,"stem":3160},"Посилання та зображення в HTML","\u002Fhtml-css\u002Fhtml-links-images","12.html-css\u002F03.html-links-images",{"title":3162,"path":3163,"stem":3164},"Списки та таблиці в HTML","\u002Fhtml-css\u002Fhtml-lists-tables","12.html-css\u002F04.html-lists-tables",{"title":3166,"path":3167,"stem":3168},"Форми в HTML","\u002Fhtml-css\u002Fhtml-forms","12.html-css\u002F05.html-forms",{"title":3170,"path":3171,"stem":3172},"Семантичні елементи HTML5","\u002Fhtml-css\u002Fhtml-semantic-elements","12.html-css\u002F06.html-semantic-elements",{"title":3174,"path":3175,"stem":3176},"Мультимедіа та розширені елементи HTML","\u002Fhtml-css\u002Fhtml-multimedia-advanced","12.html-css\u002F07.html-multimedia-advanced",{"title":3178,"path":3179,"stem":3180},"Мікророзмітка та SEO в HTML","\u002Fhtml-css\u002Fhtml-microdata-seo","12.html-css\u002F08.html-microdata-seo",{"title":3182,"path":3183,"stem":3184},"Вступ до CSS. Селектори та специфічність","\u002Fhtml-css\u002Fcss-intro-selectors","12.html-css\u002F09.css-intro-selectors",{"title":3186,"path":3187,"stem":3188},"Блокова модель CSS. Відступи. Box Sizing","\u002Fhtml-css\u002Fcss-box-model","12.html-css\u002F10.css-box-model",{"title":3190,"path":3191,"stem":3192},"Розміри у CSS: повний довідник одиниць і ключових слів","\u002Fhtml-css\u002F10a.css-sizing","12.html-css\u002F10a.css-sizing",{"title":3194,"path":3195,"stem":3196},"Типографіка в CSS. Шрифти та текст","\u002Fhtml-css\u002Fcss-typography","12.html-css\u002F11.css-typography",{"title":3198,"path":3199,"stem":3200},"Кольори та фони в CSS","\u002Fhtml-css\u002Fcss-colors-backgrounds","12.html-css\u002F12.css-colors-backgrounds",{"title":3202,"path":3203,"stem":3204},"Тіні та фільтри в CSS","\u002Fhtml-css\u002F12b.css-shadows-filters","12.html-css\u002F12b.css-shadows-filters",{"title":3206,"path":3207,"stem":3208},"CSS Flexbox: Фундамент гнучких макетів","\u002Fhtml-css\u002Fcss-flexbox-fundamentals","12.html-css\u002F13.css-flexbox-fundamentals",{"title":3210,"path":3211,"stem":3212},"CSS Flexbox: Вирівнювання та Позиціонування","\u002Fhtml-css\u002Fcss-flexbox-alignment-sizing-and-patterns","12.html-css\u002F14.css-flexbox-alignment-sizing-and-patterns",{"title":3214,"path":3215,"stem":3216},"CSS Grid. Двовимірний макет. Частина 1","\u002Fhtml-css\u002Fcss-layout-grid","12.html-css\u002F15.css-layout-grid",{"title":3218,"path":3219,"stem":3220},"CSS Grid. Двовимірний макет. Частина 2","\u002Fhtml-css\u002Fcss-layout-grid-advanced","12.html-css\u002F16.css-layout-grid-advanced",{"title":3222,"path":3223,"stem":3224},"Позиціонування в CSS. Z-index. Stacking Context","\u002Fhtml-css\u002Fcss-positioning","12.html-css\u002F17.css-positioning",{"title":3226,"path":3227,"stem":3228},"CSS Анімації та Переходи","\u002Fhtml-css\u002Fcss-animations-transitions","12.html-css\u002F18.css-animations-transitions",{"title":3230,"path":3231,"stem":3232},"Адаптивний дизайн. Media Queries. Частина 1","\u002Fhtml-css\u002Fcss-responsive-media-queries","12.html-css\u002F19.css-responsive-media-queries",{"title":3234,"path":3235,"stem":3236},"Адаптивний дизайн. Частина 2: clamp(), Container Queries, @layer","\u002Fhtml-css\u002Fcss-responsive-advanced","12.html-css\u002F20.css-responsive-advanced",{"title":3238,"path":3239,"stem":3240},"CSS Custom Properties. Методології. Сучасний CSS","\u002Fhtml-css\u002Fcss-variables-methodologies","12.html-css\u002F21.css-variables-methodologies",{"title":3242,"path":3243,"stem":3244},"Сучасний CSS 2023–2025: Нові можливості","\u002Fhtml-css\u002Fcss-modern-features","12.html-css\u002F22.css-modern-features",{"title":3246,"path":3247,"stem":3248},"CSS Nesting, @layer, @scope та @property: нативний препроцесор","\u002Fhtml-css\u002F22a.css-nesting-modern-syntax","12.html-css\u002F22a.css-nesting-modern-syntax",{"title":3250,"path":3251,"stem":3252},"CSS для форм та інтерактивних станів","\u002Fhtml-css\u002Fcss-forms-interactive-states","12.html-css\u002F23.css-forms-interactive-states",{"title":3254,"path":3255,"stem":3256},"Доступність у CSS (CSS Accessibility)","\u002Fhtml-css\u002Fcss-accessibility","12.html-css\u002F24.css-accessibility",{"title":3258,"path":3259,"stem":3260},"CSS-функції та сучасні sizing primitives","\u002Fhtml-css\u002Fcss-functions-sizing","12.html-css\u002F25.css-functions-sizing",{"title":3262,"path":3263,"stem":3264},"Rendering Pipeline і CSS Performance","\u002Fhtml-css\u002Fcss-rendering-performance","12.html-css\u002F26.css-rendering-performance",{"title":3266,"path":3267,"stem":3268},"CSS Best Practices: типові ситуації та правильні рішення","\u002Fhtml-css\u002Fcss-best-practices","12.html-css\u002F27.css-best-practices",{"title":3270,"path":3271,"stem":3272,"children":3273,"page":59},"AWS","\u002Faws","13.aws",[3274,3278,3282,3286,3290,3294,3298,3302,3306,3310,3314,3318,3322,3326,3330,3334,3338,3342],{"title":3275,"path":3276,"stem":3277},"Реєстрація AWS акаунту та студентські програми","\u002Faws\u002Faccount-registration","13.aws\u002F00.account-registration",{"title":3279,"path":3280,"stem":3281},"Вступ до хмарних обчислень та AWS","\u002Faws\u002Fintroduction-to-cloud","13.aws\u002F01.introduction-to-cloud",{"title":3283,"path":3284,"stem":3285},"AWS IAM — Identity and Access Management","\u002Faws\u002Fiam","13.aws\u002F02.iam",{"title":3287,"path":3288,"stem":3289},"AWS IAM CLI — Довідник команд","\u002Faws\u002F02a.iam-doc","13.aws\u002F02a.iam-doc",{"title":3291,"path":3292,"stem":3293},"Docker та контейнеризація в AWS — ECR, ECS та Fargate","\u002Faws\u002Fdocker-ecs","13.aws\u002F03.docker-ecs",{"title":3295,"path":3296,"stem":3297},"AWS ECR \u002F ECS CLI — Довідник команд","\u002Faws\u002F03a.docker-ecs-doc","13.aws\u002F03a.docker-ecs-doc",{"title":3299,"path":3300,"stem":3301},"Amazon EC2 — Elastic Compute Cloud","\u002Faws\u002Fec2","13.aws\u002F04.ec2",{"title":3303,"path":3304,"stem":3305},"AWS EC2 CLI — Довідник команд","\u002Faws\u002F04a.ec2-doc","13.aws\u002F04a.ec2-doc",{"title":3307,"path":3308,"stem":3309},"Elastic Load Balancing та Auto Scaling","\u002Faws\u002Falb-asg","13.aws\u002F05.alb-asg",{"title":3311,"path":3312,"stem":3313},"Amazon S3 — Simple Storage Service","\u002Faws\u002Fs3","13.aws\u002F06.s3",{"title":3315,"path":3316,"stem":3317},"Amazon CloudFront — Content Delivery Network","\u002Faws\u002Fcloudfront","13.aws\u002F07.cloudfront",{"title":3319,"path":3320,"stem":3321},"Amazon RDS — Relational Database Service","\u002Faws\u002Frds","13.aws\u002F08.rds",{"title":3323,"path":3324,"stem":3325},"Amazon DynamoDB — NoSQL Database","\u002Faws\u002Fdynamodb","13.aws\u002F09.dynamodb",{"title":3327,"path":3328,"stem":3329},"AWS Lambda та Serverless Compute","\u002Faws\u002Flambda","13.aws\u002F10.lambda",{"title":3331,"path":3332,"stem":3333},"Amazon Bedrock - Foundation Models, RAG та Agents","\u002Faws\u002Fbedrock","13.aws\u002F22.bedrock",{"title":3335,"path":3336,"stem":3337},"Amazon Rekognition - Комп'ютерний зір","\u002Faws\u002Frekognition","13.aws\u002F23.rekognition",{"title":3339,"path":3340,"stem":3341},"Amazon Textract - Інтелектуальний аналіз документів","\u002Faws\u002Ftextract","13.aws\u002F24.textract",{"title":3343,"path":3344,"stem":3345},"Amazon Polly, Transcribe, Comprehend та Translate","\u002Faws\u002Faudio-nlp-services","13.aws\u002F25.audio-nlp-services",{"title":3347,"path":3348,"stem":3349,"children":3350,"page":59},"Ai Development","\u002Fai-development","14.ai-development",[3351],{"title":3352,"path":3353,"stem":3354,"children":3355,"page":59},"Foundations","\u002Fai-development\u002Ffoundations","14.ai-development\u002F00.foundations",[3356],{"title":3357,"path":3358,"stem":3359},"Модуль 0.1. Що таке Large Language Model (LLM)","\u002Fai-development\u002Ffoundations\u002Fwhat-is-llm","14.ai-development\u002F00.foundations\u002F01.what-is-llm",{"title":3361,"path":3362,"stem":3363,"children":3364,"page":59},"React Native","\u002Freact-native","15.react-native",[3365,3386],{"title":3366,"path":3367,"stem":3368,"children":3369,"page":59},"Version1","\u002Freact-native\u002Fversion1","15.react-native\u002Fversion1",[3370,3374,3378,3382],{"title":3371,"path":3372,"stem":3373},"Що таке React Native","\u002Freact-native\u002Fversion1\u002Fwhat-is-react-native","15.react-native\u002Fversion1\u002F01.what-is-react-native",{"title":3375,"path":3376,"stem":3377},"Як влаштований React Native «під капотом»","\u002Freact-native\u002Fversion1\u002Freact-native-architecture","15.react-native\u002Fversion1\u002F02.react-native-architecture",{"title":3379,"path":3380,"stem":3381},"Від React до React Native: ключові відмінності","\u002Freact-native\u002Fversion1\u002Freact-native-vs-react","15.react-native\u002Fversion1\u002F03.react-native-vs-react",{"title":3383,"path":3384,"stem":3385},"View та Text — фундаментальні будівельні блоки","\u002Freact-native\u002Fversion1\u002Fview-and-text","15.react-native\u002Fversion1\u002F04.view-and-text",{"title":3387,"path":3388,"stem":3389,"children":3390,"page":59},"Version2","\u002Freact-native\u002Fversion2","15.react-native\u002Fversion2",[3391,3395,3399,3403,3407,3411,3415,3419,3423,3427,3431,3435,3439,3443,3447,3451,3455,3459,3463,3467,3471,3475,3479,3483,3487,3491],{"title":3392,"path":3393,"stem":3394},"React Native — навіщо він потрібен","\u002Freact-native\u002Fversion2\u002Fwhy-react-native","15.react-native\u002Fversion2\u002F01.why-react-native",{"title":3396,"path":3397,"stem":3398},"Налаштування середовища розробки","\u002Freact-native\u002Fversion2\u002Fenvironment-setup","15.react-native\u002Fversion2\u002F02.environment-setup",{"title":3400,"path":3401,"stem":3402},"Expo і класичний React Native — два шляхи одного фреймворку","\u002Freact-native\u002Fversion2\u002Fexpo-vs-cli-landscape","15.react-native\u002Fversion2\u002F03.expo-vs-cli-landscape",{"title":3404,"path":3405,"stem":3406},"Середовище Expo — перший запуск","\u002Freact-native\u002Fversion2\u002Fexpo-setup-and-tooling","15.react-native\u002Fversion2\u002F04.expo-setup-and-tooling",{"title":3408,"path":3409,"stem":3410},"Структура проєкту та перші UI-примітиви","\u002Freact-native\u002Fversion2\u002Fproject-structure-and-conventions","15.react-native\u002Fversion2\u002F05.project-structure-and-conventions",{"title":3412,"path":3413,"stem":3414},"Базові компоненти React Native","\u002Freact-native\u002Fversion2\u002Fcore-components","15.react-native\u002Fversion2\u002F06.core-components",{"title":3416,"path":3417,"stem":3418},"Flexbox і layout у React Native","\u002Freact-native\u002Fversion2\u002Fflexbox-and-layout","15.react-native\u002Fversion2\u002F07.flexbox-and-layout",{"title":3420,"path":3421,"stem":3422},"StyleSheet і теми (light \u002F dark)","\u002Freact-native\u002Fversion2\u002Fstylesheet-and-theming","15.react-native\u002Fversion2\u002F08.stylesheet-and-theming",{"title":3424,"path":3425,"stem":3426},"Списки та віртуалізація","\u002Freact-native\u002Fversion2\u002Flists-and-virtualization","15.react-native\u002Fversion2\u002F09.lists-and-virtualization",{"title":3428,"path":3429,"stem":3430},"Форми, ввід і валідація","\u002Freact-native\u002Fversion2\u002Fforms-input-validation","15.react-native\u002Fversion2\u002F10.forms-input-validation",{"title":3432,"path":3433,"stem":3434},"Основи Expo Router","\u002Freact-native\u002Fversion2\u002Fexpo-router-basics","15.react-native\u002Fversion2\u002F11.expo-router-basics",{"title":3436,"path":3437,"stem":3438},"Вкладені навігатори та модалки","\u002Freact-native\u002Fversion2\u002Fnested-navigators-and-modals","15.react-native\u002Fversion2\u002F12.nested-navigators-and-modals",{"title":3440,"path":3441,"stem":3442},"Deep linking і параметри маршрутів","\u002Freact-native\u002Fversion2\u002Fdeep-linking-and-params","15.react-native\u002Fversion2\u002F13.deep-linking-and-params",{"title":3444,"path":3445,"stem":3446},"Робота з мережею та життєвий цикл застосунку","\u002Freact-native\u002Fversion2\u002Fnetworking-and-app-lifecycle","15.react-native\u002Fversion2\u002F14.networking-and-app-lifecycle",{"title":3448,"path":3449,"stem":3450},"Redux Toolkit на мобільних платформах","\u002Freact-native\u002Fversion2\u002Fredux-toolkit-on-mobile","15.react-native\u002Fversion2\u002F15.redux-toolkit-on-mobile",{"title":3452,"path":3453,"stem":3454},"RTK Query на мобільних платформах","\u002Freact-native\u002Fversion2\u002Frtk-query-mobile","15.react-native\u002Fversion2\u002F16.rtk-query-mobile",{"title":3456,"path":3457,"stem":3458},"Автентифікація та Безпечне Зберігання","\u002Freact-native\u002Fversion2\u002Fauth-secure-storage","15.react-native\u002Fversion2\u002F18.auth-secure-storage",{"title":3460,"path":3461,"stem":3462},"Локальне сховище — AsyncStorage, MMKV, SQLite","\u002Freact-native\u002Fversion2\u002Flocal-storage-mmkv-sqlite","15.react-native\u002Fversion2\u002F19.local-storage-mmkv-sqlite",{"title":3464,"path":3465,"stem":3466},"Офлайн-First і синхронізація","\u002Freact-native\u002Fversion2\u002Foffline-first-sync","15.react-native\u002Fversion2\u002F20.offline-first-sync",{"title":3468,"path":3469,"stem":3470},"Камера, медіа та файлова система","\u002Freact-native\u002Fversion2\u002Fcamera-media-filesystem","15.react-native\u002Fversion2\u002F21.camera-media-filesystem",{"title":3472,"path":3473,"stem":3474},"Карти та геолокація","\u002Freact-native\u002Fversion2\u002Fmaps-and-location","15.react-native\u002Fversion2\u002F22.maps-and-location",{"title":3476,"path":3477,"stem":3478},"Жести та дотики","\u002Freact-native\u002Fversion2\u002Fgestures-and-touch","15.react-native\u002Fversion2\u002F23.gestures-and-touch",{"title":3480,"path":3481,"stem":3482},"Анімації з Reanimated","\u002Freact-native\u002Fversion2\u002Freanimated-animations","15.react-native\u002Fversion2\u002F24.reanimated-animations",{"title":3484,"path":3485,"stem":3486},"Push і локальні сповіщення","\u002Freact-native\u002Fversion2\u002Fpush-and-local-notifications","15.react-native\u002Fversion2\u002F25.push-and-local-notifications",{"title":3488,"path":3489,"stem":3490},"Доступність та інтернаціоналізація","\u002Freact-native\u002Fversion2\u002Faccessibility-and-i18n","15.react-native\u002Fversion2\u002F26.accessibility-and-i18n",{"title":3492,"path":3493,"stem":3494},"EAS Build та профілі збірки","\u002Freact-native\u002Fversion2\u002Feas-build-and-profiles","15.react-native\u002Fversion2\u002F29.eas-build-and-profiles",{"title":3496,"path":3497,"stem":3498,"children":3499,"page":59},"Ai Python","\u002Fai-python","16.ai-python",[3500,3504,3508],{"title":3501,"path":3502,"stem":3503},"Вступ до штучного інтелекту та машинного навчання","\u002Fai-python\u002Fintro-to-ai","16.ai-python\u002F01.intro-to-ai",{"title":3505,"path":3506,"stem":3507},"Знайомство з Jupyter Notebook та JupyterLab","\u002Fai-python\u002Fjupyter-notebook","16.ai-python\u002F02.jupyter-notebook",{"title":3509,"path":3510,"stem":3511},"Робота з даними з використанням NumPy і pandas","\u002Fai-python\u002Fnumpy-pandas","16.ai-python\u002F03.numpy-pandas",{"title":3513,"icon":658,"path":3514,"stem":3515,"children":3516,"page":59},"Клієнт-серверна архітектура","\u002Fclient-server-architecture","17.client-server-architecture",[3517,3521],{"title":3518,"path":3519,"stem":3520},"Архітектура клієнт-серверних систем та основи Go","\u002Fclient-server-architecture\u002Fclient-server-architecture-and-go","17.client-server-architecture\u002F01.client-server-architecture-and-go",{"title":3522,"path":3523,"stem":3524},"Система типів та модель пам’яті мови Go","\u002Fclient-server-architecture\u002Ftype-system-and-memory-model","17.client-server-architecture\u002F02.type-system-and-memory-model",{"title":3526,"icon":1946,"path":3527,"stem":3528,"children":3529,"page":59},"Розробка WEB-застосувань","\u002Fweb-development","18.web-development",[3530,3575,3613,3642],{"title":3531,"icon":658,"path":3532,"stem":3533,"children":3534,"page":59},"Архітектура комп'ютерних мереж та мережеві протоколи","\u002Fweb-development\u002Fnetwork-architecture-and-protocols","18.web-development\u002F01.network-architecture-and-protocols",[3535,3539,3543,3547,3551,3555,3559,3563,3567,3571],{"title":3536,"path":3537,"stem":3538},"Еволюція веброзробки та модель клієнт-сервер","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fweb-evolution-client-server","18.web-development\u002F01.network-architecture-and-protocols\u002F01.web-evolution-client-server",{"title":3540,"path":3541,"stem":3542},"Еталонна модель OSI","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fosi-model","18.web-development\u002F01.network-architecture-and-protocols\u002F02.osi-model",{"title":3544,"path":3545,"stem":3546},"Стек протоколів TCP\u002FIP","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Ftcp-ip-stack","18.web-development\u002F01.network-architecture-and-protocols\u002F03.tcp-ip-stack",{"title":3548,"path":3549,"stem":3550},"IP-адресація, порти та сокети","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fip-addressing-ports-sockets","18.web-development\u002F01.network-architecture-and-protocols\u002F04.ip-addressing-ports-sockets",{"title":3552,"path":3553,"stem":3554},"Протоколи TCP та UDP","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Ftcp-udp-protocols","18.web-development\u002F01.network-architecture-and-protocols\u002F05.tcp-udp-protocols",{"title":3556,"path":3557,"stem":3558},"Система доменних імен DNS","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fdns-system","18.web-development\u002F01.network-architecture-and-protocols\u002F06.dns-system",{"title":3560,"path":3561,"stem":3562},"Протокол SMTP та поштові протоколи","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fsmtp-email-protocols","18.web-development\u002F01.network-architecture-and-protocols\u002F07.smtp-email-protocols",{"title":3564,"path":3565,"stem":3566},"Протокол WebSocket","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fwebsocket-protocol","18.web-development\u002F01.network-architecture-and-protocols\u002F08.websocket-protocol",{"title":3568,"path":3569,"stem":3570},"Протокол SSH","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fssh-protocol","18.web-development\u002F01.network-architecture-and-protocols\u002F09.ssh-protocol",{"title":3572,"path":3573,"stem":3574},"Огляд сучасних серверних технологій","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fmodern-backend-technologies","18.web-development\u002F01.network-architecture-and-protocols\u002F10.modern-backend-technologies",{"title":3576,"icon":3577,"path":3578,"stem":3579,"children":3580,"page":59},"Протокол HTTP та захищена передача даних HTTPS","i-lucide-lock","\u002Fweb-development\u002Fhttp-and-https","18.web-development\u002F02.http-and-https",[3581,3585,3589,3593,3597,3601,3605,3609],{"title":3582,"path":3583,"stem":3584},"Вступ до протоколу HTTP та еволюція веб-технологій","\u002Fweb-development\u002Fhttp-and-https\u002Fintroduction-and-evolution","18.web-development\u002F02.http-and-https\u002F01.introduction-and-evolution",{"title":3586,"path":3587,"stem":3588},"Анатомія HTTP-запиту","\u002Fweb-development\u002Fhttp-and-https\u002Fhttp-request-structure","18.web-development\u002F02.http-and-https\u002F02.http-request-structure",{"title":3590,"path":3591,"stem":3592},"Анатомія HTTP-відповіді","\u002Fweb-development\u002Fhttp-and-https\u002Fhttp-response-structure","18.web-development\u002F02.http-and-https\u002F03.http-response-structure",{"title":3594,"path":3595,"stem":3596},"Методи HTTP та їхня семантика","\u002Fweb-development\u002Fhttp-and-https\u002Fhttp-methods","18.web-development\u002F02.http-and-https\u002F04.http-methods",{"title":3598,"path":3599,"stem":3600},"Коди статусу HTTP та їхнє призначення","\u002Fweb-development\u002Fhttp-and-https\u002Fhttp-status-codes","18.web-development\u002F02.http-and-https\u002F05.http-status-codes",{"title":3602,"path":3603,"stem":3604},"HTTP-заголовки та їхнє призначення","\u002Fweb-development\u002Fhttp-and-https\u002Fhttp-headers","18.web-development\u002F02.http-and-https\u002F06.http-headers",{"title":3606,"path":3607,"stem":3608},"CORS та механізми безпеки HTTP","\u002Fweb-development\u002Fhttp-and-https\u002Fcors-and-security","18.web-development\u002F02.http-and-https\u002F07.cors-and-security",{"title":3610,"path":3611,"stem":3612},"HTTPS та криптографічні основи TLS","\u002Fweb-development\u002Fhttp-and-https\u002Fhttps-and-tls","18.web-development\u002F02.http-and-https\u002F08.https-and-tls",{"title":3614,"icon":2326,"path":3615,"stem":3616,"children":3617,"page":59},"Система типів мови TypeScript","\u002Fweb-development\u002Ftypescript-type-system","18.web-development\u002F03.typescript-type-system",[3618,3622,3626,3630,3634,3638],{"title":3619,"path":3620,"stem":3621},"Знайомство з TypeScript Compiler","\u002Fweb-development\u002Ftypescript-type-system\u002Fintroduction-compiler-config","18.web-development\u002F03.typescript-type-system\u002F01.introduction-compiler-config",{"title":3623,"path":3624,"stem":3625},"Примітивні типи даних: string, number, boolean","\u002Fweb-development\u002Ftypescript-type-system\u002Fprimitive-special-types","18.web-development\u002F03.typescript-type-system\u002F02.primitive-special-types",{"title":3627,"path":3628,"stem":3629},"Масиви, кортежі та об","\u002Fweb-development\u002Ftypescript-type-system\u002Farrays-tuples-objects","18.web-development\u002F03.typescript-type-system\u002F03.arrays-tuples-objects",{"title":3631,"path":3632,"stem":3633},"Union типи, Intersection типи та літеральні типи","\u002Fweb-development\u002Ftypescript-type-system\u002Funion-intersection-literals","18.web-development\u002F03.typescript-type-system\u002F04.union-intersection-literals",{"title":3635,"path":3636,"stem":3637},"Type Narrowing (звуження типів)","\u002Fweb-development\u002Ftypescript-type-system\u002Ftype-narrowing","18.web-development\u002F03.typescript-type-system\u002F05.type-narrowing",{"title":3639,"path":3640,"stem":3641},"Типізація функцій","\u002Fweb-development\u002Ftypescript-type-system\u002Ffunctions-typing","18.web-development\u002F03.typescript-type-system\u002F06.functions-typing",{"title":3643,"icon":2326,"path":3644,"stem":3645,"children":3646,"page":59},"Розширені засоби мови TypeScript","\u002Fweb-development\u002Ftypescript-advanced","18.web-development\u002F04.typescript-advanced",[3647,3651,3655,3659,3663,3667],{"title":3648,"path":3649,"stem":3650},"Interfaces та Classes","\u002Fweb-development\u002Ftypescript-advanced\u002Finterfaces-classes","18.web-development\u002F04.typescript-advanced\u002F01.interfaces-classes",{"title":3652,"path":3653,"stem":3654},"Основи Generic типів","\u002Fweb-development\u002Ftypescript-advanced\u002Fgenerics-fundamentals","18.web-development\u002F04.typescript-advanced\u002F02.generics-fundamentals",{"title":3656,"path":3657,"stem":3658},"Enums та Decorators","\u002Fweb-development\u002Ftypescript-advanced\u002Fenums-decorators","18.web-development\u002F04.typescript-advanced\u002F03.enums-decorators",{"title":3660,"path":3661,"stem":3662},"Utility Types","\u002Fweb-development\u002Ftypescript-advanced\u002Futility-types","18.web-development\u002F04.typescript-advanced\u002F04.utility-types",{"title":3664,"path":3665,"stem":3666},"Mapped та Conditional Types","\u002Fweb-development\u002Ftypescript-advanced\u002Fmapped-conditional-types","18.web-development\u002F04.typescript-advanced\u002F05.mapped-conditional-types",{"title":3668,"path":3669,"stem":3670},"Модулі та Strict Mode","\u002Fweb-development\u002Ftypescript-advanced\u002Fmodules-strict-mode","18.web-development\u002F04.typescript-advanced\u002F06.modules-strict-mode",{"title":3672,"path":3673,"stem":3674,"children":3675,"page":59},"Tailwind","\u002Ftailwind","21.tailwind",[3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720],{"title":3677,"path":3678,"stem":3679},"Що таке Tailwind CSS і навіщо він потрібен","\u002Ftailwind\u002Ftailwind-intro-philosophy","21.tailwind\u002F01.tailwind-intro-philosophy",{"title":3681,"path":3682,"stem":3683},"Встановлення та налаштування Tailwind CSS v4","\u002Ftailwind\u002Ftailwind-installation-setup","21.tailwind\u002F02.tailwind-installation-setup",{"title":3685,"path":3686,"stem":3687},"Utility-класи: основи та система Tailwind","\u002Ftailwind\u002Ftailwind-utility-classes-core","21.tailwind\u002F03.tailwind-utility-classes-core",{"title":3689,"path":3690,"stem":3691},"Layout: Flexbox та Grid через Tailwind","\u002Ftailwind\u002Ftailwind-flexbox-grid","21.tailwind\u002F04.tailwind-flexbox-grid",{"title":3693,"path":3694,"stem":3695},"Кастомізація теми через @theme у Tailwind v4","\u002Ftailwind\u002Ftailwind-theme-customization","21.tailwind\u002F05.tailwind-theme-customization",{"title":3697,"path":3698,"stem":3699},"Варіанти: hover, focus, responsive, dark mode та нові v4","\u002Ftailwind\u002Ftailwind-variants-states","21.tailwind\u002F06.tailwind-variants-states",{"title":3701,"path":3702,"stem":3703},"Типографіка та система кольорів у Tailwind v4","\u002Ftailwind\u002Ftailwind-typography-colors","21.tailwind\u002F07.tailwind-typography-colors",{"title":3705,"path":3706,"stem":3707},"Компоненти та повторюваність: @apply, @utility та патерни","\u002Ftailwind\u002Ftailwind-components-patterns","21.tailwind\u002F08.tailwind-components-patterns",{"title":3709,"path":3710,"stem":3711},"Темна тема та система дизайн-токенів у Tailwind v4","\u002Ftailwind\u002Ftailwind-dark-mode-theming","21.tailwind\u002F09.tailwind-dark-mode-theming",{"title":3713,"path":3714,"stem":3715},"Довільні значення та контейнерні запити у Tailwind v4","\u002Ftailwind\u002Ftailwind-arbitrary-container-queries","21.tailwind\u002F10.tailwind-arbitrary-container-queries",{"title":3717,"path":3718,"stem":3719},"Анімації, трансформації та 3D у Tailwind v4","\u002Ftailwind\u002Ftailwind-animations-transforms","21.tailwind\u002F11.tailwind-animations-transforms",{"title":3721,"path":3722,"stem":3723},"Tailwind CLI, PostCSS та інтеграція з фреймворками","\u002Ftailwind\u002Ftailwind-cli-tooling","21.tailwind\u002F12.tailwind-cli-tooling",{"title":3725,"path":3726,"stem":3727},"Тестування компонентів діаграм","\u002Ftest-components","98.test-components",{"id":3729,"title":3564,"body":3730,"description":15650,"extension":15651,"links":15652,"meta":15653,"navigation":4256,"path":3565,"seo":15654,"stem":3566,"__hash__":15655},"docs\u002F18.web-development\u002F01.network-architecture-and-protocols\u002F08.websocket-protocol.md",{"type":3731,"value":3732,"toc":15593},"minimark",[3733,3811,3816,3828,3835,3840,3843,3857,3866,3872,3875,3879,3893,3902,3914,3918,3925,3929,3936,4037,4040,4051,4062,4069,4079,4090,4094,4104,4150,4158,4168,4190,4196,4205,4209,4216,4465,4468,4475,4479,4488,4492,4495,4503,4506,4526,4536,4550,4603,4623,4629,4638,4644,4648,4651,4659,4679,4687,4714,4721,4725,4735,4747,4750,4756,4762,4772,4776,4779,4783,4794,4915,4926,5063,5067,5077,5225,5231,5422,5426,5430,5440,5443,5678,5681,5692,5696,5706,5713,5743,5746,5801,5804,5815,5821,5967,5974,5978,5984,6035,6038,6104,6107,6135,6139,6142,6500,6503,6677,6684,6688,6691,6695,6698,6701,6864,6867,6908,6912,6915,6932,6935,6955,6958,6977,6981,6984,6987,7083,7086,7097,7101,7108,7111,7299,7302,7325,7338,7342,7345,7348,7485,7488,7616,7620,7623,7766,7769,7773,7776,7780,7787,7790,7796,7813,7823,7989,7992,8297,8301,8304,8327,8330,8369,8373,8386,8900,8911,9244,9247,9368,9372,9375,9379,9395,9398,9455,9458,9691,9708,9712,9715,9720,9773,9776,9934,9940,9945,10047,10050,10236,10250,10255,10339,10342,10664,10668,10683,10690,10847,10862,10866,10872,10877,10884,11134,11141,11434,11442,11446,11449,11839,11849,11853,11856,11860,11870,11873,12634,12639,12681,12685,12695,13065,13068,13114,13117,13195,13199,13206,13232,13235,13259,13266,13347,13350,13543,13549,13553,13556,13560,13566,13845,13848,13852,13855,13924,13927,14083,14087,14090,14485,14488,14502,14506,14509,14836,14854,14858,15509,15513,15519,15522,15574,15577,15589],[3734,3735,3736,3766],"card-group",{},[3737,3738,3741],"card",{"icon":3739,"title":3740},"i-lucide-target","🎯 Мета лекції",[3742,3743,3744,3748,3756,3763],"ul",{},[3745,3746,3747],"li",{},"Зрозуміти обмеження традиційного HTTP-протоколу для комунікації у реальному часі та необхідність альтернативних механізмів.",[3745,3749,3750,3751,3755],{},"Опанувати архітектуру протоколу WebSocket, процес рукостискання (",[3752,3753,3754],"em",{},"handshake",") та встановлення повнодуплексного з'єднання.",[3745,3757,3758,3759,3762],{},"Проаналізувати структуру фреймів (",[3752,3760,3761],{},"frames",") WebSocket, типи повідомлень та механізми фрагментації даних.",[3745,3764,3765],{},"Вивчити практичні сценарії застосування WebSocket: чати, нотифікації, онлайн-ігри та відображення даних у реальному часі.",[3737,3767,3770],{"icon":3768,"title":3769},"i-lucide-key","🔑 Ключові терміни",[3742,3771,3772,3783,3793,3799,3805],{},[3745,3773,3774,3778,3779,3782],{},[3775,3776,3777],"strong",{},"WebSocket:"," стандарт повнодуплексного зв'язку (",[3752,3780,3781],{},"full-duplex communication",") поверх одного TCP-з'єднання.",[3745,3784,3785,3788,3789,3792],{},[3775,3786,3787],{},"Handshake:"," процес рукостискання для оновлення (",[3752,3790,3791],{},"upgrade",") HTTP-з'єднання до WebSocket.",[3745,3794,3795,3798],{},[3775,3796,3797],{},"Frame:"," базовий блок даних протоколу WebSocket з визначеною структурою заголовка та корисного навантаження.",[3745,3800,3801,3804],{},[3775,3802,3803],{},"Opcode:"," код операції у фреймі, що визначає тип повідомлення (текст, бінарні дані, керуючі команди).",[3745,3806,3807,3810],{},[3775,3808,3809],{},"Full-Duplex:"," режим одночасної двосторонньої передачі даних між клієнтом та сервером.",[3812,3813,3815],"h2",{"id":3814},"архітектурний-контекст-та-еволюція-систем","Архітектурний контекст та еволюція систем",[3817,3818,3819,3820,3823,3824,3827],"p",{},"Для розуміння необхідності протоколу WebSocket розгляньмо спочатку традиційний підхід до обміну даними у вебзастосунках. Протокол HTTP є фундаментальним засобом комунікації у світовій павутині та заснований на парадигмі ",[3775,3821,3822],{},"запит-відповідь"," (",[3752,3825,3826],{},"request-response","), що означає: кожна взаємодія ініціюється клієнтом, сервер обробляє запит і повертає відповідь, після чого з'єднання закривається або переходить у режим очікування наступного запиту від того самого клієнта.",[3817,3829,3830,3831,3834],{},"Цей підхід ідеально підходить для традиційних вебдодатків, де користувач натискає посилання чи відправляє форму, а сервер формує HTML-сторінку або JSON-відповідь. Проте із зростанням вимог до інтерактивності вебзастосунків — онлайн-чати, фінансові тікери, відеоконференції, багатокористувацькі ігри, дошки для спільної роботи — обмеження HTTP стають критичними бар'єрами на шляху до справжньої комунікації у реальному часі (",[3752,3832,3833],{},"real-time communication",").",[3836,3837,3839],"h3",{"id":3838},"проблеми-http-для-реального-часу","Проблеми HTTP для реального часу",[3817,3841,3842],{},"Розгляньмо типову задачу: онлайн-чат, де повідомлення одного користувача має миттєво з'явитися у браузері іншого користувача без натискання кнопки «Оновити». У класичній HTTP-архітектурі сервер не може самостійно ініціювати передачу даних клієнту — він лише відповідає на запити. Для обходу цього обмеження розробники застосовували кілька технік, кожна з яких має свої недоліки:",[3817,3844,3845,3852,3853,3856],{},[3775,3846,3847,3848,3851],{},"1. Polling (",[3752,3849,3850],{},"опитування","):"," Клієнт періодично (наприклад, кожні 2 секунди) відправляє HTTP-запит на сервер із питанням: «Чи є нові повідомлення?». Якщо сервер повертає порожню відповідь, клієнт чекає інтервал та повторює запит. Цей підхід генерує величезну кількість зайвих запитів — якщо повідомлення надходять рідко, 90% запитів повертають відповідь «немає оновлень», марнуючи пропускну здатність мережі та ресурси сервера. Крім того, затримка (",[3752,3854,3855],{},"latency",") між надсиланням повідомлення і його відображенням у клієнта може сягати інтервалу опитування.",[3817,3858,3859,3865],{},[3775,3860,3861,3862,3851],{},"2. Long Polling (",[3752,3863,3864],{},"довге опитування"," Клієнт відправляє запит, але сервер не відповідає миттєво — він утримує з'єднання відкритим, поки не з'явиться нове повідомлення або не спрацює таймаут (наприклад, 30 секунд). Коли сервер отримує подію, він негайно повертає відповідь, клієнт обробляє дані та одразу відкриває новий long-polling запит. Цей метод зменшує кількість порожніх запитів, але все одно вимагає постійного встановлення нових HTTP-з'єднань, обробки заголовків та підтримки тисяч відкритих з'єднань на сервері.",[3817,3867,3868,3871],{},[3775,3869,3870],{},"3. Server-Sent Events (SSE):"," Стандарт HTML5, що дозволяє серверу відправляти подієво-орієнтовані повідомлення клієнту через довготривале HTTP-з'єднання. SSE підтримує лише односпрямований потік — від сервера до клієнта. Якщо клієнту потрібно відправити дані на сервер, він все одно має використовувати окремі HTTP-запити. Це робить SSE придатним для потокової трансляції оновлень (новини, котирування акцій), але недостатнім для двосторонніх інтерактивних застосунків.",[3817,3873,3874],{},"Кожен із цих методів є компромісом, що намагається втиснути комунікацію у реальному часі у рамки протоколу, який спочатку для цього не призначався. Результат — надлишкові HTTP-заголовки при кожному обміні даними (типовий HTTP-заголовок займає 500–1000 байт, навіть якщо корисне навантаження — 10 байт JSON), постійне встановлення та розрив TCP-з'єднань, складність підтримки стану на сервері та незадовільна затримка.",[3836,3876,3878],{"id":3877},"народження-websocket","Народження WebSocket",[3817,3880,3881,3882,3885,3886,3823,3889,3892],{},"Для розв'язання цих фундаментальних обмежень у 2008–2011 роках було розроблено та стандартизовано протокол ",[3775,3883,3884],{},"WebSocket"," (RFC 6455, опублікований IETF у 2011 році). WebSocket пропонує принципово інший підхід: замість нескінченного потоку незалежних запитів та відповідей встановлюється одне довготривале TCP-з'єднання, що працює у ",[3775,3887,3888],{},"повнодуплексному режимі",[3752,3890,3891],{},"full-duplex mode","). Це означає, що обидві сторони — клієнт і сервер — можуть ініціювати передачу даних у будь-який момент без попереднього запиту з протилежного боку. Уявіть телефонну розмову, де обидва співрозмовники можуть говорити одночасно (технічно), на противагу звичайній пошті, де лист завжди є відповіддю на попередній лист.",[3817,3894,3895,3896,3823,3899,3901],{},"WebSocket дозволяє усунути весь надлишковий HTTP-overhead після початкового рукостискання: дані передаються у компактних ",[3775,3897,3898],{},"фреймах",[3752,3900,3761],{},") із мінімальним заголовком (2–14 байт), що робить протокол надзвичайно ефективним для передачі частих невеликих повідомлень. Типова затримка обміну повідомленнями через WebSocket може бути на рівні 10–50 мілісекунд, що робить його придатним навіть для найвимогливіших застосунків — онлайн-шутерів, фінансових торгових платформ, систем телемедицини.",[3903,3904,3905,3906,3909,3910,3913],"note",{},"Протокол WebSocket офіційно стандартизовано у специфікації ",[3775,3907,3908],{},"RFC 6455"," (2011) та інтегровано у стандарт HTML5 як Web API ",[3911,3912,3884],"code",{},". Усі сучасні браузери та серверні платформи (Node.js, Python, Java, Go, C#) мають вбудовану підтримку WebSocket.",[3812,3915,3917],{"id":3916},"процес-встановлення-зєднання-http-upgrade-handshake","Процес встановлення з'єднання: HTTP Upgrade Handshake",[3817,3919,3920,3921,3924],{},"WebSocket — це не окремий протокол транспортного рівня (як TCP чи UDP), а прикладний протокол, що працює поверх звичайного TCP-з'єднання. Для забезпечення сумісності з існуючою інфраструктурою веба — проксі-серверами, брандмауерами, балансувальниками навантаження, які розуміють HTTP — WebSocket використовує ",[3775,3922,3923],{},"HTTP Upgrade Handshake"," як механізм початкового встановлення з'єднання.",[3836,3926,3928],{"id":3927},"ініціація-зєднання-клієнтом","Ініціація з'єднання клієнтом",[3817,3930,3931,3932,3935],{},"Процес починається з того, що клієнт (зазвичай веббраузер) відправляє спеціальний HTTP-запит на сервер. Це звичайний HTTP-запит методом ",[3911,3933,3934],{},"GET",", але з кількома специфічними заголовками, що сигналізують серверу про бажання клієнта оновити протокол з HTTP до WebSocket. Розгляньмо типовий запит рукостискання:",[3937,3938,3943],"pre",{"className":3939,"code":3940,"language":3941,"meta":3942,"style":3942},"language-http shiki shiki-themes light-plus dark-plus dark-plus","GET \u002Fchat HTTP\u002F1.1\nHost: example.com\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\nSec-WebSocket-Version: 13\nOrigin: https:\u002F\u002Fexample.com\n","http","",[3911,3944,3945,3968,3982,3993,4004,4015,4026],{"__ignoreMap":3942},[3946,3947,3950,3953,3957,3961,3964],"span",{"class":3948,"line":3949},"line",1,[3946,3951,3934],{"class":3952},"s8xlr",[3946,3954,3956],{"class":3955},"sHH4Y"," \u002Fchat ",[3946,3958,3960],{"class":3959},"su1O8","HTTP",[3946,3962,3963],{"class":3955},"\u002F",[3946,3965,3967],{"class":3966},"sJj4R","1.1\n",[3946,3969,3971,3975,3978],{"class":3948,"line":3970},2,[3946,3972,3974],{"class":3973},"sKtos","Host",[3946,3976,3977],{"class":3959},":",[3946,3979,3981],{"class":3980},"sbdoH"," example.com\n",[3946,3983,3985,3988,3990],{"class":3948,"line":3984},3,[3946,3986,3987],{"class":3973},"Upgrade",[3946,3989,3977],{"class":3959},[3946,3991,3992],{"class":3980}," websocket\n",[3946,3994,3996,3999,4001],{"class":3948,"line":3995},4,[3946,3997,3998],{"class":3973},"Connection",[3946,4000,3977],{"class":3959},[3946,4002,4003],{"class":3980}," Upgrade\n",[3946,4005,4007,4010,4012],{"class":3948,"line":4006},5,[3946,4008,4009],{"class":3973},"Sec-WebSocket-Key",[3946,4011,3977],{"class":3959},[3946,4013,4014],{"class":3980}," dGhlIHNhbXBsZSBub25jZQ==\n",[3946,4016,4018,4021,4023],{"class":3948,"line":4017},6,[3946,4019,4020],{"class":3973},"Sec-WebSocket-Version",[3946,4022,3977],{"class":3959},[3946,4024,4025],{"class":3980}," 13\n",[3946,4027,4029,4032,4034],{"class":3948,"line":4028},7,[3946,4030,4031],{"class":3973},"Origin",[3946,4033,3977],{"class":3959},[3946,4035,4036],{"class":3980}," https:\u002F\u002Fexample.com\n",[3817,4038,4039],{},"Проаналізуймо кожен заголовок детально:",[3817,4041,4042,4047,4048,4050],{},[3775,4043,4044],{},[3911,4045,4046],{},"Upgrade: websocket"," — інформує сервер, що клієнт бажає змінити протокол з HTTP на WebSocket. Заголовок ",[3911,4049,3987],{}," є стандартним механізмом HTTP\u002F1.1 для зміни протоколів (також використовується для переходу з HTTP\u002F1.1 на HTTP\u002F2).",[3817,4052,4053,4058,4059,4061],{},[3775,4054,4055],{},[3911,4056,4057],{},"Connection: Upgrade"," — вказує, що з'єднання не повинно закриватися після відповіді, а має бути оновлено до іншого протоколу згідно з заголовком ",[3911,4060,3987],{},".",[3817,4063,4064,4068],{},[3775,4065,4066],{},[3911,4067,4009],{}," — випадкове 16-байтове значення, закодоване у форматі Base64. Це криптографічний ключ, який сервер використовує для підтвердження того, що він розуміє протокол WebSocket і не просто повертає стандартну HTTP-відповідь на будь-який запит. Ключ генерується клієнтом випадково при кожному з'єднанні та є частиною механізму захисту від випадкового прийняття WebSocket-з'єднання HTTP-сервером, що не підтримує цей протокол.",[3817,4070,4071,4076,4077,4061],{},[3775,4072,4073],{},[3911,4074,4075],{},"Sec-WebSocket-Version: 13"," — номер версії протоколу WebSocket. Версія 13 є останньою стабільною версією, визначеною у RFC 6455. Якщо сервер не підтримує зазначену версію, він може повернути список підтримуваних версій у заголовку відповіді ",[3911,4078,4020],{},[3817,4080,4081,4085,4086,4089],{},[3775,4082,4083],{},[3911,4084,4031],{}," — заголовок, що містить походження (",[3752,4087,4088],{},"origin",") сторінки, яка ініціює WebSocket-з'єднання. Сервер може використовувати цей заголовок для перевірки політики Same-Origin або Cross-Origin, щоб дозволити або заблокувати з'єднання з певних доменів. Це важливий механізм безпеки проти атак типу Cross-Site WebSocket Hijacking (CSWSH).",[3836,4091,4093],{"id":4092},"відповідь-сервера-підтвердження-рукостискання","Відповідь сервера: підтвердження рукостискання",[3817,4095,4096,4097,4100,4101,4103],{},"Якщо сервер підтримує протокол WebSocket і приймає запит, він повертає HTTP-відповідь зі статус-кодом ",[3775,4098,4099],{},"101 Switching Protocols",". Цей код означає, що сервер погоджується змінити протокол згідно з заголовком ",[3911,4102,3987],{}," клієнта. Типова відповідь виглядає так:",[3937,4105,4107],{"className":3939,"code":4106,"language":3941,"meta":3942,"style":3942},"HTTP\u002F1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\n",[3911,4108,4109,4124,4132,4140],{"__ignoreMap":3942},[3946,4110,4111,4113,4115,4118,4121],{"class":3948,"line":3949},[3946,4112,3960],{"class":3959},[3946,4114,3963],{"class":3955},[3946,4116,4117],{"class":3966},"1.1",[3946,4119,4120],{"class":3966}," 101",[3946,4122,4123],{"class":3980}," Switching Protocols\n",[3946,4125,4126,4128,4130],{"class":3948,"line":3970},[3946,4127,3987],{"class":3973},[3946,4129,3977],{"class":3959},[3946,4131,3992],{"class":3980},[3946,4133,4134,4136,4138],{"class":3948,"line":3984},[3946,4135,3998],{"class":3973},[3946,4137,3977],{"class":3959},[3946,4139,4003],{"class":3980},[3946,4141,4142,4145,4147],{"class":3948,"line":3995},[3946,4143,4144],{"class":3973},"Sec-WebSocket-Accept",[3946,4146,3977],{"class":3959},[3946,4148,4149],{"class":3980}," s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\n",[3817,4151,4152,4157],{},[3775,4153,4154],{},[3911,4155,4156],{},"HTTP\u002F1.1 101 Switching Protocols"," — статус-код, що сигналізує успішне оновлення протоколу. Після відправки цієї відповіді сервер і клієнт більше не обмінюються HTTP-повідомленнями — всі подальші дані передаються у форматі фреймів WebSocket.",[3817,4159,4160,4164,4165,4167],{},[3775,4161,4162],{},[3911,4163,4144],{}," — це криптографічно обчислене значення, яке сервер формує на основі ",[3911,4166,4009],{},", отриманого від клієнта. Алгоритм обчислення такий:",[4169,4170,4171,4177,4184,4187],"ol",{},[3745,4172,4173,4174,4176],{},"Взяти значення ",[3911,4175,4009],{}," з запиту клієнта.",[3745,4178,4179,4180,4183],{},"Конкатенувати його з магічним рядком-константою ",[3911,4181,4182],{},"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"," (визначено у RFC 6455).",[3745,4185,4186],{},"Обчислити SHA-1 хеш отриманого рядка.",[3745,4188,4189],{},"Закодувати результат у форматі Base64.",[3817,4191,4192,4193,4195],{},"Цей механізм гарантує, що сервер дійсно обробив WebSocket-запит, а не просто повернув шаблонну відповідь. Клієнт після отримання відповіді виконує ту саму процедуру обчислення і порівнює результат з ",[3911,4194,4144],{},". Якщо значення збігаються — рукостискання вважається успішним, і з'єднання переходить у режим WebSocket.",[3903,4197,4198,4199,4201,4202,4204],{},"Магічний рядок ",[3911,4200,4182],{}," у специфікації RFC 6455 обрано як унікальний GUID, що не може бути випадково згенерований. Його присутність у процесі обчислення ",[3911,4203,4144],{}," унеможливлює підробку відповіді сервером, що не розуміє протокол WebSocket.",[3836,4206,4208],{"id":4207},"діаграма-послідовності-встановлення-зєднання","Діаграма послідовності встановлення з'єднання",[3817,4210,4211,4212,4215],{},"Візуалізуймо процес рукостискання у вигляді діаграми послідовності (",[3752,4213,4214],{},"sequence diagram","), що ілюструє крок за кроком взаємодію клієнта, мережевого стека та сервера:",[4217,4218,4220],"plant-uml",{"alt":4219},"Процес WebSocket Handshake між клієнтом та сервером",[3937,4221,4225],{"className":4222,"code":4223,"language":4224,"meta":3942,"style":3942},"language-plantuml shiki shiki-themes light-plus dark-plus dark-plus","@startuml\nskinparam style plain\nautonumber\nskinparam backgroundColor #FFFFFF\nskinparam sequenceMessageAlign center\n\nactor \"Клієнт (Browser)\" as Client #DBEAFE\nparticipant \"TCP\u002FIP Stack\" as TCP #E2E8F0\nparticipant \"WebSocket Server\" as Server #DCFCE7\n\nClient -> TCP : Встановлення TCP-з'єднання (3-Way Handshake)\nactivate TCP\nTCP -> Server : SYN, SYN-ACK, ACK\nactivate Server\nTCP --> Client : TCP з'єднання встановлено (ESTABLISHED)\n\nClient -> Server : HTTP GET \u002Fchat\\nUpgrade: websocket\\nSec-WebSocket-Key: [random]\nnote right of Client #FEF3C7\n  Клієнт відправляє HTTP-запит\n  з заголовками рукостискання\nend note\n\nServer -> Server : Перевірка заголовків:\\n- Upgrade: websocket\\n- Connection: Upgrade\\n- Sec-WebSocket-Version: 13\nServer -> Server : Обчислення Sec-WebSocket-Accept:\\nSHA1(Key + Magic GUID) -> Base64\n\nServer -> Client : HTTP\u002F1.1 101 Switching Protocols\\nUpgrade: websocket\\nSec-WebSocket-Accept: [hash]\nnote left of Server #DCFCE7\n  Сервер підтверджує оновлення\n  протоколу та повертає хеш\nend note\n\nClient -> Client : Верифікація Sec-WebSocket-Accept:\\nПорівняння з локально обчисленим значенням\nnote over Client, Server #D1FAE5\n  **WebSocket з'єднання активне.**\n  З цього моменту обмін даними відбувається\n  у форматі фреймів WebSocket, а не HTTP.\nend note\n\nClient \u003C-> Server : Обмін фреймами (Text, Binary, Ping\u002FPong, Close)\ndeactivate Server\ndeactivate TCP\n@enduml\n","plantuml",[3911,4226,4227,4232,4237,4242,4247,4252,4258,4263,4269,4275,4280,4286,4292,4298,4304,4310,4315,4321,4327,4333,4339,4345,4350,4356,4362,4367,4373,4379,4385,4391,4396,4401,4407,4413,4419,4425,4431,4436,4441,4447,4453,4459],{"__ignoreMap":3942},[3946,4228,4229],{"class":3948,"line":3949},[3946,4230,4231],{},"@startuml\n",[3946,4233,4234],{"class":3948,"line":3970},[3946,4235,4236],{},"skinparam style plain\n",[3946,4238,4239],{"class":3948,"line":3984},[3946,4240,4241],{},"autonumber\n",[3946,4243,4244],{"class":3948,"line":3995},[3946,4245,4246],{},"skinparam backgroundColor #FFFFFF\n",[3946,4248,4249],{"class":3948,"line":4006},[3946,4250,4251],{},"skinparam sequenceMessageAlign center\n",[3946,4253,4254],{"class":3948,"line":4017},[3946,4255,4257],{"emptyLinePlaceholder":4256},true,"\n",[3946,4259,4260],{"class":3948,"line":4028},[3946,4261,4262],{},"actor \"Клієнт (Browser)\" as Client #DBEAFE\n",[3946,4264,4266],{"class":3948,"line":4265},8,[3946,4267,4268],{},"participant \"TCP\u002FIP Stack\" as TCP #E2E8F0\n",[3946,4270,4272],{"class":3948,"line":4271},9,[3946,4273,4274],{},"participant \"WebSocket Server\" as Server #DCFCE7\n",[3946,4276,4278],{"class":3948,"line":4277},10,[3946,4279,4257],{"emptyLinePlaceholder":4256},[3946,4281,4283],{"class":3948,"line":4282},11,[3946,4284,4285],{},"Client -> TCP : Встановлення TCP-з'єднання (3-Way Handshake)\n",[3946,4287,4289],{"class":3948,"line":4288},12,[3946,4290,4291],{},"activate TCP\n",[3946,4293,4295],{"class":3948,"line":4294},13,[3946,4296,4297],{},"TCP -> Server : SYN, SYN-ACK, ACK\n",[3946,4299,4301],{"class":3948,"line":4300},14,[3946,4302,4303],{},"activate Server\n",[3946,4305,4307],{"class":3948,"line":4306},15,[3946,4308,4309],{},"TCP --> Client : TCP з'єднання встановлено (ESTABLISHED)\n",[3946,4311,4313],{"class":3948,"line":4312},16,[3946,4314,4257],{"emptyLinePlaceholder":4256},[3946,4316,4318],{"class":3948,"line":4317},17,[3946,4319,4320],{},"Client -> Server : HTTP GET \u002Fchat\\nUpgrade: websocket\\nSec-WebSocket-Key: [random]\n",[3946,4322,4324],{"class":3948,"line":4323},18,[3946,4325,4326],{},"note right of Client #FEF3C7\n",[3946,4328,4330],{"class":3948,"line":4329},19,[3946,4331,4332],{},"  Клієнт відправляє HTTP-запит\n",[3946,4334,4336],{"class":3948,"line":4335},20,[3946,4337,4338],{},"  з заголовками рукостискання\n",[3946,4340,4342],{"class":3948,"line":4341},21,[3946,4343,4344],{},"end note\n",[3946,4346,4348],{"class":3948,"line":4347},22,[3946,4349,4257],{"emptyLinePlaceholder":4256},[3946,4351,4353],{"class":3948,"line":4352},23,[3946,4354,4355],{},"Server -> Server : Перевірка заголовків:\\n- Upgrade: websocket\\n- Connection: Upgrade\\n- Sec-WebSocket-Version: 13\n",[3946,4357,4359],{"class":3948,"line":4358},24,[3946,4360,4361],{},"Server -> Server : Обчислення Sec-WebSocket-Accept:\\nSHA1(Key + Magic GUID) -> Base64\n",[3946,4363,4365],{"class":3948,"line":4364},25,[3946,4366,4257],{"emptyLinePlaceholder":4256},[3946,4368,4370],{"class":3948,"line":4369},26,[3946,4371,4372],{},"Server -> Client : HTTP\u002F1.1 101 Switching Protocols\\nUpgrade: websocket\\nSec-WebSocket-Accept: [hash]\n",[3946,4374,4376],{"class":3948,"line":4375},27,[3946,4377,4378],{},"note left of Server #DCFCE7\n",[3946,4380,4382],{"class":3948,"line":4381},28,[3946,4383,4384],{},"  Сервер підтверджує оновлення\n",[3946,4386,4388],{"class":3948,"line":4387},29,[3946,4389,4390],{},"  протоколу та повертає хеш\n",[3946,4392,4394],{"class":3948,"line":4393},30,[3946,4395,4344],{},[3946,4397,4399],{"class":3948,"line":4398},31,[3946,4400,4257],{"emptyLinePlaceholder":4256},[3946,4402,4404],{"class":3948,"line":4403},32,[3946,4405,4406],{},"Client -> Client : Верифікація Sec-WebSocket-Accept:\\nПорівняння з локально обчисленим значенням\n",[3946,4408,4410],{"class":3948,"line":4409},33,[3946,4411,4412],{},"note over Client, Server #D1FAE5\n",[3946,4414,4416],{"class":3948,"line":4415},34,[3946,4417,4418],{},"  **WebSocket з'єднання активне.**\n",[3946,4420,4422],{"class":3948,"line":4421},35,[3946,4423,4424],{},"  З цього моменту обмін даними відбувається\n",[3946,4426,4428],{"class":3948,"line":4427},36,[3946,4429,4430],{},"  у форматі фреймів WebSocket, а не HTTP.\n",[3946,4432,4434],{"class":3948,"line":4433},37,[3946,4435,4344],{},[3946,4437,4439],{"class":3948,"line":4438},38,[3946,4440,4257],{"emptyLinePlaceholder":4256},[3946,4442,4444],{"class":3948,"line":4443},39,[3946,4445,4446],{},"Client \u003C-> Server : Обмін фреймами (Text, Binary, Ping\u002FPong, Close)\n",[3946,4448,4450],{"class":3948,"line":4449},40,[3946,4451,4452],{},"deactivate Server\n",[3946,4454,4456],{"class":3948,"line":4455},41,[3946,4457,4458],{},"deactivate TCP\n",[3946,4460,4462],{"class":3948,"line":4461},42,[3946,4463,4464],{},"@enduml\n",[3817,4466,4467],{},"Після успішного завершення рукостискання TCP-з'єднання залишається відкритим і переходить у режим WebSocket. Обидві сторони тепер можуть відправляти та отримувати повідомлення у будь-який момент без додаткових HTTP-заголовків та запитів.",[4469,4470,4471,4472,4474],"warning",{},"Якщо сервер не підтримує WebSocket або відхиляє з'єднання з причин безпеки (наприклад, неправильний ",[3911,4473,4031],{},"), він поверне стандартний HTTP-статус помилки (400 Bad Request, 403 Forbidden, 426 Upgrade Required) замість 101 Switching Protocols. У цьому випадку клієнт отримає помилку, і WebSocket-з'єднання не буде встановлено.",[3812,4476,4478],{"id":4477},"структура-та-типи-фреймів-websocket","Структура та типи фреймів WebSocket",[3817,4480,4481,4482,3823,4485,4487],{},"Після встановлення з'єднання всі дані між клієнтом і сервером передаються у вигляді ",[3775,4483,4484],{},"фреймів",[3752,4486,3761],{},"). Фрейм є базовою одиницею передачі даних у протоколі WebSocket і має чітко визначену структуру бітових полів. На відміну від HTTP, де кожен запит і відповідь містять текстові заголовки, фрейми WebSocket є бінарними структурами з мінімальним накладом — базовий заголовок займає лише 2 байти, що робить протокол надзвичайно ефективним для передачі невеликих повідомлень.",[3836,4489,4491],{"id":4490},"бітова-структура-фрейма","Бітова структура фрейма",[3817,4493,4494],{},"Згідно зі специфікацією RFC 6455, фрейм WebSocket починається з обов'язкового 2-байтового заголовка, за яким можуть слідувати додаткові байти залежно від розміру корисного навантаження та наявності маски. Структура фрейма виглядає так:",[3937,4496,4501],{"className":4497,"code":4499,"language":4500},[4498],"language-text"," 0                   1                   2                   3\n 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n+-+-+-+-+-------+-+-------------+-------------------------------+\n|F|R|R|R| opcode|M| Payload len |    Extended payload length    |\n|I|S|S|S|  (4)  |A|     (7)     |             (16\u002F64)           |\n|N|V|V|V|       |S|             |   (if payload len==126\u002F127)   |\n| |1|2|3|       |K|             |                               |\n+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +\n|     Extended payload length continued, if payload len == 127  |\n+ - - - - - - - - - - - - - - - +-------------------------------+\n|                               | Masking-key, if MASK set to 1 |\n+-------------------------------+-------------------------------+\n| Masking-key (continued)       |          Payload Data         |\n+-------------------------------- - - - - - - - - - - - - - - - +\n:                     Payload Data continued ...                :\n+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +\n|                     Payload Data continued ...                |\n+---------------------------------------------------------------+\n","text",[3911,4502,4499],{"__ignoreMap":3942},[3817,4504,4505],{},"Розглянемо ключові поля цієї структури:",[3817,4507,4508,4511,4512,4515,4516,4519,4520,4523,4524,4061],{},[3775,4509,4510],{},"FIN (1 біт):"," прапорець завершення (",[3752,4513,4514],{},"finish","). Якщо ",[3911,4517,4518],{},"FIN = 1",", це означає, що даний фрейм є останнім фрагментом повідомлення. Якщо ",[3911,4521,4522],{},"FIN = 0",", повідомлення продовжується у наступних фреймах (механізм фрагментації, який розглянемо далі). Більшість простих повідомлень вміщується в один фрейм, тому зазвичай ",[3911,4525,4518],{},[3817,4527,4528,4531,4532,4535],{},[3775,4529,4530],{},"RSV1, RSV2, RSV3 (по 1 біту кожен):"," резервні біти для розширень протоколу. У стандартній реалізації ці біти мають бути встановлені у 0, якщо не узгоджено спеціальних розширень (наприклад, компресія ",[3911,4533,4534],{},"permessage-deflate","). Якщо сервер отримує фрейм з ненульовими RSV-бітами без попереднього узгодження розширення, він повинен закрити з'єднання з помилкою.",[3817,4537,4538,4541,4542,4545,4546,4549],{},[3775,4539,4540],{},"Opcode (4 біти):"," код операції, що визначає інтерпретацію корисного навантаження фрейма. Опкоди поділяються на дві категорії: фрейми даних (",[3752,4543,4544],{},"data frames",") та керуючі фрейми (",[3752,4547,4548],{},"control frames","). Основні значення:",[3742,4551,4552,4562,4571,4581,4591,4597],{},[3745,4553,4554,4557,4558,4561],{},[3911,4555,4556],{},"0x0"," (продовження, ",[3752,4559,4560],{},"continuation","): вказує, що це фрагмент багаточастинного повідомлення.",[3745,4563,4564,4567,4568,4570],{},[3911,4565,4566],{},"0x1"," (текст, ",[3752,4569,4500],{},"): корисне навантаження є текстовими даними у кодуванні UTF-8.",[3745,4572,4573,4576,4577,4580],{},[3911,4574,4575],{},"0x2"," (бінарні дані, ",[3752,4578,4579],{},"binary","): корисне навантаження є довільними бінарними даними.",[3745,4582,4583,4586,4587,4590],{},[3911,4584,4585],{},"0x8"," (закриття, ",[3752,4588,4589],{},"close","): ініціює процедуру закриття з'єднання.",[3745,4592,4593,4596],{},[3911,4594,4595],{},"0x9"," (ping): запит перевірки доступності з'єднання.",[3745,4598,4599,4602],{},[3911,4600,4601],{},"0xA"," (pong): відповідь на ping-фрейм.",[3817,4604,4605,4608,4609,3823,4612,4615,4616,3823,4619,4622],{},[3775,4606,4607],{},"MASK (1 біт):"," вказує, чи замасковане корисне навантаження фрейма. Згідно зі специфікацією, ",[3775,4610,4611],{},"усі фрейми, відправлені клієнтом, мають бути замасковані",[3911,4613,4614],{},"MASK = 1","), тоді як фрейми від сервера ",[3775,4617,4618],{},"не повинні бути замасковані",[3911,4620,4621],{},"MASK = 0","). Це асиметричне правило введено для захисту від атак на проксі-сервери, що можуть неправильно інтерпретувати незашифровані дані як HTTP-трафік.",[3817,4624,4625,4628],{},[3775,4626,4627],{},"Payload Length (7 біт):"," вказує довжину корисного навантаження. Якщо значення від 0 до 125, це безпосередньо довжина у байтах. Якщо 126, наступні 2 байти містять 16-бітове беззнакове ціле число з довжиною. Якщо 127, наступні 8 байт містять 64-бітове беззнакове ціле число. Це дозволяє передавати повідомлення розміром від 0 байт до ~18 екзабайт.",[3817,4630,4631,4634,4635,4637],{},[3775,4632,4633],{},"Masking-key (0 або 4 байти):"," випадковий 32-бітний ключ, що використовується для маскування даних, якщо ",[3911,4636,4614],{},". Алгоритм маскування простий: кожен байт корисного навантаження XOR-иться з відповідним байтом ключа (циклічно, якщо дані довші за 4 байти). Це не є шифруванням — мета маскування полягає у запобіганні виявлення структурованих патернів у даних, які можуть бути сприйняті проміжним обладнанням як HTTP.",[3817,4639,4640,4643],{},[3775,4641,4642],{},"Payload Data:"," власне дані повідомлення — текст у UTF-8 (для opcode=1) або довільні байти (для opcode=2).",[3836,4645,4647],{"id":4646},"типи-фреймів-дані-та-керування","Типи фреймів: дані та керування",[3817,4649,4650],{},"Фрейми WebSocket діляться на дві функціональні категорії:",[3817,4652,4653,3823,4656,4658],{},[3775,4654,4655],{},"Фрейми даних",[3752,4657,4544],{},") передають корисну інформацію застосунку:",[3742,4660,4661,4667,4673],{},[3745,4662,4663,4666],{},[3775,4664,4665],{},"Text Frame (opcode=0x1):"," містить текстові дані у кодуванні UTF-8. Якщо отримувач виявляє некоректну послідовність UTF-8, він має закрити з'єднання з кодом помилки 1007 (Invalid Frame Payload Data).",[3745,4668,4669,4672],{},[3775,4670,4671],{},"Binary Frame (opcode=0x2):"," містить довільні бінарні дані — зображення, аудіо, серіалізовані об'єкти (наприклад, Protocol Buffers, MessagePack).",[3745,4674,4675,4678],{},[3775,4676,4677],{},"Continuation Frame (opcode=0x0):"," використовується для передачі фрагментів одного логічного повідомлення, якщо воно розбите на кілька фреймів.",[3817,4680,4681,3823,4684,4686],{},[3775,4682,4683],{},"Керуючі фрейми",[3752,4685,4548],{},") координують роботу з'єднання:",[3742,4688,4689,4695,4701],{},[3745,4690,4691,4694],{},[3775,4692,4693],{},"Close Frame (opcode=0x8):"," ініціює процес коректного закриття з'єднання. Може містити 2-байтний код статусу закриття (наприклад, 1000 = нормальне закриття, 1001 = клієнт йде, 1008 = порушення політики) та необов'язкове текстове повідомлення причини.",[3745,4696,4697,4700],{},[3775,4698,4699],{},"Ping Frame (opcode=0x9):"," клієнт або сервер може відправити ping-фрейм для перевірки, чи активне з'єднання. Отримувач повинен негайно відповісти pong-фреймом з тим самим корисним навантаженням.",[3745,4702,4703,4706,4707,4710,4711,3834],{},[3775,4704,4705],{},"Pong Frame (opcode=0xA):"," відповідь на ping. Може бути відправлений і несолісітовано (",[3752,4708,4709],{},"unsolicited pong",") як односторонній сигнал підтримки з'єднання (",[3752,4712,4713],{},"keep-alive",[3903,4715,4716,4717,4720],{},"Керуючі фрейми мають обмеження: їхнє корисне навантаження не може перевищувати 125 байт, і вони не можуть бути фрагментовані (",[3911,4718,4719],{},"FIN"," завжди має дорівнювати 1). Це забезпечує швидку обробку керуючих команд без буферизації фрагментів.",[3836,4722,4724],{"id":4723},"фрагментація-повідомлень","Фрагментація повідомлень",[3817,4726,4727,4728,3823,4731,4734],{},"Протокол WebSocket підтримує механізм ",[3775,4729,4730],{},"фрагментації",[3752,4732,4733],{},"fragmentation","), що дозволяє розбивати велике логічне повідомлення на кілька менших фреймів. Це корисно, коли відправник не знає заздалегідь розмір повідомлення (наприклад, потокова передача даних) або хоче перемежовувати передачу різних повідомлень без блокування (multiplexing різних потоків даних в одному з'єднанні).",[3817,4736,4737,4738,4740,4741,4743,4744,4746],{},"Перший фрейм багаточастинного повідомлення має opcode, що відповідає типу даних (0x1 для тексту або 0x2 для бінарних даних), і ",[3911,4739,4522],{},". Усі проміжні фрейми мають opcode=0x0 (continuation) і ",[3911,4742,4522],{},". Останній фрейм також має opcode=0x0, але ",[3911,4745,4518],{},", що сигналізує про завершення повідомлення.",[3817,4748,4749],{},"Приклад передачі фрагментованого текстового повідомлення «Hello, World!»:",[3937,4751,4754],{"className":4752,"code":4753,"language":4500},[4498],"Фрейм 1: FIN=0, opcode=0x1 (text), payload=\"Hello, \"\nФрейм 2: FIN=0, opcode=0x0 (continuation), payload=\"Wor\"\nФрейм 3: FIN=1, opcode=0x0 (continuation), payload=\"ld!\"\n",[3911,4755,4753],{"__ignoreMap":3942},[3817,4757,4758,4759,4761],{},"Отримувач збирає фрагменти у буфері до моменту отримання фрейма з ",[3911,4760,4518],{},", після чого об'єднує всі частини у повне повідомлення та передає його застосунку.",[4469,4763,4764,4765,4768,4769,4771],{},"Фрагментація можлива лише для фреймів даних (text і binary). Керуючі фрейми (close, ping, pong) ",[3775,4766,4767],{},"не можуть бути фрагментовані"," і мають передаватися цілком (",[3911,4770,4518],{},"). Крім того, керуючі фрейми можуть вклинюватися між фрагментами повідомлення даних — отримувач має обробляти їх негайно, не чекаючи завершення багаточастинного повідомлення.",[3812,4773,4775],{"id":4774},"життєвий-цикл-websocket-зєднання","Життєвий цикл WebSocket-з'єднання",[3817,4777,4778],{},"WebSocket-з'єднання проходить чіткі фази від встановлення до закриття. Розуміння цього життєвого циклу критично важливе для коректної обробки помилок та звільнення ресурсів.",[3836,4780,4782],{"id":4781},"фаза-1-встановлення-зєднання-opening-handshake","Фаза 1: Встановлення з'єднання (Opening Handshake)",[3817,4784,4785,4786,4789,4790,4793],{},"Як було детально розглянуто раніше, клієнт відправляє HTTP Upgrade-запит, сервер повертає відповідь зі статусом 101, і з'єднання переходить у стан ",[3911,4787,4788],{},"OPEN",". У цьому стані обидві сторони можуть вільно обмінюватися фреймами. Web API в браузері надає подію ",[3911,4791,4792],{},"onopen",", яка спрацьовує після успішного завершення рукостискання:",[3937,4795,4799],{"className":4796,"code":4797,"language":4798,"meta":3942,"style":3942},"language-javascript shiki shiki-themes light-plus dark-plus dark-plus","const socket = new WebSocket('wss:\u002F\u002Fexample.com\u002Fchat');\n\nsocket.onopen = (event) => {\n  console.log('WebSocket з\\'єднання встановлено:', event);\n  \u002F\u002F Можна розпочати відправку повідомлень\n  socket.send('Привіт, сервер!');\n};\n","javascript",[3911,4800,4801,4829,4833,4858,4887,4893,4910],{"__ignoreMap":3942},[3946,4802,4803,4806,4810,4813,4816,4820,4823,4826],{"class":3948,"line":3949},[3946,4804,4805],{"class":3959},"const",[3946,4807,4809],{"class":4808},"s-QsJ"," socket",[3946,4811,4812],{"class":3955}," = ",[3946,4814,4815],{"class":3959},"new",[3946,4817,4819],{"class":4818},"s8Opu"," WebSocket",[3946,4821,4822],{"class":3955},"(",[3946,4824,4825],{"class":3980},"'wss:\u002F\u002Fexample.com\u002Fchat'",[3946,4827,4828],{"class":3955},");\n",[3946,4830,4831],{"class":3948,"line":3970},[3946,4832,4257],{"emptyLinePlaceholder":4256},[3946,4834,4835,4839,4841,4843,4846,4849,4852,4855],{"class":3948,"line":3984},[3946,4836,4838],{"class":4837},"siwwj","socket",[3946,4840,4061],{"class":3955},[3946,4842,4792],{"class":4818},[3946,4844,4845],{"class":3955}," = (",[3946,4847,4848],{"class":4837},"event",[3946,4850,4851],{"class":3955},") ",[3946,4853,4854],{"class":3959},"=>",[3946,4856,4857],{"class":3955}," {\n",[3946,4859,4860,4863,4865,4868,4870,4873,4877,4880,4883,4885],{"class":3948,"line":3995},[3946,4861,4862],{"class":4837},"  console",[3946,4864,4061],{"class":3955},[3946,4866,4867],{"class":4818},"log",[3946,4869,4822],{"class":3955},[3946,4871,4872],{"class":3980},"'WebSocket з",[3946,4874,4876],{"class":4875},"sjcCO","\\'",[3946,4878,4879],{"class":3980},"єднання встановлено:'",[3946,4881,4882],{"class":3955},", ",[3946,4884,4848],{"class":4837},[3946,4886,4828],{"class":3955},[3946,4888,4889],{"class":3948,"line":4006},[3946,4890,4892],{"class":4891},"spJ8K","  \u002F\u002F Можна розпочати відправку повідомлень\n",[3946,4894,4895,4898,4900,4903,4905,4908],{"class":3948,"line":4017},[3946,4896,4897],{"class":4837},"  socket",[3946,4899,4061],{"class":3955},[3946,4901,4902],{"class":4818},"send",[3946,4904,4822],{"class":3955},[3946,4906,4907],{"class":3980},"'Привіт, сервер!'",[3946,4909,4828],{"class":3955},[3946,4911,4912],{"class":3948,"line":4028},[3946,4913,4914],{"class":3955},"};\n",[3817,4916,4917,4918,4921,4922,4925],{},"На серверній стороні (наприклад, у Node.js з бібліотекою ",[3911,4919,4920],{},"ws",") подія ",[3911,4923,4924],{},"connection"," сигналізує про нове з'єднання:",[3937,4927,4929],{"className":4796,"code":4928,"language":4798,"meta":3942,"style":3942},"const WebSocket = require('ws');\nconst wss = new WebSocket.Server({ port: 8080 });\n\nwss.on('connection', (ws, req) => {\n  console.log('Новий клієнт підключився з адреси:', req.socket.remoteAddress);\n  ws.send('Вітаємо на сервері!');\n});\n",[3911,4930,4931,4949,4979,4983,5014,5042,5058],{"__ignoreMap":3942},[3946,4932,4933,4935,4937,4939,4942,4944,4947],{"class":3948,"line":3949},[3946,4934,4805],{"class":3959},[3946,4936,4819],{"class":4808},[3946,4938,4812],{"class":3955},[3946,4940,4941],{"class":4818},"require",[3946,4943,4822],{"class":3955},[3946,4945,4946],{"class":3980},"'ws'",[3946,4948,4828],{"class":3955},[3946,4950,4951,4953,4956,4958,4960,4962,4964,4967,4970,4973,4976],{"class":3948,"line":3970},[3946,4952,4805],{"class":3959},[3946,4954,4955],{"class":4808}," wss",[3946,4957,4812],{"class":3955},[3946,4959,4815],{"class":3959},[3946,4961,4819],{"class":4837},[3946,4963,4061],{"class":3955},[3946,4965,4966],{"class":4818},"Server",[3946,4968,4969],{"class":3955},"({ ",[3946,4971,4972],{"class":4837},"port:",[3946,4974,4975],{"class":3966}," 8080",[3946,4977,4978],{"class":3955}," });\n",[3946,4980,4981],{"class":3948,"line":3984},[3946,4982,4257],{"emptyLinePlaceholder":4256},[3946,4984,4985,4988,4990,4993,4995,4998,5001,5003,5005,5008,5010,5012],{"class":3948,"line":3995},[3946,4986,4987],{"class":4837},"wss",[3946,4989,4061],{"class":3955},[3946,4991,4992],{"class":4818},"on",[3946,4994,4822],{"class":3955},[3946,4996,4997],{"class":3980},"'connection'",[3946,4999,5000],{"class":3955},", (",[3946,5002,4920],{"class":4837},[3946,5004,4882],{"class":3955},[3946,5006,5007],{"class":4837},"req",[3946,5009,4851],{"class":3955},[3946,5011,4854],{"class":3959},[3946,5013,4857],{"class":3955},[3946,5015,5016,5018,5020,5022,5024,5027,5029,5031,5033,5035,5037,5040],{"class":3948,"line":4006},[3946,5017,4862],{"class":4837},[3946,5019,4061],{"class":3955},[3946,5021,4867],{"class":4818},[3946,5023,4822],{"class":3955},[3946,5025,5026],{"class":3980},"'Новий клієнт підключився з адреси:'",[3946,5028,4882],{"class":3955},[3946,5030,5007],{"class":4837},[3946,5032,4061],{"class":3955},[3946,5034,4838],{"class":4837},[3946,5036,4061],{"class":3955},[3946,5038,5039],{"class":4837},"remoteAddress",[3946,5041,4828],{"class":3955},[3946,5043,5044,5047,5049,5051,5053,5056],{"class":3948,"line":4017},[3946,5045,5046],{"class":4837},"  ws",[3946,5048,4061],{"class":3955},[3946,5050,4902],{"class":4818},[3946,5052,4822],{"class":3955},[3946,5054,5055],{"class":3980},"'Вітаємо на сервері!'",[3946,5057,4828],{"class":3955},[3946,5059,5060],{"class":3948,"line":4028},[3946,5061,5062],{"class":3955},"});\n",[3836,5064,5066],{"id":5065},"фаза-2-обмін-повідомленнями-data-transfer","Фаза 2: Обмін повідомленнями (Data Transfer)",[3817,5068,5069,5070,5072,5073,5076],{},"У стані ",[3911,5071,4788],{}," клієнт і сервер можуть відправляти необмежену кількість повідомлень у будь-якому напрямку. Кожен виклик методу ",[3911,5074,5075],{},"send()"," генерує один або кілька фреймів залежно від розміру та типу даних:",[3937,5078,5080],{"className":4796,"code":5079,"language":4798,"meta":3942,"style":3942},"\u002F\u002F Відправка текстового повідомлення (Text Frame)\nsocket.send('Це текстове повідомлення у UTF-8');\n\n\u002F\u002F Відправка бінарних даних (Binary Frame)\nconst buffer = new Uint8Array([0x48, 0x65, 0x6C, 0x6C, 0x6F]); \u002F\u002F \"Hello\"\nsocket.send(buffer);\n\n\u002F\u002F Відправка Blob (у браузері, автоматично перетворюється на Binary Frame)\nconst blob = new Blob(['Дані файлу'], { type: 'text\u002Fplain' });\nsocket.send(blob);\n",[3911,5081,5082,5087,5102,5106,5111,5156,5171,5175,5180,5210],{"__ignoreMap":3942},[3946,5083,5084],{"class":3948,"line":3949},[3946,5085,5086],{"class":4891},"\u002F\u002F Відправка текстового повідомлення (Text Frame)\n",[3946,5088,5089,5091,5093,5095,5097,5100],{"class":3948,"line":3970},[3946,5090,4838],{"class":4837},[3946,5092,4061],{"class":3955},[3946,5094,4902],{"class":4818},[3946,5096,4822],{"class":3955},[3946,5098,5099],{"class":3980},"'Це текстове повідомлення у UTF-8'",[3946,5101,4828],{"class":3955},[3946,5103,5104],{"class":3948,"line":3984},[3946,5105,4257],{"emptyLinePlaceholder":4256},[3946,5107,5108],{"class":3948,"line":3995},[3946,5109,5110],{"class":4891},"\u002F\u002F Відправка бінарних даних (Binary Frame)\n",[3946,5112,5113,5115,5118,5120,5122,5125,5128,5131,5133,5136,5138,5141,5143,5145,5147,5150,5153],{"class":3948,"line":4006},[3946,5114,4805],{"class":3959},[3946,5116,5117],{"class":4808}," buffer",[3946,5119,4812],{"class":3955},[3946,5121,4815],{"class":3959},[3946,5123,5124],{"class":4818}," Uint8Array",[3946,5126,5127],{"class":3955},"([",[3946,5129,5130],{"class":3966},"0x48",[3946,5132,4882],{"class":3955},[3946,5134,5135],{"class":3966},"0x65",[3946,5137,4882],{"class":3955},[3946,5139,5140],{"class":3966},"0x6C",[3946,5142,4882],{"class":3955},[3946,5144,5140],{"class":3966},[3946,5146,4882],{"class":3955},[3946,5148,5149],{"class":3966},"0x6F",[3946,5151,5152],{"class":3955},"]); ",[3946,5154,5155],{"class":4891},"\u002F\u002F \"Hello\"\n",[3946,5157,5158,5160,5162,5164,5166,5169],{"class":3948,"line":4017},[3946,5159,4838],{"class":4837},[3946,5161,4061],{"class":3955},[3946,5163,4902],{"class":4818},[3946,5165,4822],{"class":3955},[3946,5167,5168],{"class":4837},"buffer",[3946,5170,4828],{"class":3955},[3946,5172,5173],{"class":3948,"line":4028},[3946,5174,4257],{"emptyLinePlaceholder":4256},[3946,5176,5177],{"class":3948,"line":4265},[3946,5178,5179],{"class":4891},"\u002F\u002F Відправка Blob (у браузері, автоматично перетворюється на Binary Frame)\n",[3946,5181,5182,5184,5187,5189,5191,5194,5196,5199,5202,5205,5208],{"class":3948,"line":4271},[3946,5183,4805],{"class":3959},[3946,5185,5186],{"class":4808}," blob",[3946,5188,4812],{"class":3955},[3946,5190,4815],{"class":3959},[3946,5192,5193],{"class":4818}," Blob",[3946,5195,5127],{"class":3955},[3946,5197,5198],{"class":3980},"'Дані файлу'",[3946,5200,5201],{"class":3955},"], { ",[3946,5203,5204],{"class":4837},"type:",[3946,5206,5207],{"class":3980}," 'text\u002Fplain'",[3946,5209,4978],{"class":3955},[3946,5211,5212,5214,5216,5218,5220,5223],{"class":3948,"line":4277},[3946,5213,4838],{"class":4837},[3946,5215,4061],{"class":3955},[3946,5217,4902],{"class":4818},[3946,5219,4822],{"class":3955},[3946,5221,5222],{"class":4837},"blob",[3946,5224,4828],{"class":3955},[3817,5226,5227,5228,3977],{},"Отримувач обробляє вхідні повідомлення через подію ",[3911,5229,5230],{},"onmessage",[3937,5232,5234],{"className":4796,"code":5233,"language":4798,"meta":3942,"style":3942},"socket.onmessage = (event) => {\n  if (typeof event.data === 'string') {\n    console.log('Отримано текст:', event.data);\n  } else if (event.data instanceof Blob) {\n    console.log('Отримано Blob розміром:', event.data.size);\n  } else if (event.data instanceof ArrayBuffer) {\n    console.log('Отримано бінарні дані:', new Uint8Array(event.data));\n  }\n};\n",[3911,5235,5236,5254,5281,5305,5332,5360,5383,5413,5418],{"__ignoreMap":3942},[3946,5237,5238,5240,5242,5244,5246,5248,5250,5252],{"class":3948,"line":3949},[3946,5239,4838],{"class":4837},[3946,5241,4061],{"class":3955},[3946,5243,5230],{"class":4818},[3946,5245,4845],{"class":3955},[3946,5247,4848],{"class":4837},[3946,5249,4851],{"class":3955},[3946,5251,4854],{"class":3959},[3946,5253,4857],{"class":3955},[3946,5255,5256,5259,5261,5264,5267,5269,5272,5275,5278],{"class":3948,"line":3970},[3946,5257,5258],{"class":3952},"  if",[3946,5260,3823],{"class":3955},[3946,5262,5263],{"class":3959},"typeof",[3946,5265,5266],{"class":4837}," event",[3946,5268,4061],{"class":3955},[3946,5270,5271],{"class":4837},"data",[3946,5273,5274],{"class":3955}," === ",[3946,5276,5277],{"class":3980},"'string'",[3946,5279,5280],{"class":3955},") {\n",[3946,5282,5283,5286,5288,5290,5292,5295,5297,5299,5301,5303],{"class":3948,"line":3984},[3946,5284,5285],{"class":4837},"    console",[3946,5287,4061],{"class":3955},[3946,5289,4867],{"class":4818},[3946,5291,4822],{"class":3955},[3946,5293,5294],{"class":3980},"'Отримано текст:'",[3946,5296,4882],{"class":3955},[3946,5298,4848],{"class":4837},[3946,5300,4061],{"class":3955},[3946,5302,5271],{"class":4837},[3946,5304,4828],{"class":3955},[3946,5306,5307,5310,5313,5316,5318,5320,5322,5324,5327,5330],{"class":3948,"line":3995},[3946,5308,5309],{"class":3955},"  } ",[3946,5311,5312],{"class":3952},"else",[3946,5314,5315],{"class":3952}," if",[3946,5317,3823],{"class":3955},[3946,5319,4848],{"class":4837},[3946,5321,4061],{"class":3955},[3946,5323,5271],{"class":4837},[3946,5325,5326],{"class":3959}," instanceof",[3946,5328,5193],{"class":5329},"sN1BT",[3946,5331,5280],{"class":3955},[3946,5333,5334,5336,5338,5340,5342,5345,5347,5349,5351,5353,5355,5358],{"class":3948,"line":4006},[3946,5335,5285],{"class":4837},[3946,5337,4061],{"class":3955},[3946,5339,4867],{"class":4818},[3946,5341,4822],{"class":3955},[3946,5343,5344],{"class":3980},"'Отримано Blob розміром:'",[3946,5346,4882],{"class":3955},[3946,5348,4848],{"class":4837},[3946,5350,4061],{"class":3955},[3946,5352,5271],{"class":4837},[3946,5354,4061],{"class":3955},[3946,5356,5357],{"class":4837},"size",[3946,5359,4828],{"class":3955},[3946,5361,5362,5364,5366,5368,5370,5372,5374,5376,5378,5381],{"class":3948,"line":4017},[3946,5363,5309],{"class":3955},[3946,5365,5312],{"class":3952},[3946,5367,5315],{"class":3952},[3946,5369,3823],{"class":3955},[3946,5371,4848],{"class":4837},[3946,5373,4061],{"class":3955},[3946,5375,5271],{"class":4837},[3946,5377,5326],{"class":3959},[3946,5379,5380],{"class":5329}," ArrayBuffer",[3946,5382,5280],{"class":3955},[3946,5384,5385,5387,5389,5391,5393,5396,5398,5400,5402,5404,5406,5408,5410],{"class":3948,"line":4028},[3946,5386,5285],{"class":4837},[3946,5388,4061],{"class":3955},[3946,5390,4867],{"class":4818},[3946,5392,4822],{"class":3955},[3946,5394,5395],{"class":3980},"'Отримано бінарні дані:'",[3946,5397,4882],{"class":3955},[3946,5399,4815],{"class":3959},[3946,5401,5124],{"class":4818},[3946,5403,4822],{"class":3955},[3946,5405,4848],{"class":4837},[3946,5407,4061],{"class":3955},[3946,5409,5271],{"class":4837},[3946,5411,5412],{"class":3955},"));\n",[3946,5414,5415],{"class":3948,"line":4265},[3946,5416,5417],{"class":3955},"  }\n",[3946,5419,5420],{"class":3948,"line":4271},[3946,5421,4914],{"class":3955},[5423,5424,5425],"tip",{},"Для високочастотного обміну даними (наприклад, у грі з частотою 60 FPS) рекомендується використовувати бінарні повідомлення замість JSON. Бінарні формати (Protocol Buffers, MessagePack, FlatBuffers) займають менше місця та обробляються швидше, ніж парсинг JSON.",[3836,5427,5429],{"id":5428},"фаза-3-механізм-keep-alive-pingpong","Фаза 3: Механізм keep-alive (Ping\u002FPong)",[3817,5431,5432,5433,3823,5436,5439],{},"WebSocket-з'єднання може простоювати тривалий час без обміну даними. Проміжні мережеві вузли (NAT-шлюзи, балансувальники, проксі) можуть автоматично закривати неактивні TCP-з'єднання через таймаут (зазвичай 30–120 секунд). Для запобігання цьому використовується механізм ",[3775,5434,5435],{},"heartbeat",[3752,5437,5438],{},"пульс серця",") на основі ping\u002Fpong фреймів.",[3817,5441,5442],{},"Сервер або клієнт періодично (наприклад, кожні 30 секунд) відправляє ping-фрейм:",[3937,5444,5446],{"className":4796,"code":5445,"language":4798,"meta":3942,"style":3942},"\u002F\u002F Серверна сторона (Node.js + ws)\nconst interval = setInterval(() => {\n  wss.clients.forEach((ws) => {\n    if (ws.isAlive === false) {\n      \u002F\u002F Клієнт не відповів на попередній ping — закриваємо з'єднання\n      return ws.terminate();\n    }\n    ws.isAlive = false;\n    ws.ping(); \u002F\u002F Відправка ping-фрейма\n  });\n}, 30000);\n\nwss.on('connection', (ws) => {\n  ws.isAlive = true;\n  ws.on('pong', () => {\n    ws.isAlive = true; \u002F\u002F Клієнт відповів — з'єднання активне\n  });\n});\n",[3911,5447,5448,5453,5472,5498,5519,5524,5540,5545,5561,5576,5581,5591,5595,5617,5632,5652,5670,5674],{"__ignoreMap":3942},[3946,5449,5450],{"class":3948,"line":3949},[3946,5451,5452],{"class":4891},"\u002F\u002F Серверна сторона (Node.js + ws)\n",[3946,5454,5455,5457,5460,5462,5465,5468,5470],{"class":3948,"line":3970},[3946,5456,4805],{"class":3959},[3946,5458,5459],{"class":4808}," interval",[3946,5461,4812],{"class":3955},[3946,5463,5464],{"class":4818},"setInterval",[3946,5466,5467],{"class":3955},"(() ",[3946,5469,4854],{"class":3959},[3946,5471,4857],{"class":3955},[3946,5473,5474,5477,5479,5482,5484,5487,5490,5492,5494,5496],{"class":3948,"line":3984},[3946,5475,5476],{"class":4837},"  wss",[3946,5478,4061],{"class":3955},[3946,5480,5481],{"class":4837},"clients",[3946,5483,4061],{"class":3955},[3946,5485,5486],{"class":4818},"forEach",[3946,5488,5489],{"class":3955},"((",[3946,5491,4920],{"class":4837},[3946,5493,4851],{"class":3955},[3946,5495,4854],{"class":3959},[3946,5497,4857],{"class":3955},[3946,5499,5500,5503,5505,5507,5509,5512,5514,5517],{"class":3948,"line":3995},[3946,5501,5502],{"class":3952},"    if",[3946,5504,3823],{"class":3955},[3946,5506,4920],{"class":4837},[3946,5508,4061],{"class":3955},[3946,5510,5511],{"class":4837},"isAlive",[3946,5513,5274],{"class":3955},[3946,5515,5516],{"class":3959},"false",[3946,5518,5280],{"class":3955},[3946,5520,5521],{"class":3948,"line":4006},[3946,5522,5523],{"class":4891},"      \u002F\u002F Клієнт не відповів на попередній ping — закриваємо з'єднання\n",[3946,5525,5526,5529,5532,5534,5537],{"class":3948,"line":4017},[3946,5527,5528],{"class":3952},"      return",[3946,5530,5531],{"class":4837}," ws",[3946,5533,4061],{"class":3955},[3946,5535,5536],{"class":4818},"terminate",[3946,5538,5539],{"class":3955},"();\n",[3946,5541,5542],{"class":3948,"line":4028},[3946,5543,5544],{"class":3955},"    }\n",[3946,5546,5547,5550,5552,5554,5556,5558],{"class":3948,"line":4265},[3946,5548,5549],{"class":4837},"    ws",[3946,5551,4061],{"class":3955},[3946,5553,5511],{"class":4837},[3946,5555,4812],{"class":3955},[3946,5557,5516],{"class":3959},[3946,5559,5560],{"class":3955},";\n",[3946,5562,5563,5565,5567,5570,5573],{"class":3948,"line":4271},[3946,5564,5549],{"class":4837},[3946,5566,4061],{"class":3955},[3946,5568,5569],{"class":4818},"ping",[3946,5571,5572],{"class":3955},"(); ",[3946,5574,5575],{"class":4891},"\u002F\u002F Відправка ping-фрейма\n",[3946,5577,5578],{"class":3948,"line":4277},[3946,5579,5580],{"class":3955},"  });\n",[3946,5582,5583,5586,5589],{"class":3948,"line":4282},[3946,5584,5585],{"class":3955},"}, ",[3946,5587,5588],{"class":3966},"30000",[3946,5590,4828],{"class":3955},[3946,5592,5593],{"class":3948,"line":4288},[3946,5594,4257],{"emptyLinePlaceholder":4256},[3946,5596,5597,5599,5601,5603,5605,5607,5609,5611,5613,5615],{"class":3948,"line":4294},[3946,5598,4987],{"class":4837},[3946,5600,4061],{"class":3955},[3946,5602,4992],{"class":4818},[3946,5604,4822],{"class":3955},[3946,5606,4997],{"class":3980},[3946,5608,5000],{"class":3955},[3946,5610,4920],{"class":4837},[3946,5612,4851],{"class":3955},[3946,5614,4854],{"class":3959},[3946,5616,4857],{"class":3955},[3946,5618,5619,5621,5623,5625,5627,5630],{"class":3948,"line":4300},[3946,5620,5046],{"class":4837},[3946,5622,4061],{"class":3955},[3946,5624,5511],{"class":4837},[3946,5626,4812],{"class":3955},[3946,5628,5629],{"class":3959},"true",[3946,5631,5560],{"class":3955},[3946,5633,5634,5636,5638,5640,5642,5645,5648,5650],{"class":3948,"line":4306},[3946,5635,5046],{"class":4837},[3946,5637,4061],{"class":3955},[3946,5639,4992],{"class":4818},[3946,5641,4822],{"class":3955},[3946,5643,5644],{"class":3980},"'pong'",[3946,5646,5647],{"class":3955},", () ",[3946,5649,4854],{"class":3959},[3946,5651,4857],{"class":3955},[3946,5653,5654,5656,5658,5660,5662,5664,5667],{"class":3948,"line":4312},[3946,5655,5549],{"class":4837},[3946,5657,4061],{"class":3955},[3946,5659,5511],{"class":4837},[3946,5661,4812],{"class":3955},[3946,5663,5629],{"class":3959},[3946,5665,5666],{"class":3955},"; ",[3946,5668,5669],{"class":4891},"\u002F\u002F Клієнт відповів — з'єднання активне\n",[3946,5671,5672],{"class":3948,"line":4317},[3946,5673,5580],{"class":3955},[3946,5675,5676],{"class":3948,"line":4323},[3946,5677,5062],{"class":3955},[3817,5679,5680],{},"У браузерному API немає прямого доступу до відправки ping\u002Fpong фреймів (це реалізовано автоматично браузером), але на серверній стороні цей механізм має бути реалізований явно для виявлення «мертвих» з'єднань.",[3903,5682,5683,5684,5687,5688,5691],{},"Альтернативою ping\u002Fpong є відправка звичайних повідомлень застосунку з типом «heartbeat». Наприклад, ",[3911,5685,5686],{},"{\"type\": \"ping\"}"," кожні 30 секунд, на які клієнт відповідає ",[3911,5689,5690],{},"{\"type\": \"pong\"}",". Цей підхід дає більше контролю, але вимагає обробки на рівні логіки застосунку.",[3836,5693,5695],{"id":5694},"фаза-4-коректне-закриття-зєднання-closing-handshake","Фаза 4: Коректне закриття з'єднання (Closing Handshake)",[3817,5697,5698,5699,3823,5702,5705],{},"На відміну від раптового обриву TCP-з'єднання, протокол WebSocket визначає процедуру ",[3775,5700,5701],{},"коректного закриття",[3752,5703,5704],{},"graceful shutdown","), що дозволяє обом сторонам домовитися про завершення роботи та обробити всі буферизовані дані.",[3817,5707,5708,5709,5712],{},"Ініціатор закриття (клієнт або сервер) відправляє ",[3775,5710,5711],{},"Close Frame"," з опціональним 2-байтним кодом статусу та текстовою причиною (до 123 байт UTF-8):",[3937,5714,5716],{"className":4796,"code":5715,"language":4798,"meta":3942,"style":3942},"\u002F\u002F Клієнт ініціює закриття\nsocket.close(1000, 'Користувач вийшов з чату');\n",[3911,5717,5718,5723],{"__ignoreMap":3942},[3946,5719,5720],{"class":3948,"line":3949},[3946,5721,5722],{"class":4891},"\u002F\u002F Клієнт ініціює закриття\n",[3946,5724,5725,5727,5729,5731,5733,5736,5738,5741],{"class":3948,"line":3970},[3946,5726,4838],{"class":4837},[3946,5728,4061],{"class":3955},[3946,5730,4589],{"class":4818},[3946,5732,4822],{"class":3955},[3946,5734,5735],{"class":3966},"1000",[3946,5737,4882],{"class":3955},[3946,5739,5740],{"class":3980},"'Користувач вийшов з чату'",[3946,5742,4828],{"class":3955},[3817,5744,5745],{},"Коди статусу закриття стандартизовані RFC 6455:",[3742,5747,5748,5753,5759,5765,5771,5777,5783,5789,5795],{},[3745,5749,5750,5752],{},[3775,5751,5735],{}," — Normal Closure (нормальне закриття, задача виконана).",[3745,5754,5755,5758],{},[3775,5756,5757],{},"1001"," — Going Away (клієнт покидає сторінку або сервер вимикається).",[3745,5760,5761,5764],{},[3775,5762,5763],{},"1002"," — Protocol Error (помилка протоколу, некоректний фрейм).",[3745,5766,5767,5770],{},[3775,5768,5769],{},"1003"," — Unsupported Data (отримано дані неприйнятного типу).",[3745,5772,5773,5776],{},[3775,5774,5775],{},"1006"," — Abnormal Closure (аварійне закриття без Close Frame, лише для клієнтського API).",[3745,5778,5779,5782],{},[3775,5780,5781],{},"1007"," — Invalid Frame Payload Data (некоректне кодування UTF-8 у текстовому фреймі).",[3745,5784,5785,5788],{},[3775,5786,5787],{},"1008"," — Policy Violation (порушення політики застосунку).",[3745,5790,5791,5794],{},[3775,5792,5793],{},"1009"," — Message Too Big (повідомлення занадто велике для обробки).",[3745,5796,5797,5800],{},[3775,5798,5799],{},"1011"," — Internal Server Error (несподівана помилка на сервері).",[3817,5802,5803],{},"Коли одна сторона відправляє Close Frame, отримувач має:",[4169,5805,5806,5809,5812],{},[3745,5807,5808],{},"Припинити відправку нових повідомлень даних.",[3745,5810,5811],{},"Відповісти власним Close Frame (зазвичай з тим самим кодом статусу).",[3745,5813,5814],{},"Закрити базове TCP-з'єднання.",[3817,5816,5817,5818,3977],{},"Після цього обидві сторони вважають з'єднання закритим. У Web API спрацьовує подія ",[3911,5819,5820],{},"onclose",[3937,5822,5824],{"className":4796,"code":5823,"language":4798,"meta":3942,"style":3942},"socket.onclose = (event) => {\n  console.log('З\\'єднання закрито:', {\n    code: event.code,    \u002F\u002F Код статусу (наприклад, 1000)\n    reason: event.reason, \u002F\u002F Текстова причина\n    wasClean: event.wasClean \u002F\u002F true, якщо Close Frame отримано\n  });\n  \n  if (!event.wasClean) {\n    console.error('Аварійне закриття з\\'єднання');\n  }\n};\n",[3911,5825,5826,5844,5865,5882,5899,5914,5918,5923,5938,5959,5963],{"__ignoreMap":3942},[3946,5827,5828,5830,5832,5834,5836,5838,5840,5842],{"class":3948,"line":3949},[3946,5829,4838],{"class":4837},[3946,5831,4061],{"class":3955},[3946,5833,5820],{"class":4818},[3946,5835,4845],{"class":3955},[3946,5837,4848],{"class":4837},[3946,5839,4851],{"class":3955},[3946,5841,4854],{"class":3959},[3946,5843,4857],{"class":3955},[3946,5845,5846,5848,5850,5852,5854,5857,5859,5862],{"class":3948,"line":3970},[3946,5847,4862],{"class":4837},[3946,5849,4061],{"class":3955},[3946,5851,4867],{"class":4818},[3946,5853,4822],{"class":3955},[3946,5855,5856],{"class":3980},"'З",[3946,5858,4876],{"class":4875},[3946,5860,5861],{"class":3980},"єднання закрито:'",[3946,5863,5864],{"class":3955},", {\n",[3946,5866,5867,5870,5872,5874,5876,5879],{"class":3948,"line":3984},[3946,5868,5869],{"class":4837},"    code:",[3946,5871,5266],{"class":4837},[3946,5873,4061],{"class":3955},[3946,5875,3911],{"class":4837},[3946,5877,5878],{"class":3955},",    ",[3946,5880,5881],{"class":4891},"\u002F\u002F Код статусу (наприклад, 1000)\n",[3946,5883,5884,5887,5889,5891,5894,5896],{"class":3948,"line":3995},[3946,5885,5886],{"class":4837},"    reason:",[3946,5888,5266],{"class":4837},[3946,5890,4061],{"class":3955},[3946,5892,5893],{"class":4837},"reason",[3946,5895,4882],{"class":3955},[3946,5897,5898],{"class":4891},"\u002F\u002F Текстова причина\n",[3946,5900,5901,5904,5906,5908,5911],{"class":3948,"line":4006},[3946,5902,5903],{"class":4837},"    wasClean:",[3946,5905,5266],{"class":4837},[3946,5907,4061],{"class":3955},[3946,5909,5910],{"class":4837},"wasClean",[3946,5912,5913],{"class":4891}," \u002F\u002F true, якщо Close Frame отримано\n",[3946,5915,5916],{"class":3948,"line":4017},[3946,5917,5580],{"class":3955},[3946,5919,5920],{"class":3948,"line":4028},[3946,5921,5922],{"class":3955},"  \n",[3946,5924,5925,5927,5930,5932,5934,5936],{"class":3948,"line":4265},[3946,5926,5258],{"class":3952},[3946,5928,5929],{"class":3955}," (!",[3946,5931,4848],{"class":4837},[3946,5933,4061],{"class":3955},[3946,5935,5910],{"class":4837},[3946,5937,5280],{"class":3955},[3946,5939,5940,5942,5944,5947,5949,5952,5954,5957],{"class":3948,"line":4271},[3946,5941,5285],{"class":4837},[3946,5943,4061],{"class":3955},[3946,5945,5946],{"class":4818},"error",[3946,5948,4822],{"class":3955},[3946,5950,5951],{"class":3980},"'Аварійне закриття з",[3946,5953,4876],{"class":4875},[3946,5955,5956],{"class":3980},"єднання'",[3946,5958,4828],{"class":3955},[3946,5960,5961],{"class":3948,"line":4277},[3946,5962,5417],{"class":3955},[3946,5964,5965],{"class":3948,"line":4282},[3946,5966,4914],{"class":3955},[4469,5968,5969,5970,5973],{},"Якщо сторона не отримує Close Frame у відповідь протягом розумного таймауту (зазвичай 2–5 секунд), вона має примусово закрити TCP-з'єднання методом ",[3911,5971,5972],{},"terminate()"," для звільнення ресурсів. Без цього можливе накопичення «підвислих» з'єднань на сервері (connection leak).",[3836,5975,5977],{"id":5976},"обробка-помилок","Обробка помилок",[3817,5979,5980,5981,3977],{},"У разі критичних помилок (некоректний фрейм, порушення протоколу, помилка мережі) спрацьовує подія ",[3911,5982,5983],{},"onerror",[3937,5985,5987],{"className":4796,"code":5986,"language":4798,"meta":3942,"style":3942},"socket.onerror = (error) => {\n  console.error('Помилка WebSocket:', error);\n  \u002F\u002F Подія 'onerror' завжди супроводжується подією 'onclose'\n};\n",[3911,5988,5989,6007,6026,6031],{"__ignoreMap":3942},[3946,5990,5991,5993,5995,5997,5999,6001,6003,6005],{"class":3948,"line":3949},[3946,5992,4838],{"class":4837},[3946,5994,4061],{"class":3955},[3946,5996,5983],{"class":4818},[3946,5998,4845],{"class":3955},[3946,6000,5946],{"class":4837},[3946,6002,4851],{"class":3955},[3946,6004,4854],{"class":3959},[3946,6006,4857],{"class":3955},[3946,6008,6009,6011,6013,6015,6017,6020,6022,6024],{"class":3948,"line":3970},[3946,6010,4862],{"class":4837},[3946,6012,4061],{"class":3955},[3946,6014,5946],{"class":4818},[3946,6016,4822],{"class":3955},[3946,6018,6019],{"class":3980},"'Помилка WebSocket:'",[3946,6021,4882],{"class":3955},[3946,6023,5946],{"class":4837},[3946,6025,4828],{"class":3955},[3946,6027,6028],{"class":3948,"line":3984},[3946,6029,6030],{"class":4891},"  \u002F\u002F Подія 'onerror' завжди супроводжується подією 'onclose'\n",[3946,6032,6033],{"class":3948,"line":3995},[3946,6034,4914],{"class":3955},[3817,6036,6037],{},"На серверній стороні:",[3937,6039,6041],{"className":4796,"code":6040,"language":4798,"meta":3942,"style":3942},"ws.on('error', (error) => {\n  console.error('Помилка з\\'єднання з клієнтом:', error.message);\n  \u002F\u002F Логування та моніторинг\n});\n",[3911,6042,6043,6066,6095,6100],{"__ignoreMap":3942},[3946,6044,6045,6047,6049,6051,6053,6056,6058,6060,6062,6064],{"class":3948,"line":3949},[3946,6046,4920],{"class":4837},[3946,6048,4061],{"class":3955},[3946,6050,4992],{"class":4818},[3946,6052,4822],{"class":3955},[3946,6054,6055],{"class":3980},"'error'",[3946,6057,5000],{"class":3955},[3946,6059,5946],{"class":4837},[3946,6061,4851],{"class":3955},[3946,6063,4854],{"class":3959},[3946,6065,4857],{"class":3955},[3946,6067,6068,6070,6072,6074,6076,6079,6081,6084,6086,6088,6090,6093],{"class":3948,"line":3970},[3946,6069,4862],{"class":4837},[3946,6071,4061],{"class":3955},[3946,6073,5946],{"class":4818},[3946,6075,4822],{"class":3955},[3946,6077,6078],{"class":3980},"'Помилка з",[3946,6080,4876],{"class":4875},[3946,6082,6083],{"class":3980},"єднання з клієнтом:'",[3946,6085,4882],{"class":3955},[3946,6087,5946],{"class":4837},[3946,6089,4061],{"class":3955},[3946,6091,6092],{"class":4837},"message",[3946,6094,4828],{"class":3955},[3946,6096,6097],{"class":3948,"line":3984},[3946,6098,6099],{"class":4891},"  \u002F\u002F Логування та моніторинг\n",[3946,6101,6102],{"class":3948,"line":3995},[3946,6103,5062],{"class":3955},[3817,6105,6106],{},"Типові помилки:",[3742,6108,6109,6115,6123,6129],{},[3745,6110,6111,6114],{},[3775,6112,6113],{},"ECONNREFUSED"," — сервер не відповідає на запит рукостискання.",[3745,6116,6117,6120,6121,4061],{},[3775,6118,6119],{},"HTTP 403 Forbidden"," — сервер відхилив з'єднання через невідповідність ",[3911,6122,4031],{},[3745,6124,6125,6128],{},[3775,6126,6127],{},"HTTP 426 Upgrade Required"," — сервер не підтримує оновлення до WebSocket.",[3745,6130,6131,6134],{},[3775,6132,6133],{},"Protocol Error"," — отримано фрейм з некоректною структурою.",[3812,6136,6138],{"id":6137},"порівняння-websocket-із-альтернативними-технологіями","Порівняння WebSocket із альтернативними технологіями",[3817,6140,6141],{},"Для розуміння ніші WebSocket у екосистемі вебтехнологій корисно порівняти його з іншими підходами до комунікації у реальному часі.",[6143,6144,6145,6257,6382],"code-group",{},[3937,6146,6149],{"className":4796,"code":6147,"filename":6148,"language":4798,"meta":3942,"style":3942},"const socket = new WebSocket('wss:\u002F\u002Fexample.com\u002Fstream');\n\n\u002F\u002F Клієнт може відправляти будь-коли\nsocket.send('Повідомлення від клієнта');\n\n\u002F\u002F Сервер може відправляти будь-коли\nsocket.onmessage = (event) => {\n  console.log('Повідомлення від сервера:', event.data);\n};\n\n\u002F\u002F Обидві сторони рівноправні\n","WebSocket (повнодуплексний)",[3911,6150,6151,6170,6174,6179,6194,6198,6203,6221,6244,6248,6252],{"__ignoreMap":3942},[3946,6152,6153,6155,6157,6159,6161,6163,6165,6168],{"class":3948,"line":3949},[3946,6154,4805],{"class":3959},[3946,6156,4809],{"class":4808},[3946,6158,4812],{"class":3955},[3946,6160,4815],{"class":3959},[3946,6162,4819],{"class":4818},[3946,6164,4822],{"class":3955},[3946,6166,6167],{"class":3980},"'wss:\u002F\u002Fexample.com\u002Fstream'",[3946,6169,4828],{"class":3955},[3946,6171,6172],{"class":3948,"line":3970},[3946,6173,4257],{"emptyLinePlaceholder":4256},[3946,6175,6176],{"class":3948,"line":3984},[3946,6177,6178],{"class":4891},"\u002F\u002F Клієнт може відправляти будь-коли\n",[3946,6180,6181,6183,6185,6187,6189,6192],{"class":3948,"line":3995},[3946,6182,4838],{"class":4837},[3946,6184,4061],{"class":3955},[3946,6186,4902],{"class":4818},[3946,6188,4822],{"class":3955},[3946,6190,6191],{"class":3980},"'Повідомлення від клієнта'",[3946,6193,4828],{"class":3955},[3946,6195,6196],{"class":3948,"line":4006},[3946,6197,4257],{"emptyLinePlaceholder":4256},[3946,6199,6200],{"class":3948,"line":4017},[3946,6201,6202],{"class":4891},"\u002F\u002F Сервер може відправляти будь-коли\n",[3946,6204,6205,6207,6209,6211,6213,6215,6217,6219],{"class":3948,"line":4028},[3946,6206,4838],{"class":4837},[3946,6208,4061],{"class":3955},[3946,6210,5230],{"class":4818},[3946,6212,4845],{"class":3955},[3946,6214,4848],{"class":4837},[3946,6216,4851],{"class":3955},[3946,6218,4854],{"class":3959},[3946,6220,4857],{"class":3955},[3946,6222,6223,6225,6227,6229,6231,6234,6236,6238,6240,6242],{"class":3948,"line":4265},[3946,6224,4862],{"class":4837},[3946,6226,4061],{"class":3955},[3946,6228,4867],{"class":4818},[3946,6230,4822],{"class":3955},[3946,6232,6233],{"class":3980},"'Повідомлення від сервера:'",[3946,6235,4882],{"class":3955},[3946,6237,4848],{"class":4837},[3946,6239,4061],{"class":3955},[3946,6241,5271],{"class":4837},[3946,6243,4828],{"class":3955},[3946,6245,6246],{"class":3948,"line":4271},[3946,6247,4914],{"class":3955},[3946,6249,6250],{"class":3948,"line":4277},[3946,6251,4257],{"emptyLinePlaceholder":4256},[3946,6253,6254],{"class":3948,"line":4282},[3946,6255,6256],{"class":4891},"\u002F\u002F Обидві сторони рівноправні\n",[3937,6258,6261],{"className":4796,"code":6259,"filename":6260,"language":4798,"meta":3942,"style":3942},"const eventSource = new EventSource('\u002Fevents');\n\n\u002F\u002F Лише сервер може відправляти дані\neventSource.onmessage = (event) => {\n  console.log('Подія від сервера:', event.data);\n};\n\n\u002F\u002F Клієнт НЕ може відправляти через SSE\n\u002F\u002F Потрібен окремий HTTP-запит для відправки\nfetch('\u002Fapi\u002Faction', { method: 'POST', body: data });\n","Server-Sent Events (однонаправлений)",[3911,6262,6263,6284,6288,6293,6312,6335,6339,6343,6348,6353],{"__ignoreMap":3942},[3946,6264,6265,6267,6270,6272,6274,6277,6279,6282],{"class":3948,"line":3949},[3946,6266,4805],{"class":3959},[3946,6268,6269],{"class":4808}," eventSource",[3946,6271,4812],{"class":3955},[3946,6273,4815],{"class":3959},[3946,6275,6276],{"class":4818}," EventSource",[3946,6278,4822],{"class":3955},[3946,6280,6281],{"class":3980},"'\u002Fevents'",[3946,6283,4828],{"class":3955},[3946,6285,6286],{"class":3948,"line":3970},[3946,6287,4257],{"emptyLinePlaceholder":4256},[3946,6289,6290],{"class":3948,"line":3984},[3946,6291,6292],{"class":4891},"\u002F\u002F Лише сервер може відправляти дані\n",[3946,6294,6295,6298,6300,6302,6304,6306,6308,6310],{"class":3948,"line":3995},[3946,6296,6297],{"class":4837},"eventSource",[3946,6299,4061],{"class":3955},[3946,6301,5230],{"class":4818},[3946,6303,4845],{"class":3955},[3946,6305,4848],{"class":4837},[3946,6307,4851],{"class":3955},[3946,6309,4854],{"class":3959},[3946,6311,4857],{"class":3955},[3946,6313,6314,6316,6318,6320,6322,6325,6327,6329,6331,6333],{"class":3948,"line":4006},[3946,6315,4862],{"class":4837},[3946,6317,4061],{"class":3955},[3946,6319,4867],{"class":4818},[3946,6321,4822],{"class":3955},[3946,6323,6324],{"class":3980},"'Подія від сервера:'",[3946,6326,4882],{"class":3955},[3946,6328,4848],{"class":4837},[3946,6330,4061],{"class":3955},[3946,6332,5271],{"class":4837},[3946,6334,4828],{"class":3955},[3946,6336,6337],{"class":3948,"line":4017},[3946,6338,4914],{"class":3955},[3946,6340,6341],{"class":3948,"line":4028},[3946,6342,4257],{"emptyLinePlaceholder":4256},[3946,6344,6345],{"class":3948,"line":4265},[3946,6346,6347],{"class":4891},"\u002F\u002F Клієнт НЕ може відправляти через SSE\n",[3946,6349,6350],{"class":3948,"line":4271},[3946,6351,6352],{"class":4891},"\u002F\u002F Потрібен окремий HTTP-запит для відправки\n",[3946,6354,6355,6358,6360,6363,6366,6369,6372,6374,6377,6380],{"class":3948,"line":4277},[3946,6356,6357],{"class":4818},"fetch",[3946,6359,4822],{"class":3955},[3946,6361,6362],{"class":3980},"'\u002Fapi\u002Faction'",[3946,6364,6365],{"class":3955},", { ",[3946,6367,6368],{"class":4837},"method:",[3946,6370,6371],{"class":3980}," 'POST'",[3946,6373,4882],{"class":3955},[3946,6375,6376],{"class":4837},"body:",[3946,6378,6379],{"class":4837}," data",[3946,6381,4978],{"class":3955},[3937,6383,6386],{"className":4796,"code":6384,"filename":6385,"language":4798,"meta":3942,"style":3942},"async function pollServer() {\n  const response = await fetch('\u002Fpoll?timeout=30000');\n  const data = await response.json();\n  console.log('Отримано оновлення:', data);\n  \n  \u002F\u002F Негайно відкриваємо новий запит\n  pollServer();\n}\n\npollServer();\n\u002F\u002F Кожен запит = нові HTTP-заголовки, нове TCP-з'єднання\n","HTTP Long Polling (симуляція реального часу)",[3911,6387,6388,6402,6425,6444,6463,6467,6472,6479,6484,6488,6495],{"__ignoreMap":3942},[3946,6389,6390,6393,6396,6399],{"class":3948,"line":3949},[3946,6391,6392],{"class":3959},"async",[3946,6394,6395],{"class":3959}," function",[3946,6397,6398],{"class":4818}," pollServer",[3946,6400,6401],{"class":3955},"() {\n",[3946,6403,6404,6407,6410,6412,6415,6418,6420,6423],{"class":3948,"line":3970},[3946,6405,6406],{"class":3959},"  const",[3946,6408,6409],{"class":4808}," response",[3946,6411,4812],{"class":3955},[3946,6413,6414],{"class":3952},"await",[3946,6416,6417],{"class":4818}," fetch",[3946,6419,4822],{"class":3955},[3946,6421,6422],{"class":3980},"'\u002Fpoll?timeout=30000'",[3946,6424,4828],{"class":3955},[3946,6426,6427,6429,6431,6433,6435,6437,6439,6442],{"class":3948,"line":3984},[3946,6428,6406],{"class":3959},[3946,6430,6379],{"class":4808},[3946,6432,4812],{"class":3955},[3946,6434,6414],{"class":3952},[3946,6436,6409],{"class":4837},[3946,6438,4061],{"class":3955},[3946,6440,6441],{"class":4818},"json",[3946,6443,5539],{"class":3955},[3946,6445,6446,6448,6450,6452,6454,6457,6459,6461],{"class":3948,"line":3995},[3946,6447,4862],{"class":4837},[3946,6449,4061],{"class":3955},[3946,6451,4867],{"class":4818},[3946,6453,4822],{"class":3955},[3946,6455,6456],{"class":3980},"'Отримано оновлення:'",[3946,6458,4882],{"class":3955},[3946,6460,5271],{"class":4837},[3946,6462,4828],{"class":3955},[3946,6464,6465],{"class":3948,"line":4006},[3946,6466,5922],{"class":3955},[3946,6468,6469],{"class":3948,"line":4017},[3946,6470,6471],{"class":4891},"  \u002F\u002F Негайно відкриваємо новий запит\n",[3946,6473,6474,6477],{"class":3948,"line":4028},[3946,6475,6476],{"class":4818},"  pollServer",[3946,6478,5539],{"class":3955},[3946,6480,6481],{"class":3948,"line":4265},[3946,6482,6483],{"class":3955},"}\n",[3946,6485,6486],{"class":3948,"line":4271},[3946,6487,4257],{"emptyLinePlaceholder":4256},[3946,6489,6490,6493],{"class":3948,"line":4277},[3946,6491,6492],{"class":4818},"pollServer",[3946,6494,5539],{"class":3955},[3946,6496,6497],{"class":3948,"line":4282},[3946,6498,6499],{"class":4891},"\u002F\u002F Кожен запит = нові HTTP-заголовки, нове TCP-з'єднання\n",[3817,6501,6502],{},"Порівняльна таблиця характеристик:",[6504,6505,6506,6524],"table",{},[6507,6508,6509],"thead",{},[6510,6511,6512,6516,6518,6521],"tr",{},[6513,6514,6515],"th",{},"Характеристика",[6513,6517,3884],{},[6513,6519,6520],{},"SSE",[6513,6522,6523],{},"Long Polling",[6525,6526,6527,6543,6564,6580,6596,6612,6629,6645,6661],"tbody",{},[6510,6528,6529,6535,6538,6541],{},[6530,6531,6532],"td",{},[3775,6533,6534],{},"Напрямок даних",[6530,6536,6537],{},"Повнодуплексний (клієнт ↔ сервер)",[6530,6539,6540],{},"Однонаправлений (сервер → клієнт)",[6530,6542,6540],{},[6510,6544,6545,6550,6559,6562],{},[6530,6546,6547],{},[3775,6548,6549],{},"Протокол",[6530,6551,6552,6555,6556],{},[3911,6553,6554],{},"ws:\u002F\u002F"," або ",[3911,6557,6558],{},"wss:\u002F\u002F",[6530,6560,6561],{},"HTTP\u002FHTTPS",[6530,6563,6561],{},[6510,6565,6566,6571,6574,6577],{},[6530,6567,6568],{},[3775,6569,6570],{},"З'єднання",[6530,6572,6573],{},"Одне довготривале TCP",[6530,6575,6576],{},"Одне довготривале HTTP",[6530,6578,6579],{},"Багато коротких HTTP",[6510,6581,6582,6587,6590,6593],{},[6530,6583,6584],{},[3775,6585,6586],{},"Overhead",[6530,6588,6589],{},"Мінімальний (2–14 байт на фрейм)",[6530,6591,6592],{},"Середній (HTTP-заголовки)",[6530,6594,6595],{},"Високий (повні HTTP-заголовки при кожному запиті)",[6510,6597,6598,6603,6606,6609],{},[6530,6599,6600],{},[3775,6601,6602],{},"Затримка",[6530,6604,6605],{},"10–50 мс",[6530,6607,6608],{},"50–200 мс",[6530,6610,6611],{},"500–2000 мс (залежить від інтервалу)",[6510,6613,6614,6619,6624,6627],{},[6530,6615,6616],{},[3775,6617,6618],{},"Proxy-friendly",[6530,6620,6621,6622],{},"Потребує підтримки ",[3911,6623,3987],{},[6530,6625,6626],{},"Повна сумісність",[6530,6628,6626],{},[6510,6630,6631,6636,6639,6642],{},[6530,6632,6633],{},[3775,6634,6635],{},"Автоматична реконнект",[6530,6637,6638],{},"Потрібна реалізація вручну",[6530,6640,6641],{},"Вбудована (EventSource API)",[6530,6643,6644],{},"Вбудована у логіці polling",[6510,6646,6647,6652,6655,6658],{},[6530,6648,6649],{},[3775,6650,6651],{},"Бінарні дані",[6530,6653,6654],{},"Підтримка нативна",[6530,6656,6657],{},"Підтримка відсутня (лише текст)",[6530,6659,6660],{},"Підтримка через Base64 (неефективно)",[6510,6662,6663,6668,6671,6674],{},[6530,6664,6665],{},[3775,6666,6667],{},"Сценарії використання",[6530,6669,6670],{},"Чати, ігри, фінанси, спільна робота",[6530,6672,6673],{},"Нотифікації, новинні стрічки, прогрес-бари",[6530,6675,6676],{},"Легаси-системи, прості оновлення",[5423,6678,6679,6680,6683],{},"Якщо застосунок вимагає лише односпрямованої передачі від сервера до клієнта (наприклад, оновлення цін на біржі), а клієнт надсилає дані рідко через звичайні HTTP API, ",[3775,6681,6682],{},"Server-Sent Events"," може бути простішим рішенням за WebSocket. SSE має вбудований механізм автоматичного перепідключення і не вимагає обробки бінарних фреймів.",[3812,6685,6687],{"id":6686},"практичні-сценарії-застосування-websocket","Практичні сценарії застосування WebSocket",[3817,6689,6690],{},"Протокол WebSocket знайшов широке застосування у найрізноманітніших категоріях вебзастосунків, де вимагається низька затримка та двостороння комунікація. Розглянемо типові архітектурні патерни та реальні приклади.",[3836,6692,6694],{"id":6693},"застосунки-обміну-миттєвими-повідомленнями-messaging","Застосунки обміну миттєвими повідомленнями (Messaging)",[3817,6696,6697],{},"Чат-застосунки є класичним сценарієм використання WebSocket. Коли користувач A надсилає повідомлення користувачеві B, сервер отримує дані через WebSocket-з'єднання A, зберігає повідомлення у базі даних та негайно пересилає його через активне WebSocket-з'єднання B без будь-яких запитів з боку B.",[3817,6699,6700],{},"Архітектура типового чат-застосунку:",[4217,6702,6704],{"alt":6703},"Архітектура чат-застосунку на WebSocket",[3937,6705,6707],{"className":4222,"code":6706,"language":4224,"meta":3942,"style":3942},"@startuml\nskinparam style plain\nskinparam backgroundColor #FFFFFF\n\npackage \"Клієнтська частина (Browser)\" {\n  [Користувач A\\nChat UI] as UserA #DBEAFE\n  [Користувач B\\nChat UI] as UserB #DBEAFE\n}\n\npackage \"Серверна частина (Node.js + Socket.IO)\" {\n  [WebSocket Server\\nConnection Manager] as WSServer #E2E8F0\n  [Chat Service\\nBusiness Logic] as ChatService #FEF3C7\n  [User Session Store\\n(Redis)] as Redis #DCFCE7\n}\n\ndatabase \"PostgreSQL\\nMessages DB\" as DB #F1F5F9\n\nUserA -down-> WSServer : WebSocket з'єднання A\nUserB -down-> WSServer : WebSocket з'єднання B\n\nWSServer -right-> ChatService : подія: нове повідомлення\nChatService -down-> DB : INSERT INTO messages\nChatService -down-> Redis : кешування online-статусу\nChatService -up-> WSServer : broadcast до отримувачів\n\nnote right of WSServer #FEF3C7\n  Сервер утримує map з'єднань:\n  { userId: WebSocketConnection }\n  \n  Коли отримано повідомлення від A,\n  знаходить з'єднання B та відправляє\nend note\n\n@enduml\n",[3911,6708,6709,6713,6717,6721,6725,6730,6735,6740,6744,6748,6753,6758,6763,6768,6772,6776,6781,6785,6790,6795,6799,6804,6809,6814,6819,6823,6828,6833,6838,6842,6847,6852,6856,6860],{"__ignoreMap":3942},[3946,6710,6711],{"class":3948,"line":3949},[3946,6712,4231],{},[3946,6714,6715],{"class":3948,"line":3970},[3946,6716,4236],{},[3946,6718,6719],{"class":3948,"line":3984},[3946,6720,4246],{},[3946,6722,6723],{"class":3948,"line":3995},[3946,6724,4257],{"emptyLinePlaceholder":4256},[3946,6726,6727],{"class":3948,"line":4006},[3946,6728,6729],{},"package \"Клієнтська частина (Browser)\" {\n",[3946,6731,6732],{"class":3948,"line":4017},[3946,6733,6734],{},"  [Користувач A\\nChat UI] as UserA #DBEAFE\n",[3946,6736,6737],{"class":3948,"line":4028},[3946,6738,6739],{},"  [Користувач B\\nChat UI] as UserB #DBEAFE\n",[3946,6741,6742],{"class":3948,"line":4265},[3946,6743,6483],{},[3946,6745,6746],{"class":3948,"line":4271},[3946,6747,4257],{"emptyLinePlaceholder":4256},[3946,6749,6750],{"class":3948,"line":4277},[3946,6751,6752],{},"package \"Серверна частина (Node.js + Socket.IO)\" {\n",[3946,6754,6755],{"class":3948,"line":4282},[3946,6756,6757],{},"  [WebSocket Server\\nConnection Manager] as WSServer #E2E8F0\n",[3946,6759,6760],{"class":3948,"line":4288},[3946,6761,6762],{},"  [Chat Service\\nBusiness Logic] as ChatService #FEF3C7\n",[3946,6764,6765],{"class":3948,"line":4294},[3946,6766,6767],{},"  [User Session Store\\n(Redis)] as Redis #DCFCE7\n",[3946,6769,6770],{"class":3948,"line":4300},[3946,6771,6483],{},[3946,6773,6774],{"class":3948,"line":4306},[3946,6775,4257],{"emptyLinePlaceholder":4256},[3946,6777,6778],{"class":3948,"line":4312},[3946,6779,6780],{},"database \"PostgreSQL\\nMessages DB\" as DB #F1F5F9\n",[3946,6782,6783],{"class":3948,"line":4317},[3946,6784,4257],{"emptyLinePlaceholder":4256},[3946,6786,6787],{"class":3948,"line":4323},[3946,6788,6789],{},"UserA -down-> WSServer : WebSocket з'єднання A\n",[3946,6791,6792],{"class":3948,"line":4329},[3946,6793,6794],{},"UserB -down-> WSServer : WebSocket з'єднання B\n",[3946,6796,6797],{"class":3948,"line":4335},[3946,6798,4257],{"emptyLinePlaceholder":4256},[3946,6800,6801],{"class":3948,"line":4341},[3946,6802,6803],{},"WSServer -right-> ChatService : подія: нове повідомлення\n",[3946,6805,6806],{"class":3948,"line":4347},[3946,6807,6808],{},"ChatService -down-> DB : INSERT INTO messages\n",[3946,6810,6811],{"class":3948,"line":4352},[3946,6812,6813],{},"ChatService -down-> Redis : кешування online-статусу\n",[3946,6815,6816],{"class":3948,"line":4358},[3946,6817,6818],{},"ChatService -up-> WSServer : broadcast до отримувачів\n",[3946,6820,6821],{"class":3948,"line":4364},[3946,6822,4257],{"emptyLinePlaceholder":4256},[3946,6824,6825],{"class":3948,"line":4369},[3946,6826,6827],{},"note right of WSServer #FEF3C7\n",[3946,6829,6830],{"class":3948,"line":4375},[3946,6831,6832],{},"  Сервер утримує map з'єднань:\n",[3946,6834,6835],{"class":3948,"line":4381},[3946,6836,6837],{},"  { userId: WebSocketConnection }\n",[3946,6839,6840],{"class":3948,"line":4387},[3946,6841,5922],{},[3946,6843,6844],{"class":3948,"line":4393},[3946,6845,6846],{},"  Коли отримано повідомлення від A,\n",[3946,6848,6849],{"class":3948,"line":4398},[3946,6850,6851],{},"  знаходить з'єднання B та відправляє\n",[3946,6853,6854],{"class":3948,"line":4403},[3946,6855,4344],{},[3946,6857,6858],{"class":3948,"line":4409},[3946,6859,4257],{"emptyLinePlaceholder":4256},[3946,6861,6862],{"class":3948,"line":4415},[3946,6863,4464],{},[3817,6865,6866],{},"Ключові переваги WebSocket у чатах:",[3742,6868,6869,6875,6889,6895],{},[3745,6870,6871,6874],{},[3775,6872,6873],{},"Миттєва доставка:"," повідомлення доставляються за ~10–50 мс замість 1–2 секунд затримки при polling.",[3745,6876,6877,6880,6881,6884,6885,6888],{},[3775,6878,6879],{},"Індикатори набору тексту:"," коли користувач друкує, клієнт може відправляти події ",[3911,6882,6883],{},"typing_start"," \u002F ",[3911,6886,6887],{},"typing_stop"," без створення нових HTTP-запитів.",[3745,6890,6891,6894],{},[3775,6892,6893],{},"Онлайн-статус у реальному часі:"," сервер може транслювати зміни статусу (online\u002Foffline\u002Faway) усім підключеним користувачам негайно.",[3745,6896,6897,6900,6901,6904,6905,4061],{},[3775,6898,6899],{},"Групові кімнати:"," Socket.IO та подібні бібліотеки надають механізм ",[3775,6902,6903],{},"rooms"," — логічного групування з'єднань, що дозволяє ефективно розсилати повідомлення всім учасникам кімнати одним викликом ",[3911,6906,6907],{},"io.to('room-123').emit('message', data)",[3836,6909,6911],{"id":6910},"інтерактивні-дошки-для-спільної-роботи-collaborative-editing","Інтерактивні дошки для спільної роботи (Collaborative Editing)",[3817,6913,6914],{},"Застосунки типу Google Docs, Miro, Figma дозволяють кільком користувачам одночасно редагувати документ або креслення. Кожна дія одного користувача (додавання тексту, переміщення об'єкта, зміна кольору) має миттєво відображатися у всіх інших клієнтів.",[3817,6916,6917,6918,3823,6921,6924,6925,3823,6928,6931],{},"WebSocket дозволяє реалізувати ",[3775,6919,6920],{},"оперативну трансформацію",[3752,6922,6923],{},"Operational Transformation, OT",") або ",[3775,6926,6927],{},"конфліктно-вільні реплікуючі типи даних",[3752,6929,6930],{},"Conflict-Free Replicated Data Types, CRDT",") — алгоритми, що синхронізують стан документа між клієнтами через потік інкрементальних змін.",[3817,6933,6934],{},"Приклад потоку подій:",[4169,6936,6937,6940,6946,6949,6952],{},[3745,6938,6939],{},"Користувач A вводить символ «H» у позицію 5 документа.",[3745,6941,6942,6943,4061],{},"Клієнт A відправляє через WebSocket: ",[3911,6944,6945],{},"{ op: 'insert', pos: 5, char: 'H' }",[3745,6947,6948],{},"Сервер перевіряє операцію, застосовує до серверної копії документа.",[3745,6950,6951],{},"Сервер транслює операцію всім підключеним клієнтам (B, C, D).",[3745,6953,6954],{},"Клієнти B, C, D отримують операцію через WebSocket і негайно оновлюють свій локальний стан документа.",[3817,6956,6957],{},"Типова затримка від введення символу до його відображення у віддаленого користувача — 50–100 мс, що є прийнятним для відчуття «синхронності».",[3903,6959,6960,6961,6964,6965,6968,6969,6972,6973,6976],{},"Для реалізації спільного редагування зазвичай використовують бібліотеки, що абстрагують складність: ",[3775,6962,6963],{},"Yjs"," (CRDT-бібліотека з підтримкою WebSocket через ",[3911,6966,6967],{},"y-websocket","), ",[3775,6970,6971],{},"ShareDB"," (OT-фреймворк на Node.js), ",[3775,6974,6975],{},"Automerge"," (CRDT для JavaScript).",[3836,6978,6980],{"id":6979},"фінансові-тікери-та-трейдинг-платформи","Фінансові тікери та трейдинг-платформи",[3817,6982,6983],{},"Біржові застосунки вимагають відображення ціни активів у реальному часі з мінімальною затримкою. Традиційний підхід polling генерував би тисячі запитів на секунду від усіх підключених трейдерів, створюючи нестерпне навантаження на сервер та мережу.",[3817,6985,6986],{},"З WebSocket біржа підтримує одне з'єднання з кожним клієнтом і надсилає оновлення ціни лише при зміні:",[3937,6988,6990],{"className":4796,"code":6989,"language":4798,"meta":3942,"style":3942},"\u002F\u002F Сервер відправляє оновлення ціни\nws.send(JSON.stringify({\n  type: 'price_update',\n  symbol: 'AAPL',\n  price: 178.42,\n  change: +1.25,\n  timestamp: Date.now()\n}));\n",[3911,6991,6992,6997,7018,7029,7039,7049,7062,7078],{"__ignoreMap":3942},[3946,6993,6994],{"class":3948,"line":3949},[3946,6995,6996],{"class":4891},"\u002F\u002F Сервер відправляє оновлення ціни\n",[3946,6998,6999,7001,7003,7005,7007,7010,7012,7015],{"class":3948,"line":3970},[3946,7000,4920],{"class":4837},[3946,7002,4061],{"class":3955},[3946,7004,4902],{"class":4818},[3946,7006,4822],{"class":3955},[3946,7008,7009],{"class":4808},"JSON",[3946,7011,4061],{"class":3955},[3946,7013,7014],{"class":4818},"stringify",[3946,7016,7017],{"class":3955},"({\n",[3946,7019,7020,7023,7026],{"class":3948,"line":3984},[3946,7021,7022],{"class":4837},"  type:",[3946,7024,7025],{"class":3980}," 'price_update'",[3946,7027,7028],{"class":3955},",\n",[3946,7030,7031,7034,7037],{"class":3948,"line":3995},[3946,7032,7033],{"class":4837},"  symbol:",[3946,7035,7036],{"class":3980}," 'AAPL'",[3946,7038,7028],{"class":3955},[3946,7040,7041,7044,7047],{"class":3948,"line":4006},[3946,7042,7043],{"class":4837},"  price:",[3946,7045,7046],{"class":3966}," 178.42",[3946,7048,7028],{"class":3955},[3946,7050,7051,7054,7057,7060],{"class":3948,"line":4017},[3946,7052,7053],{"class":4837},"  change:",[3946,7055,7056],{"class":3955}," +",[3946,7058,7059],{"class":3966},"1.25",[3946,7061,7028],{"class":3955},[3946,7063,7064,7067,7070,7072,7075],{"class":3948,"line":4028},[3946,7065,7066],{"class":4837},"  timestamp:",[3946,7068,7069],{"class":4837}," Date",[3946,7071,4061],{"class":3955},[3946,7073,7074],{"class":4818},"now",[3946,7076,7077],{"class":3955},"()\n",[3946,7079,7080],{"class":3948,"line":4265},[3946,7081,7082],{"class":3955},"}));\n",[3817,7084,7085],{},"Клієнт отримує дані та негайно оновлює інтерфейс без перемальовування всієї сторінки. Якщо за секунду ціна змінюється 10 разів, клієнт отримає 10 мікроповідомлень загальним обсягом ~500 байт замість 10 HTTP-запитів по ~1 КБ кожен (з заголовками).",[3817,7087,7088,7089,7092,7093,7096],{},"Для критичних сценаріїв (високочастотний трейдинг, ",[3752,7090,7091],{},"High-Frequency Trading, HFT",") використовують бінарні протоколи поверх WebSocket — наприклад, ",[3775,7094,7095],{},"FIX (Financial Information eXchange)"," у бінарному форматі, що зменшує розмір повідомлення до десятків байт та прискорює парсинг.",[3836,7098,7100],{"id":7099},"багатокористувацькі-онлайн-ігри-multiplayer-gaming","Багатокористувацькі онлайн-ігри (Multiplayer Gaming)",[3817,7102,7103,7104,7107],{},"Онлайн-ігри з багатьма гравцями у реальному часі (MOBA, шутери, аркади) вимагають синхронізації стану гри між клієнтами з частотою 10–60 разів на секунду (",[3752,7105,7106],{},"tick rate","). Кожен tick містить позиції гравців, стан об'єктів, події (постріли, підбір предметів).",[3817,7109,7110],{},"WebSocket дозволяє ігровому серверу розсилати tick-апдейти всім підключеним гравцям:",[3937,7112,7114],{"className":4796,"code":7113,"language":4798,"meta":3942,"style":3942},"\u002F\u002F Сервер надсилає стан гри 20 разів на секунду\nsetInterval(() => {\n  const gameState = {\n    tick: currentTick++,\n    players: gameWorld.getPlayers().map(p => ({\n      id: p.id,\n      x: p.x,\n      y: p.y,\n      health: p.health\n    })),\n    projectiles: gameWorld.getProjectiles()\n  };\n  \n  \u002F\u002F Broadcast усім клієнтам\n  gameRoom.broadcast(gameState);\n}, 50); \u002F\u002F 20 FPS\n",[3911,7115,7116,7121,7131,7141,7152,7181,7196,7210,7224,7236,7241,7255,7260,7264,7269,7286],{"__ignoreMap":3942},[3946,7117,7118],{"class":3948,"line":3949},[3946,7119,7120],{"class":4891},"\u002F\u002F Сервер надсилає стан гри 20 разів на секунду\n",[3946,7122,7123,7125,7127,7129],{"class":3948,"line":3970},[3946,7124,5464],{"class":4818},[3946,7126,5467],{"class":3955},[3946,7128,4854],{"class":3959},[3946,7130,4857],{"class":3955},[3946,7132,7133,7135,7138],{"class":3948,"line":3984},[3946,7134,6406],{"class":3959},[3946,7136,7137],{"class":4808}," gameState",[3946,7139,7140],{"class":3955}," = {\n",[3946,7142,7143,7146,7149],{"class":3948,"line":3995},[3946,7144,7145],{"class":4837},"    tick:",[3946,7147,7148],{"class":4837}," currentTick",[3946,7150,7151],{"class":3955},"++,\n",[3946,7153,7154,7157,7160,7162,7165,7168,7171,7173,7175,7178],{"class":3948,"line":4006},[3946,7155,7156],{"class":4837},"    players:",[3946,7158,7159],{"class":4837}," gameWorld",[3946,7161,4061],{"class":3955},[3946,7163,7164],{"class":4818},"getPlayers",[3946,7166,7167],{"class":3955},"().",[3946,7169,7170],{"class":4818},"map",[3946,7172,4822],{"class":3955},[3946,7174,3817],{"class":4837},[3946,7176,7177],{"class":3959}," =>",[3946,7179,7180],{"class":3955}," ({\n",[3946,7182,7183,7186,7189,7191,7194],{"class":3948,"line":4017},[3946,7184,7185],{"class":4837},"      id:",[3946,7187,7188],{"class":4837}," p",[3946,7190,4061],{"class":3955},[3946,7192,7193],{"class":4837},"id",[3946,7195,7028],{"class":3955},[3946,7197,7198,7201,7203,7205,7208],{"class":3948,"line":4028},[3946,7199,7200],{"class":4837},"      x:",[3946,7202,7188],{"class":4837},[3946,7204,4061],{"class":3955},[3946,7206,7207],{"class":4837},"x",[3946,7209,7028],{"class":3955},[3946,7211,7212,7215,7217,7219,7222],{"class":3948,"line":4265},[3946,7213,7214],{"class":4837},"      y:",[3946,7216,7188],{"class":4837},[3946,7218,4061],{"class":3955},[3946,7220,7221],{"class":4837},"y",[3946,7223,7028],{"class":3955},[3946,7225,7226,7229,7231,7233],{"class":3948,"line":4271},[3946,7227,7228],{"class":4837},"      health:",[3946,7230,7188],{"class":4837},[3946,7232,4061],{"class":3955},[3946,7234,7235],{"class":4837},"health\n",[3946,7237,7238],{"class":3948,"line":4277},[3946,7239,7240],{"class":3955},"    })),\n",[3946,7242,7243,7246,7248,7250,7253],{"class":3948,"line":4282},[3946,7244,7245],{"class":4837},"    projectiles:",[3946,7247,7159],{"class":4837},[3946,7249,4061],{"class":3955},[3946,7251,7252],{"class":4818},"getProjectiles",[3946,7254,7077],{"class":3955},[3946,7256,7257],{"class":3948,"line":4288},[3946,7258,7259],{"class":3955},"  };\n",[3946,7261,7262],{"class":3948,"line":4294},[3946,7263,5922],{"class":3955},[3946,7265,7266],{"class":3948,"line":4300},[3946,7267,7268],{"class":4891},"  \u002F\u002F Broadcast усім клієнтам\n",[3946,7270,7271,7274,7276,7279,7281,7284],{"class":3948,"line":4306},[3946,7272,7273],{"class":4837},"  gameRoom",[3946,7275,4061],{"class":3955},[3946,7277,7278],{"class":4818},"broadcast",[3946,7280,4822],{"class":3955},[3946,7282,7283],{"class":4837},"gameState",[3946,7285,4828],{"class":3955},[3946,7287,7288,7290,7293,7296],{"class":3948,"line":4312},[3946,7289,5585],{"class":3955},[3946,7291,7292],{"class":3966},"50",[3946,7294,7295],{"class":3955},"); ",[3946,7297,7298],{"class":4891},"\u002F\u002F 20 FPS\n",[3817,7300,7301],{},"Для зменшення обсягу даних застосовують:",[3742,7303,7304,7310,7316],{},[3745,7305,7306,7309],{},[3775,7307,7308],{},"Delta-компресію:"," надсилати лише зміни стану порівняно з попереднім tick.",[3745,7311,7312,7315],{},[3775,7313,7314],{},"Інтерполяцію на клієнті:"," клієнт згладжує рух між отриманими tick для плавної анімації.",[3745,7317,7318,3823,7321,7324],{},[3775,7319,7320],{},"Передбачення на клієнті",[3752,7322,7323],{},"client-side prediction","): клієнт миттєво відображає дії гравця локально, а сервер підтверджує або корегує стан у наступному tick.",[4469,7326,7327,7328,7331,7332,4882,7335,3834],{},"Для багатокористувацьких ігор з дуже низькими вимогами до затримки (\u003C 30 мс) WebSocket може бути недостатнім через overhead TCP-протоколу (гарантована доставка, повторна передача втрачених пакетів). У таких випадках використовують протокол ",[3775,7329,7330],{},"WebRTC Data Channels"," (працює поверх UDP через DTLS) або спеціалізовані протоколи UDP з власною логікою надійності (наприклад, ",[3775,7333,7334],{},"ENet",[3775,7336,7337],{},"KCP",[3836,7339,7341],{"id":7340},"системи-моніторингу-та-дашборди","Системи моніторингу та дашборди",[3817,7343,7344],{},"Адміністративні панелі для моніторингу серверів, застосунків, IoT-пристроїв вимагають відображення метрик у реальному часі — завантаження CPU, використання пам'яті, кількість запитів, статус сервісів. Замість оновлення сторінки кожні 5 секунд, WebSocket дозволяє серверу надсилати нові дані безпосередньо після їх збору.",[3817,7346,7347],{},"Приклад серверної частини дашборду:",[3937,7349,7351],{"className":4796,"code":7350,"language":4798,"meta":3942,"style":3942},"\u002F\u002F Сервер збирає метрики кожні 2 секунди\nsetInterval(async () => {\n  const metrics = await collectSystemMetrics(); \u002F\u002F CPU, RAM, Disk\n  \n  \u002F\u002F Надсилаємо всім підключеним адмінським клієнтам\n  adminRoom.emit('metrics_update', {\n    timestamp: Date.now(),\n    cpu: metrics.cpuUsage,\n    memory: metrics.memoryUsage,\n    activeConnections: metrics.connections\n  });\n}, 2000);\n",[3911,7352,7353,7358,7373,7392,7396,7401,7418,7432,7446,7460,7472,7476],{"__ignoreMap":3942},[3946,7354,7355],{"class":3948,"line":3949},[3946,7356,7357],{"class":4891},"\u002F\u002F Сервер збирає метрики кожні 2 секунди\n",[3946,7359,7360,7362,7364,7366,7369,7371],{"class":3948,"line":3970},[3946,7361,5464],{"class":4818},[3946,7363,4822],{"class":3955},[3946,7365,6392],{"class":3959},[3946,7367,7368],{"class":3955}," () ",[3946,7370,4854],{"class":3959},[3946,7372,4857],{"class":3955},[3946,7374,7375,7377,7380,7382,7384,7387,7389],{"class":3948,"line":3984},[3946,7376,6406],{"class":3959},[3946,7378,7379],{"class":4808}," metrics",[3946,7381,4812],{"class":3955},[3946,7383,6414],{"class":3952},[3946,7385,7386],{"class":4818}," collectSystemMetrics",[3946,7388,5572],{"class":3955},[3946,7390,7391],{"class":4891},"\u002F\u002F CPU, RAM, Disk\n",[3946,7393,7394],{"class":3948,"line":3995},[3946,7395,5922],{"class":3955},[3946,7397,7398],{"class":3948,"line":4006},[3946,7399,7400],{"class":4891},"  \u002F\u002F Надсилаємо всім підключеним адмінським клієнтам\n",[3946,7402,7403,7406,7408,7411,7413,7416],{"class":3948,"line":4017},[3946,7404,7405],{"class":4837},"  adminRoom",[3946,7407,4061],{"class":3955},[3946,7409,7410],{"class":4818},"emit",[3946,7412,4822],{"class":3955},[3946,7414,7415],{"class":3980},"'metrics_update'",[3946,7417,5864],{"class":3955},[3946,7419,7420,7423,7425,7427,7429],{"class":3948,"line":4028},[3946,7421,7422],{"class":4837},"    timestamp:",[3946,7424,7069],{"class":4837},[3946,7426,4061],{"class":3955},[3946,7428,7074],{"class":4818},[3946,7430,7431],{"class":3955},"(),\n",[3946,7433,7434,7437,7439,7441,7444],{"class":3948,"line":4265},[3946,7435,7436],{"class":4837},"    cpu:",[3946,7438,7379],{"class":4837},[3946,7440,4061],{"class":3955},[3946,7442,7443],{"class":4837},"cpuUsage",[3946,7445,7028],{"class":3955},[3946,7447,7448,7451,7453,7455,7458],{"class":3948,"line":4271},[3946,7449,7450],{"class":4837},"    memory:",[3946,7452,7379],{"class":4837},[3946,7454,4061],{"class":3955},[3946,7456,7457],{"class":4837},"memoryUsage",[3946,7459,7028],{"class":3955},[3946,7461,7462,7465,7467,7469],{"class":3948,"line":4277},[3946,7463,7464],{"class":4837},"    activeConnections:",[3946,7466,7379],{"class":4837},[3946,7468,4061],{"class":3955},[3946,7470,7471],{"class":4837},"connections\n",[3946,7473,7474],{"class":3948,"line":4282},[3946,7475,5580],{"class":3955},[3946,7477,7478,7480,7483],{"class":3948,"line":4288},[3946,7479,5585],{"class":3955},[3946,7481,7482],{"class":3966},"2000",[3946,7484,4828],{"class":3955},[3817,7486,7487],{},"Клієнт отримує метрики та оновлює графіки у реальному часі без перезавантаження:",[3937,7489,7491],{"className":4796,"code":7490,"language":4798,"meta":3942,"style":3942},"socket.on('metrics_update', (data) => {\n  \u002F\u002F Оновлюємо Chart.js або інший графічний компонент\n  cpuChart.data.labels.push(new Date(data.timestamp).toLocaleTimeString());\n  cpuChart.data.datasets[0].data.push(data.cpu);\n  cpuChart.update();\n});\n",[3911,7492,7493,7515,7520,7562,7601,7612],{"__ignoreMap":3942},[3946,7494,7495,7497,7499,7501,7503,7505,7507,7509,7511,7513],{"class":3948,"line":3949},[3946,7496,4838],{"class":4837},[3946,7498,4061],{"class":3955},[3946,7500,4992],{"class":4818},[3946,7502,4822],{"class":3955},[3946,7504,7415],{"class":3980},[3946,7506,5000],{"class":3955},[3946,7508,5271],{"class":4837},[3946,7510,4851],{"class":3955},[3946,7512,4854],{"class":3959},[3946,7514,4857],{"class":3955},[3946,7516,7517],{"class":3948,"line":3970},[3946,7518,7519],{"class":4891},"  \u002F\u002F Оновлюємо Chart.js або інший графічний компонент\n",[3946,7521,7522,7525,7527,7529,7531,7534,7536,7539,7541,7543,7545,7547,7549,7551,7554,7556,7559],{"class":3948,"line":3984},[3946,7523,7524],{"class":4837},"  cpuChart",[3946,7526,4061],{"class":3955},[3946,7528,5271],{"class":4837},[3946,7530,4061],{"class":3955},[3946,7532,7533],{"class":4837},"labels",[3946,7535,4061],{"class":3955},[3946,7537,7538],{"class":4818},"push",[3946,7540,4822],{"class":3955},[3946,7542,4815],{"class":3959},[3946,7544,7069],{"class":4818},[3946,7546,4822],{"class":3955},[3946,7548,5271],{"class":4837},[3946,7550,4061],{"class":3955},[3946,7552,7553],{"class":4837},"timestamp",[3946,7555,3834],{"class":3955},[3946,7557,7558],{"class":4818},"toLocaleTimeString",[3946,7560,7561],{"class":3955},"());\n",[3946,7563,7564,7566,7568,7570,7572,7575,7578,7581,7584,7586,7588,7590,7592,7594,7596,7599],{"class":3948,"line":3995},[3946,7565,7524],{"class":4837},[3946,7567,4061],{"class":3955},[3946,7569,5271],{"class":4837},[3946,7571,4061],{"class":3955},[3946,7573,7574],{"class":4837},"datasets",[3946,7576,7577],{"class":3955},"[",[3946,7579,7580],{"class":3966},"0",[3946,7582,7583],{"class":3955},"].",[3946,7585,5271],{"class":4837},[3946,7587,4061],{"class":3955},[3946,7589,7538],{"class":4818},[3946,7591,4822],{"class":3955},[3946,7593,5271],{"class":4837},[3946,7595,4061],{"class":3955},[3946,7597,7598],{"class":4837},"cpu",[3946,7600,4828],{"class":3955},[3946,7602,7603,7605,7607,7610],{"class":3948,"line":4006},[3946,7604,7524],{"class":4837},[3946,7606,4061],{"class":3955},[3946,7608,7609],{"class":4818},"update",[3946,7611,5539],{"class":3955},[3946,7613,7614],{"class":3948,"line":4017},[3946,7615,5062],{"class":3955},[3836,7617,7619],{"id":7618},"iot-та-телеметрія-пристроїв","IoT та телеметрія пристроїв",[3817,7621,7622],{},"Пристрої Інтернету речей (IoT) — сенсори температури, камери безпеки, розумні лічильники — генерують потоки даних, які мають бути відображені у веб-інтерфейсі оператора. Замість постійного опитування сервера, пристрої підключаються до WebSocket-сервера і надсилають телеметрію при зміні стану:",[3937,7624,7626],{"className":4796,"code":7625,"language":4798,"meta":3942,"style":3942},"\u002F\u002F Сенсор температури (ESP32 + JavaScript runtime)\nconst socket = new WebSocket('wss:\u002F\u002Fiot-hub.example.com\u002Fsensor-feed');\n\nsetInterval(() => {\n  const temperature = readTemperatureSensor();\n  socket.send(JSON.stringify({\n    deviceId: 'sensor-001',\n    type: 'temperature',\n    value: temperature,\n    unit: 'celsius',\n    timestamp: Date.now()\n  }));\n}, 10000); \u002F\u002F Кожні 10 секунд\n",[3911,7627,7628,7633,7652,7656,7666,7680,7698,7708,7718,7727,7737,7749,7754],{"__ignoreMap":3942},[3946,7629,7630],{"class":3948,"line":3949},[3946,7631,7632],{"class":4891},"\u002F\u002F Сенсор температури (ESP32 + JavaScript runtime)\n",[3946,7634,7635,7637,7639,7641,7643,7645,7647,7650],{"class":3948,"line":3970},[3946,7636,4805],{"class":3959},[3946,7638,4809],{"class":4808},[3946,7640,4812],{"class":3955},[3946,7642,4815],{"class":3959},[3946,7644,4819],{"class":4818},[3946,7646,4822],{"class":3955},[3946,7648,7649],{"class":3980},"'wss:\u002F\u002Fiot-hub.example.com\u002Fsensor-feed'",[3946,7651,4828],{"class":3955},[3946,7653,7654],{"class":3948,"line":3984},[3946,7655,4257],{"emptyLinePlaceholder":4256},[3946,7657,7658,7660,7662,7664],{"class":3948,"line":3995},[3946,7659,5464],{"class":4818},[3946,7661,5467],{"class":3955},[3946,7663,4854],{"class":3959},[3946,7665,4857],{"class":3955},[3946,7667,7668,7670,7673,7675,7678],{"class":3948,"line":4006},[3946,7669,6406],{"class":3959},[3946,7671,7672],{"class":4808}," temperature",[3946,7674,4812],{"class":3955},[3946,7676,7677],{"class":4818},"readTemperatureSensor",[3946,7679,5539],{"class":3955},[3946,7681,7682,7684,7686,7688,7690,7692,7694,7696],{"class":3948,"line":4017},[3946,7683,4897],{"class":4837},[3946,7685,4061],{"class":3955},[3946,7687,4902],{"class":4818},[3946,7689,4822],{"class":3955},[3946,7691,7009],{"class":4808},[3946,7693,4061],{"class":3955},[3946,7695,7014],{"class":4818},[3946,7697,7017],{"class":3955},[3946,7699,7700,7703,7706],{"class":3948,"line":4028},[3946,7701,7702],{"class":4837},"    deviceId:",[3946,7704,7705],{"class":3980}," 'sensor-001'",[3946,7707,7028],{"class":3955},[3946,7709,7710,7713,7716],{"class":3948,"line":4265},[3946,7711,7712],{"class":4837},"    type:",[3946,7714,7715],{"class":3980}," 'temperature'",[3946,7717,7028],{"class":3955},[3946,7719,7720,7723,7725],{"class":3948,"line":4271},[3946,7721,7722],{"class":4837},"    value:",[3946,7724,7672],{"class":4837},[3946,7726,7028],{"class":3955},[3946,7728,7729,7732,7735],{"class":3948,"line":4277},[3946,7730,7731],{"class":4837},"    unit:",[3946,7733,7734],{"class":3980}," 'celsius'",[3946,7736,7028],{"class":3955},[3946,7738,7739,7741,7743,7745,7747],{"class":3948,"line":4282},[3946,7740,7422],{"class":4837},[3946,7742,7069],{"class":4837},[3946,7744,4061],{"class":3955},[3946,7746,7074],{"class":4818},[3946,7748,7077],{"class":3955},[3946,7750,7751],{"class":3948,"line":4288},[3946,7752,7753],{"class":3955},"  }));\n",[3946,7755,7756,7758,7761,7763],{"class":3948,"line":4294},[3946,7757,5585],{"class":3955},[3946,7759,7760],{"class":3966},"10000",[3946,7762,7295],{"class":3955},[3946,7764,7765],{"class":4891},"\u002F\u002F Кожні 10 секунд\n",[3817,7767,7768],{},"Центральний сервер агрегує дані від тисяч пристроїв і транслює їх адміністраторам через окремі WebSocket-з'єднання.",[3812,7770,7772],{"id":7771},"масштабування-та-архітектурні-виклики-websocket","Масштабування та архітектурні виклики WebSocket",[3817,7774,7775],{},"Реалізація високонавантаженої системи на основі WebSocket вимагає врахування кількох важливих архітектурних аспектів, що відрізняються від традиційних stateless HTTP-застосунків.",[3836,7777,7779],{"id":7778},"проблема-збереження-стану-statefulness","Проблема збереження стану (Statefulness)",[3817,7781,7782,7783,7786],{},"На відміну від HTTP, де кожен запит є незалежним і може бути оброблений будь-яким сервером за балансувальником навантаження, WebSocket-з'єднання є ",[3775,7784,7785],{},"stateful"," — воно прив'язане до конкретного серверного процесу, що утримує відкритий TCP-сокет. Якщо клієнт A підключений до сервера S1, всі повідомлення від\u002Fдо A мають проходити через S1, а не через S2 або S3.",[3817,7788,7789],{},"Це створює два виклики:",[3817,7791,7792,7795],{},[3775,7793,7794],{},"1. Sticky Sessions:"," балансувальник навантаження має спрямовувати всі запити від одного клієнта на той самий сервер. Це досягається через механізми:",[3742,7797,7798,7801,7804],{},[3745,7799,7800],{},"Cookie-based routing (балансувальник встановлює cookie з ідентифікатором сервера при першому з'єднанні).",[3745,7802,7803],{},"IP Hash routing (хешування IP-адреси клієнта для вибору сервера).",[3745,7805,7806,7807,6555,7810,3834],{},"WebSocket-aware балансувальники (Nginx, HAProxy з директивою ",[3911,7808,7809],{},"ip_hash",[3911,7811,7812],{},"least_conn",[3817,7814,7815,7818,7819,7822],{},[3775,7816,7817],{},"2. Міжсерверна комунікація:"," Якщо користувач A підключений до сервера S1, а користувач B до сервера S2, як відправити повідомлення від A до B? Рішення — використання ",[3775,7820,7821],{},"pub\u002Fsub системи"," (Redis Pub\u002FSub, RabbitMQ, Kafka):",[4217,7824,7826],{"alt":7825},"Масштабування WebSocket через Redis Pub\u002FSub",[3937,7827,7829],{"className":4222,"code":7828,"language":4224,"meta":3942,"style":3942},"@startuml\nskinparam style plain\nskinparam backgroundColor #FFFFFF\n\nactor \"Користувач A\" as UserA #DBEAFE\nactor \"Користувач B\" as UserB #DBEAFE\n\npackage \"Сервер S1 (Node.js)\" {\n  [WebSocket Handler S1] as WS1 #E2E8F0\n}\n\npackage \"Сервер S2 (Node.js)\" {\n  [WebSocket Handler S2] as WS2 #E2E8F0\n}\n\npackage \"Redis Pub\u002FSub\" {\n  [Channel: chat-messages] as Redis #FEF3C7\n}\n\nUserA -> WS1 : WebSocket: \"Привіт, B!\"\nWS1 -> Redis : PUBLISH chat-messages\\n{\"from\": \"A\", \"to\": \"B\", \"text\": \"Привіт, B!\"}\n\nRedis -> WS1 : Subscriber отримує повідомлення\nRedis -> WS2 : Subscriber отримує повідомлення\n\nWS2 -> UserB : WebSocket: \"Привіт, B!\"\n\nnote bottom of Redis #DCFCE7\n  Кожен серверний процес підписується на канал Redis.\n  Коли S1 публікує повідомлення, Redis транслює\n  його всім підписникам (S1, S2, ..., SN).\n  S2 перевіряє, чи підключений користувач B, і відправляє.\nend note\n\n@enduml\n",[3911,7830,7831,7835,7839,7843,7847,7852,7857,7861,7866,7871,7875,7879,7884,7889,7893,7897,7902,7907,7911,7915,7920,7925,7929,7934,7939,7943,7948,7952,7957,7962,7967,7972,7977,7981,7985],{"__ignoreMap":3942},[3946,7832,7833],{"class":3948,"line":3949},[3946,7834,4231],{},[3946,7836,7837],{"class":3948,"line":3970},[3946,7838,4236],{},[3946,7840,7841],{"class":3948,"line":3984},[3946,7842,4246],{},[3946,7844,7845],{"class":3948,"line":3995},[3946,7846,4257],{"emptyLinePlaceholder":4256},[3946,7848,7849],{"class":3948,"line":4006},[3946,7850,7851],{},"actor \"Користувач A\" as UserA #DBEAFE\n",[3946,7853,7854],{"class":3948,"line":4017},[3946,7855,7856],{},"actor \"Користувач B\" as UserB #DBEAFE\n",[3946,7858,7859],{"class":3948,"line":4028},[3946,7860,4257],{"emptyLinePlaceholder":4256},[3946,7862,7863],{"class":3948,"line":4265},[3946,7864,7865],{},"package \"Сервер S1 (Node.js)\" {\n",[3946,7867,7868],{"class":3948,"line":4271},[3946,7869,7870],{},"  [WebSocket Handler S1] as WS1 #E2E8F0\n",[3946,7872,7873],{"class":3948,"line":4277},[3946,7874,6483],{},[3946,7876,7877],{"class":3948,"line":4282},[3946,7878,4257],{"emptyLinePlaceholder":4256},[3946,7880,7881],{"class":3948,"line":4288},[3946,7882,7883],{},"package \"Сервер S2 (Node.js)\" {\n",[3946,7885,7886],{"class":3948,"line":4294},[3946,7887,7888],{},"  [WebSocket Handler S2] as WS2 #E2E8F0\n",[3946,7890,7891],{"class":3948,"line":4300},[3946,7892,6483],{},[3946,7894,7895],{"class":3948,"line":4306},[3946,7896,4257],{"emptyLinePlaceholder":4256},[3946,7898,7899],{"class":3948,"line":4312},[3946,7900,7901],{},"package \"Redis Pub\u002FSub\" {\n",[3946,7903,7904],{"class":3948,"line":4317},[3946,7905,7906],{},"  [Channel: chat-messages] as Redis #FEF3C7\n",[3946,7908,7909],{"class":3948,"line":4323},[3946,7910,6483],{},[3946,7912,7913],{"class":3948,"line":4329},[3946,7914,4257],{"emptyLinePlaceholder":4256},[3946,7916,7917],{"class":3948,"line":4335},[3946,7918,7919],{},"UserA -> WS1 : WebSocket: \"Привіт, B!\"\n",[3946,7921,7922],{"class":3948,"line":4341},[3946,7923,7924],{},"WS1 -> Redis : PUBLISH chat-messages\\n{\"from\": \"A\", \"to\": \"B\", \"text\": \"Привіт, B!\"}\n",[3946,7926,7927],{"class":3948,"line":4347},[3946,7928,4257],{"emptyLinePlaceholder":4256},[3946,7930,7931],{"class":3948,"line":4352},[3946,7932,7933],{},"Redis -> WS1 : Subscriber отримує повідомлення\n",[3946,7935,7936],{"class":3948,"line":4358},[3946,7937,7938],{},"Redis -> WS2 : Subscriber отримує повідомлення\n",[3946,7940,7941],{"class":3948,"line":4364},[3946,7942,4257],{"emptyLinePlaceholder":4256},[3946,7944,7945],{"class":3948,"line":4369},[3946,7946,7947],{},"WS2 -> UserB : WebSocket: \"Привіт, B!\"\n",[3946,7949,7950],{"class":3948,"line":4375},[3946,7951,4257],{"emptyLinePlaceholder":4256},[3946,7953,7954],{"class":3948,"line":4381},[3946,7955,7956],{},"note bottom of Redis #DCFCE7\n",[3946,7958,7959],{"class":3948,"line":4387},[3946,7960,7961],{},"  Кожен серверний процес підписується на канал Redis.\n",[3946,7963,7964],{"class":3948,"line":4393},[3946,7965,7966],{},"  Коли S1 публікує повідомлення, Redis транслює\n",[3946,7968,7969],{"class":3948,"line":4398},[3946,7970,7971],{},"  його всім підписникам (S1, S2, ..., SN).\n",[3946,7973,7974],{"class":3948,"line":4403},[3946,7975,7976],{},"  S2 перевіряє, чи підключений користувач B, і відправляє.\n",[3946,7978,7979],{"class":3948,"line":4409},[3946,7980,4344],{},[3946,7982,7983],{"class":3948,"line":4415},[3946,7984,4257],{"emptyLinePlaceholder":4256},[3946,7986,7987],{"class":3948,"line":4421},[3946,7988,4464],{},[3817,7990,7991],{},"Код серверної частини з Redis Pub\u002FSub:",[3937,7993,7995],{"className":4796,"code":7994,"language":4798,"meta":3942,"style":3942},"const Redis = require('ioredis');\nconst pub = new Redis();\nconst sub = new Redis();\n\n\u002F\u002F Підписуємося на канал повідомлень\nsub.subscribe('chat-messages');\n\nsub.on('message', (channel, message) => {\n  const msg = JSON.parse(message);\n  \n  \u002F\u002F Шукаємо з'єднання отримувача на цьому сервері\n  const recipientSocket = connections.get(msg.to);\n  if (recipientSocket) {\n    recipientSocket.send(JSON.stringify(msg));\n  }\n});\n\n\u002F\u002F Коли отримуємо повідомлення від клієнта\nws.on('message', (data) => {\n  const msg = JSON.parse(data);\n  \n  \u002F\u002F Публікуємо у Redis — всі сервери отримають\n  pub.publish('chat-messages', JSON.stringify(msg));\n});\n",[3911,7996,7997,8015,8030,8045,8049,8054,8071,8075,8103,8125,8129,8134,8163,8174,8197,8201,8205,8209,8214,8236,8256,8260,8265,8293],{"__ignoreMap":3942},[3946,7998,7999,8001,8004,8006,8008,8010,8013],{"class":3948,"line":3949},[3946,8000,4805],{"class":3959},[3946,8002,8003],{"class":4808}," Redis",[3946,8005,4812],{"class":3955},[3946,8007,4941],{"class":4818},[3946,8009,4822],{"class":3955},[3946,8011,8012],{"class":3980},"'ioredis'",[3946,8014,4828],{"class":3955},[3946,8016,8017,8019,8022,8024,8026,8028],{"class":3948,"line":3970},[3946,8018,4805],{"class":3959},[3946,8020,8021],{"class":4808}," pub",[3946,8023,4812],{"class":3955},[3946,8025,4815],{"class":3959},[3946,8027,8003],{"class":4818},[3946,8029,5539],{"class":3955},[3946,8031,8032,8034,8037,8039,8041,8043],{"class":3948,"line":3984},[3946,8033,4805],{"class":3959},[3946,8035,8036],{"class":4808}," sub",[3946,8038,4812],{"class":3955},[3946,8040,4815],{"class":3959},[3946,8042,8003],{"class":4818},[3946,8044,5539],{"class":3955},[3946,8046,8047],{"class":3948,"line":3995},[3946,8048,4257],{"emptyLinePlaceholder":4256},[3946,8050,8051],{"class":3948,"line":4006},[3946,8052,8053],{"class":4891},"\u002F\u002F Підписуємося на канал повідомлень\n",[3946,8055,8056,8059,8061,8064,8066,8069],{"class":3948,"line":4017},[3946,8057,8058],{"class":4837},"sub",[3946,8060,4061],{"class":3955},[3946,8062,8063],{"class":4818},"subscribe",[3946,8065,4822],{"class":3955},[3946,8067,8068],{"class":3980},"'chat-messages'",[3946,8070,4828],{"class":3955},[3946,8072,8073],{"class":3948,"line":4028},[3946,8074,4257],{"emptyLinePlaceholder":4256},[3946,8076,8077,8079,8081,8083,8085,8088,8090,8093,8095,8097,8099,8101],{"class":3948,"line":4265},[3946,8078,8058],{"class":4837},[3946,8080,4061],{"class":3955},[3946,8082,4992],{"class":4818},[3946,8084,4822],{"class":3955},[3946,8086,8087],{"class":3980},"'message'",[3946,8089,5000],{"class":3955},[3946,8091,8092],{"class":4837},"channel",[3946,8094,4882],{"class":3955},[3946,8096,6092],{"class":4837},[3946,8098,4851],{"class":3955},[3946,8100,4854],{"class":3959},[3946,8102,4857],{"class":3955},[3946,8104,8105,8107,8110,8112,8114,8116,8119,8121,8123],{"class":3948,"line":4271},[3946,8106,6406],{"class":3959},[3946,8108,8109],{"class":4808}," msg",[3946,8111,4812],{"class":3955},[3946,8113,7009],{"class":4808},[3946,8115,4061],{"class":3955},[3946,8117,8118],{"class":4818},"parse",[3946,8120,4822],{"class":3955},[3946,8122,6092],{"class":4837},[3946,8124,4828],{"class":3955},[3946,8126,8127],{"class":3948,"line":4277},[3946,8128,5922],{"class":3955},[3946,8130,8131],{"class":3948,"line":4282},[3946,8132,8133],{"class":4891},"  \u002F\u002F Шукаємо з'єднання отримувача на цьому сервері\n",[3946,8135,8136,8138,8141,8143,8146,8148,8151,8153,8156,8158,8161],{"class":3948,"line":4288},[3946,8137,6406],{"class":3959},[3946,8139,8140],{"class":4808}," recipientSocket",[3946,8142,4812],{"class":3955},[3946,8144,8145],{"class":4837},"connections",[3946,8147,4061],{"class":3955},[3946,8149,8150],{"class":4818},"get",[3946,8152,4822],{"class":3955},[3946,8154,8155],{"class":4837},"msg",[3946,8157,4061],{"class":3955},[3946,8159,8160],{"class":4837},"to",[3946,8162,4828],{"class":3955},[3946,8164,8165,8167,8169,8172],{"class":3948,"line":4294},[3946,8166,5258],{"class":3952},[3946,8168,3823],{"class":3955},[3946,8170,8171],{"class":4837},"recipientSocket",[3946,8173,5280],{"class":3955},[3946,8175,8176,8179,8181,8183,8185,8187,8189,8191,8193,8195],{"class":3948,"line":4300},[3946,8177,8178],{"class":4837},"    recipientSocket",[3946,8180,4061],{"class":3955},[3946,8182,4902],{"class":4818},[3946,8184,4822],{"class":3955},[3946,8186,7009],{"class":4808},[3946,8188,4061],{"class":3955},[3946,8190,7014],{"class":4818},[3946,8192,4822],{"class":3955},[3946,8194,8155],{"class":4837},[3946,8196,5412],{"class":3955},[3946,8198,8199],{"class":3948,"line":4306},[3946,8200,5417],{"class":3955},[3946,8202,8203],{"class":3948,"line":4312},[3946,8204,5062],{"class":3955},[3946,8206,8207],{"class":3948,"line":4317},[3946,8208,4257],{"emptyLinePlaceholder":4256},[3946,8210,8211],{"class":3948,"line":4323},[3946,8212,8213],{"class":4891},"\u002F\u002F Коли отримуємо повідомлення від клієнта\n",[3946,8215,8216,8218,8220,8222,8224,8226,8228,8230,8232,8234],{"class":3948,"line":4329},[3946,8217,4920],{"class":4837},[3946,8219,4061],{"class":3955},[3946,8221,4992],{"class":4818},[3946,8223,4822],{"class":3955},[3946,8225,8087],{"class":3980},[3946,8227,5000],{"class":3955},[3946,8229,5271],{"class":4837},[3946,8231,4851],{"class":3955},[3946,8233,4854],{"class":3959},[3946,8235,4857],{"class":3955},[3946,8237,8238,8240,8242,8244,8246,8248,8250,8252,8254],{"class":3948,"line":4335},[3946,8239,6406],{"class":3959},[3946,8241,8109],{"class":4808},[3946,8243,4812],{"class":3955},[3946,8245,7009],{"class":4808},[3946,8247,4061],{"class":3955},[3946,8249,8118],{"class":4818},[3946,8251,4822],{"class":3955},[3946,8253,5271],{"class":4837},[3946,8255,4828],{"class":3955},[3946,8257,8258],{"class":3948,"line":4341},[3946,8259,5922],{"class":3955},[3946,8261,8262],{"class":3948,"line":4347},[3946,8263,8264],{"class":4891},"  \u002F\u002F Публікуємо у Redis — всі сервери отримають\n",[3946,8266,8267,8270,8272,8275,8277,8279,8281,8283,8285,8287,8289,8291],{"class":3948,"line":4352},[3946,8268,8269],{"class":4837},"  pub",[3946,8271,4061],{"class":3955},[3946,8273,8274],{"class":4818},"publish",[3946,8276,4822],{"class":3955},[3946,8278,8068],{"class":3980},[3946,8280,4882],{"class":3955},[3946,8282,7009],{"class":4808},[3946,8284,4061],{"class":3955},[3946,8286,7014],{"class":4818},[3946,8288,4822],{"class":3955},[3946,8290,8155],{"class":4837},[3946,8292,5412],{"class":3955},[3946,8294,8295],{"class":3948,"line":4358},[3946,8296,5062],{"class":3955},[3836,8298,8300],{"id":8299},"обмеження-ресурсів-та-вертикальне-масштабування","Обмеження ресурсів та вертикальне масштабування",[3817,8302,8303],{},"Кожне активне WebSocket-з'єднання споживає ресурси:",[3742,8305,8306,8312,8321],{},[3745,8307,8308,8311],{},[3775,8309,8310],{},"Пам'ять:"," ~4–16 КБ на з'єднання (буфери TCP, структури даних).",[3745,8313,8314,8317,8318,3834],{},[3775,8315,8316],{},"Дескриптори файлів:"," кожен сокет — це відкритий файловий дескриптор в ОС. Linux за замовчуванням обмежує процес ~1024 дескрипторами (збільшується через ",[3911,8319,8320],{},"ulimit -n 65536",[3745,8322,8323,8326],{},[3775,8324,8325],{},"CPU:"," обробка вхідних фреймів, серіалізація\u002Fдесеріалізація JSON, логіка бізнес-застосунку.",[3817,8328,8329],{},"Один серверний процес Node.js (однопотоковий Event Loop) може утримувати 10 000–50 000 одночасних з'єднань залежно від частоти повідомлень та складності обробки. Для підтримки 500 000 з'єднань потрібно ~10–50 серверів або використання багатопотокових рантаймів (C++ з Boost.Beast, Go, Rust).",[5423,8331,8332,8335],{},[3817,8333,8334],{},"Для екстремального масштабування (мільйони з'єднань) застосовують:",[3742,8336,8337,8346,8363],{},[3745,8338,8339,8342,8343,4061],{},[3775,8340,8341],{},"Кластеризацію на рівні ОС:"," запуск кількох процесів Node.js на одному сервері (по одному на ядро CPU) через модуль ",[3911,8344,8345],{},"cluster",[3745,8347,8348,8351,8352,4882,8355,8358,8359,8362],{},[3775,8349,8350],{},"Спеціалізовані WebSocket-шлюзи:"," написані на низькорівневих мовах (Nginx з модулем ",[3911,8353,8354],{},"ngx_http_v2_module",[3775,8356,8357],{},"Centrifugo"," на Go, ",[3775,8360,8361],{},"Phoenix Channels"," на Elixir).",[3745,8364,8365,8368],{},[3775,8366,8367],{},"Serverless WebSocket:"," AWS API Gateway WebSocket, Azure SignalR Service — повністю керовані сервіси, що автоматично масштабуються.",[3836,8370,8372],{"id":8371},"реконнект-та-гарантії-доставки","Реконнект та гарантії доставки",[3817,8374,8375,8376,3823,8379,8382,8383,3851],{},"WebSocket-з'єднання може обриватися з різних причин: втрата мережі, перезапуск сервера, таймаути проксі. Клієнт має реалізувати логіку ",[3775,8377,8378],{},"автоматичного перепідключення",[3752,8380,8381],{},"auto-reconnect",") з експоненційною затримкою (",[3752,8384,8385],{},"exponential backoff",[3937,8387,8389],{"className":4796,"code":8388,"language":4798,"meta":3942,"style":3942},"class ReconnectingWebSocket {\n  constructor(url) {\n    this.url = url;\n    this.reconnectDelay = 1000; \u002F\u002F Початкова затримка 1 сек\n    this.maxReconnectDelay = 30000; \u002F\u002F Максимум 30 сек\n    this.connect();\n  }\n\n  connect() {\n    this.ws = new WebSocket(this.url);\n    \n    this.ws.onopen = () => {\n      console.log('Підключено до WebSocket');\n      this.reconnectDelay = 1000; \u002F\u002F Скидаємо затримку\n    };\n    \n    this.ws.onclose = (event) => {\n      if (!event.wasClean) {\n        console.warn('З\\'єднання втрачено, перепідключення через', this.reconnectDelay, 'мс');\n        setTimeout(() => this.connect(), this.reconnectDelay);\n        \n        \u002F\u002F Збільшуємо затримку для наступної спроби\n        this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);\n      }\n    };\n    \n    this.ws.onerror = (error) => {\n      console.error('Помилка WebSocket:', error);\n    };\n  }\n\n  send(data) {\n    if (this.ws.readyState === WebSocket.OPEN) {\n      this.ws.send(data);\n    } else {\n      console.warn('Не можна відправити: з\\'єднання не активне');\n      \u002F\u002F Буферизувати повідомлення та відправити після реконнекту\n    }\n  }\n}\n",[3911,8390,8391,8401,8413,8428,8446,8464,8475,8479,8483,8490,8515,8520,8539,8555,8573,8578,8582,8604,8619,8653,8680,8685,8690,8733,8738,8742,8746,8768,8786,8790,8794,8798,8809,8836,8854,8863,8883,8888,8892,8896],{"__ignoreMap":3942},[3946,8392,8393,8396,8399],{"class":3948,"line":3949},[3946,8394,8395],{"class":3959},"class",[3946,8397,8398],{"class":5329}," ReconnectingWebSocket",[3946,8400,4857],{"class":3955},[3946,8402,8403,8406,8408,8411],{"class":3948,"line":3970},[3946,8404,8405],{"class":3959},"  constructor",[3946,8407,4822],{"class":3955},[3946,8409,8410],{"class":4837},"url",[3946,8412,5280],{"class":3955},[3946,8414,8415,8418,8420,8422,8424,8426],{"class":3948,"line":3984},[3946,8416,8417],{"class":3959},"    this",[3946,8419,4061],{"class":3955},[3946,8421,8410],{"class":4837},[3946,8423,4812],{"class":3955},[3946,8425,8410],{"class":4837},[3946,8427,5560],{"class":3955},[3946,8429,8430,8432,8434,8437,8439,8441,8443],{"class":3948,"line":3995},[3946,8431,8417],{"class":3959},[3946,8433,4061],{"class":3955},[3946,8435,8436],{"class":4837},"reconnectDelay",[3946,8438,4812],{"class":3955},[3946,8440,5735],{"class":3966},[3946,8442,5666],{"class":3955},[3946,8444,8445],{"class":4891},"\u002F\u002F Початкова затримка 1 сек\n",[3946,8447,8448,8450,8452,8455,8457,8459,8461],{"class":3948,"line":4006},[3946,8449,8417],{"class":3959},[3946,8451,4061],{"class":3955},[3946,8453,8454],{"class":4837},"maxReconnectDelay",[3946,8456,4812],{"class":3955},[3946,8458,5588],{"class":3966},[3946,8460,5666],{"class":3955},[3946,8462,8463],{"class":4891},"\u002F\u002F Максимум 30 сек\n",[3946,8465,8466,8468,8470,8473],{"class":3948,"line":4017},[3946,8467,8417],{"class":3959},[3946,8469,4061],{"class":3955},[3946,8471,8472],{"class":4818},"connect",[3946,8474,5539],{"class":3955},[3946,8476,8477],{"class":3948,"line":4028},[3946,8478,5417],{"class":3955},[3946,8480,8481],{"class":3948,"line":4265},[3946,8482,4257],{"emptyLinePlaceholder":4256},[3946,8484,8485,8488],{"class":3948,"line":4271},[3946,8486,8487],{"class":4818},"  connect",[3946,8489,6401],{"class":3955},[3946,8491,8492,8494,8496,8498,8500,8502,8504,8506,8509,8511,8513],{"class":3948,"line":4277},[3946,8493,8417],{"class":3959},[3946,8495,4061],{"class":3955},[3946,8497,4920],{"class":4837},[3946,8499,4812],{"class":3955},[3946,8501,4815],{"class":3959},[3946,8503,4819],{"class":4818},[3946,8505,4822],{"class":3955},[3946,8507,8508],{"class":3959},"this",[3946,8510,4061],{"class":3955},[3946,8512,8410],{"class":4837},[3946,8514,4828],{"class":3955},[3946,8516,8517],{"class":3948,"line":4282},[3946,8518,8519],{"class":3955},"    \n",[3946,8521,8522,8524,8526,8528,8530,8532,8535,8537],{"class":3948,"line":4288},[3946,8523,8417],{"class":3959},[3946,8525,4061],{"class":3955},[3946,8527,4920],{"class":4837},[3946,8529,4061],{"class":3955},[3946,8531,4792],{"class":4818},[3946,8533,8534],{"class":3955}," = () ",[3946,8536,4854],{"class":3959},[3946,8538,4857],{"class":3955},[3946,8540,8541,8544,8546,8548,8550,8553],{"class":3948,"line":4294},[3946,8542,8543],{"class":4837},"      console",[3946,8545,4061],{"class":3955},[3946,8547,4867],{"class":4818},[3946,8549,4822],{"class":3955},[3946,8551,8552],{"class":3980},"'Підключено до WebSocket'",[3946,8554,4828],{"class":3955},[3946,8556,8557,8560,8562,8564,8566,8568,8570],{"class":3948,"line":4300},[3946,8558,8559],{"class":3959},"      this",[3946,8561,4061],{"class":3955},[3946,8563,8436],{"class":4837},[3946,8565,4812],{"class":3955},[3946,8567,5735],{"class":3966},[3946,8569,5666],{"class":3955},[3946,8571,8572],{"class":4891},"\u002F\u002F Скидаємо затримку\n",[3946,8574,8575],{"class":3948,"line":4306},[3946,8576,8577],{"class":3955},"    };\n",[3946,8579,8580],{"class":3948,"line":4312},[3946,8581,8519],{"class":3955},[3946,8583,8584,8586,8588,8590,8592,8594,8596,8598,8600,8602],{"class":3948,"line":4317},[3946,8585,8417],{"class":3959},[3946,8587,4061],{"class":3955},[3946,8589,4920],{"class":4837},[3946,8591,4061],{"class":3955},[3946,8593,5820],{"class":4818},[3946,8595,4845],{"class":3955},[3946,8597,4848],{"class":4837},[3946,8599,4851],{"class":3955},[3946,8601,4854],{"class":3959},[3946,8603,4857],{"class":3955},[3946,8605,8606,8609,8611,8613,8615,8617],{"class":3948,"line":4323},[3946,8607,8608],{"class":3952},"      if",[3946,8610,5929],{"class":3955},[3946,8612,4848],{"class":4837},[3946,8614,4061],{"class":3955},[3946,8616,5910],{"class":4837},[3946,8618,5280],{"class":3955},[3946,8620,8621,8624,8626,8629,8631,8633,8635,8638,8640,8642,8644,8646,8648,8651],{"class":3948,"line":4329},[3946,8622,8623],{"class":4837},"        console",[3946,8625,4061],{"class":3955},[3946,8627,8628],{"class":4818},"warn",[3946,8630,4822],{"class":3955},[3946,8632,5856],{"class":3980},[3946,8634,4876],{"class":4875},[3946,8636,8637],{"class":3980},"єднання втрачено, перепідключення через'",[3946,8639,4882],{"class":3955},[3946,8641,8508],{"class":3959},[3946,8643,4061],{"class":3955},[3946,8645,8436],{"class":4837},[3946,8647,4882],{"class":3955},[3946,8649,8650],{"class":3980},"'мс'",[3946,8652,4828],{"class":3955},[3946,8654,8655,8658,8660,8662,8665,8667,8669,8672,8674,8676,8678],{"class":3948,"line":4335},[3946,8656,8657],{"class":4818},"        setTimeout",[3946,8659,5467],{"class":3955},[3946,8661,4854],{"class":3959},[3946,8663,8664],{"class":3959}," this",[3946,8666,4061],{"class":3955},[3946,8668,8472],{"class":4818},[3946,8670,8671],{"class":3955},"(), ",[3946,8673,8508],{"class":3959},[3946,8675,4061],{"class":3955},[3946,8677,8436],{"class":4837},[3946,8679,4828],{"class":3955},[3946,8681,8682],{"class":3948,"line":4341},[3946,8683,8684],{"class":3955},"        \n",[3946,8686,8687],{"class":3948,"line":4347},[3946,8688,8689],{"class":4891},"        \u002F\u002F Збільшуємо затримку для наступної спроби\n",[3946,8691,8692,8695,8697,8699,8701,8704,8706,8709,8711,8713,8715,8717,8720,8723,8725,8727,8729,8731],{"class":3948,"line":4352},[3946,8693,8694],{"class":3959},"        this",[3946,8696,4061],{"class":3955},[3946,8698,8436],{"class":4837},[3946,8700,4812],{"class":3955},[3946,8702,8703],{"class":4837},"Math",[3946,8705,4061],{"class":3955},[3946,8707,8708],{"class":4818},"min",[3946,8710,4822],{"class":3955},[3946,8712,8508],{"class":3959},[3946,8714,4061],{"class":3955},[3946,8716,8436],{"class":4837},[3946,8718,8719],{"class":3955}," * ",[3946,8721,8722],{"class":3966},"2",[3946,8724,4882],{"class":3955},[3946,8726,8508],{"class":3959},[3946,8728,4061],{"class":3955},[3946,8730,8454],{"class":4837},[3946,8732,4828],{"class":3955},[3946,8734,8735],{"class":3948,"line":4358},[3946,8736,8737],{"class":3955},"      }\n",[3946,8739,8740],{"class":3948,"line":4364},[3946,8741,8577],{"class":3955},[3946,8743,8744],{"class":3948,"line":4369},[3946,8745,8519],{"class":3955},[3946,8747,8748,8750,8752,8754,8756,8758,8760,8762,8764,8766],{"class":3948,"line":4375},[3946,8749,8417],{"class":3959},[3946,8751,4061],{"class":3955},[3946,8753,4920],{"class":4837},[3946,8755,4061],{"class":3955},[3946,8757,5983],{"class":4818},[3946,8759,4845],{"class":3955},[3946,8761,5946],{"class":4837},[3946,8763,4851],{"class":3955},[3946,8765,4854],{"class":3959},[3946,8767,4857],{"class":3955},[3946,8769,8770,8772,8774,8776,8778,8780,8782,8784],{"class":3948,"line":4381},[3946,8771,8543],{"class":4837},[3946,8773,4061],{"class":3955},[3946,8775,5946],{"class":4818},[3946,8777,4822],{"class":3955},[3946,8779,6019],{"class":3980},[3946,8781,4882],{"class":3955},[3946,8783,5946],{"class":4837},[3946,8785,4828],{"class":3955},[3946,8787,8788],{"class":3948,"line":4387},[3946,8789,8577],{"class":3955},[3946,8791,8792],{"class":3948,"line":4393},[3946,8793,5417],{"class":3955},[3946,8795,8796],{"class":3948,"line":4398},[3946,8797,4257],{"emptyLinePlaceholder":4256},[3946,8799,8800,8803,8805,8807],{"class":3948,"line":4403},[3946,8801,8802],{"class":4818},"  send",[3946,8804,4822],{"class":3955},[3946,8806,5271],{"class":4837},[3946,8808,5280],{"class":3955},[3946,8810,8811,8813,8815,8817,8819,8821,8823,8826,8828,8830,8832,8834],{"class":3948,"line":4409},[3946,8812,5502],{"class":3952},[3946,8814,3823],{"class":3955},[3946,8816,8508],{"class":3959},[3946,8818,4061],{"class":3955},[3946,8820,4920],{"class":4837},[3946,8822,4061],{"class":3955},[3946,8824,8825],{"class":4837},"readyState",[3946,8827,5274],{"class":3955},[3946,8829,3884],{"class":4837},[3946,8831,4061],{"class":3955},[3946,8833,4788],{"class":4808},[3946,8835,5280],{"class":3955},[3946,8837,8838,8840,8842,8844,8846,8848,8850,8852],{"class":3948,"line":4415},[3946,8839,8559],{"class":3959},[3946,8841,4061],{"class":3955},[3946,8843,4920],{"class":4837},[3946,8845,4061],{"class":3955},[3946,8847,4902],{"class":4818},[3946,8849,4822],{"class":3955},[3946,8851,5271],{"class":4837},[3946,8853,4828],{"class":3955},[3946,8855,8856,8859,8861],{"class":3948,"line":4421},[3946,8857,8858],{"class":3955},"    } ",[3946,8860,5312],{"class":3952},[3946,8862,4857],{"class":3955},[3946,8864,8865,8867,8869,8871,8873,8876,8878,8881],{"class":3948,"line":4427},[3946,8866,8543],{"class":4837},[3946,8868,4061],{"class":3955},[3946,8870,8628],{"class":4818},[3946,8872,4822],{"class":3955},[3946,8874,8875],{"class":3980},"'Не можна відправити: з",[3946,8877,4876],{"class":4875},[3946,8879,8880],{"class":3980},"єднання не активне'",[3946,8882,4828],{"class":3955},[3946,8884,8885],{"class":3948,"line":4433},[3946,8886,8887],{"class":4891},"      \u002F\u002F Буферизувати повідомлення та відправити після реконнекту\n",[3946,8889,8890],{"class":3948,"line":4438},[3946,8891,5544],{"class":3955},[3946,8893,8894],{"class":3948,"line":4443},[3946,8895,5417],{"class":3955},[3946,8897,8898],{"class":3948,"line":4449},[3946,8899,6483],{"class":3955},[3817,8901,8902,8903,8906,8907,8910],{},"Проте WebSocket ",[3775,8904,8905],{},"не гарантує доставку повідомлень"," на рівні застосунку — якщо з'єднання обривається між відправкою та підтвердженням, повідомлення може бути втрачено. Для критичних даних необхідно реалізувати механізм підтверджень (",[3752,8908,8909],{},"acknowledgements",") на рівні застосунку:",[3937,8912,8914],{"className":4796,"code":8913,"language":4798,"meta":3942,"style":3942},"\u002F\u002F Клієнт\nlet messageId = 0;\nconst pendingMessages = new Map();\n\nfunction sendMessage(data) {\n  const id = messageId++;\n  const message = { id, data };\n  \n  socket.send(JSON.stringify(message));\n  \n  \u002F\u002F Зберігаємо для повторної відправки, якщо не отримаємо ACK\n  pendingMessages.set(id, message);\n  \n  setTimeout(() => {\n    if (pendingMessages.has(id)) {\n      console.warn('Не отримано ACK для повідомлення', id, '— повторна відправка');\n      sendMessage(data);\n    }\n  }, 5000);\n}\n\nsocket.onmessage = (event) => {\n  const msg = JSON.parse(event.data);\n  if (msg.type === 'ack') {\n    pendingMessages.delete(msg.id); \u002F\u002F Підтверджено\n  }\n};\n",[3911,8915,8916,8921,8935,8951,8955,8969,8984,9003,9007,9029,9033,9038,9058,9062,9073,9094,9118,9129,9133,9143,9147,9151,9169,9193,9213,9236,9240],{"__ignoreMap":3942},[3946,8917,8918],{"class":3948,"line":3949},[3946,8919,8920],{"class":4891},"\u002F\u002F Клієнт\n",[3946,8922,8923,8926,8929,8931,8933],{"class":3948,"line":3970},[3946,8924,8925],{"class":3959},"let",[3946,8927,8928],{"class":4837}," messageId",[3946,8930,4812],{"class":3955},[3946,8932,7580],{"class":3966},[3946,8934,5560],{"class":3955},[3946,8936,8937,8939,8942,8944,8946,8949],{"class":3948,"line":3984},[3946,8938,4805],{"class":3959},[3946,8940,8941],{"class":4808}," pendingMessages",[3946,8943,4812],{"class":3955},[3946,8945,4815],{"class":3959},[3946,8947,8948],{"class":4818}," Map",[3946,8950,5539],{"class":3955},[3946,8952,8953],{"class":3948,"line":3995},[3946,8954,4257],{"emptyLinePlaceholder":4256},[3946,8956,8957,8960,8963,8965,8967],{"class":3948,"line":4006},[3946,8958,8959],{"class":3959},"function",[3946,8961,8962],{"class":4818}," sendMessage",[3946,8964,4822],{"class":3955},[3946,8966,5271],{"class":4837},[3946,8968,5280],{"class":3955},[3946,8970,8971,8973,8976,8978,8981],{"class":3948,"line":4017},[3946,8972,6406],{"class":3959},[3946,8974,8975],{"class":4808}," id",[3946,8977,4812],{"class":3955},[3946,8979,8980],{"class":4837},"messageId",[3946,8982,8983],{"class":3955},"++;\n",[3946,8985,8986,8988,8991,8994,8996,8998,9000],{"class":3948,"line":4028},[3946,8987,6406],{"class":3959},[3946,8989,8990],{"class":4808}," message",[3946,8992,8993],{"class":3955}," = { ",[3946,8995,7193],{"class":4837},[3946,8997,4882],{"class":3955},[3946,8999,5271],{"class":4837},[3946,9001,9002],{"class":3955}," };\n",[3946,9004,9005],{"class":3948,"line":4265},[3946,9006,5922],{"class":3955},[3946,9008,9009,9011,9013,9015,9017,9019,9021,9023,9025,9027],{"class":3948,"line":4271},[3946,9010,4897],{"class":4837},[3946,9012,4061],{"class":3955},[3946,9014,4902],{"class":4818},[3946,9016,4822],{"class":3955},[3946,9018,7009],{"class":4808},[3946,9020,4061],{"class":3955},[3946,9022,7014],{"class":4818},[3946,9024,4822],{"class":3955},[3946,9026,6092],{"class":4837},[3946,9028,5412],{"class":3955},[3946,9030,9031],{"class":3948,"line":4277},[3946,9032,5922],{"class":3955},[3946,9034,9035],{"class":3948,"line":4282},[3946,9036,9037],{"class":4891},"  \u002F\u002F Зберігаємо для повторної відправки, якщо не отримаємо ACK\n",[3946,9039,9040,9043,9045,9048,9050,9052,9054,9056],{"class":3948,"line":4288},[3946,9041,9042],{"class":4837},"  pendingMessages",[3946,9044,4061],{"class":3955},[3946,9046,9047],{"class":4818},"set",[3946,9049,4822],{"class":3955},[3946,9051,7193],{"class":4837},[3946,9053,4882],{"class":3955},[3946,9055,6092],{"class":4837},[3946,9057,4828],{"class":3955},[3946,9059,9060],{"class":3948,"line":4294},[3946,9061,5922],{"class":3955},[3946,9063,9064,9067,9069,9071],{"class":3948,"line":4300},[3946,9065,9066],{"class":4818},"  setTimeout",[3946,9068,5467],{"class":3955},[3946,9070,4854],{"class":3959},[3946,9072,4857],{"class":3955},[3946,9074,9075,9077,9079,9082,9084,9087,9089,9091],{"class":3948,"line":4306},[3946,9076,5502],{"class":3952},[3946,9078,3823],{"class":3955},[3946,9080,9081],{"class":4837},"pendingMessages",[3946,9083,4061],{"class":3955},[3946,9085,9086],{"class":4818},"has",[3946,9088,4822],{"class":3955},[3946,9090,7193],{"class":4837},[3946,9092,9093],{"class":3955},")) {\n",[3946,9095,9096,9098,9100,9102,9104,9107,9109,9111,9113,9116],{"class":3948,"line":4312},[3946,9097,8543],{"class":4837},[3946,9099,4061],{"class":3955},[3946,9101,8628],{"class":4818},[3946,9103,4822],{"class":3955},[3946,9105,9106],{"class":3980},"'Не отримано ACK для повідомлення'",[3946,9108,4882],{"class":3955},[3946,9110,7193],{"class":4837},[3946,9112,4882],{"class":3955},[3946,9114,9115],{"class":3980},"'— повторна відправка'",[3946,9117,4828],{"class":3955},[3946,9119,9120,9123,9125,9127],{"class":3948,"line":4317},[3946,9121,9122],{"class":4818},"      sendMessage",[3946,9124,4822],{"class":3955},[3946,9126,5271],{"class":4837},[3946,9128,4828],{"class":3955},[3946,9130,9131],{"class":3948,"line":4323},[3946,9132,5544],{"class":3955},[3946,9134,9135,9138,9141],{"class":3948,"line":4329},[3946,9136,9137],{"class":3955},"  }, ",[3946,9139,9140],{"class":3966},"5000",[3946,9142,4828],{"class":3955},[3946,9144,9145],{"class":3948,"line":4335},[3946,9146,6483],{"class":3955},[3946,9148,9149],{"class":3948,"line":4341},[3946,9150,4257],{"emptyLinePlaceholder":4256},[3946,9152,9153,9155,9157,9159,9161,9163,9165,9167],{"class":3948,"line":4347},[3946,9154,4838],{"class":4837},[3946,9156,4061],{"class":3955},[3946,9158,5230],{"class":4818},[3946,9160,4845],{"class":3955},[3946,9162,4848],{"class":4837},[3946,9164,4851],{"class":3955},[3946,9166,4854],{"class":3959},[3946,9168,4857],{"class":3955},[3946,9170,9171,9173,9175,9177,9179,9181,9183,9185,9187,9189,9191],{"class":3948,"line":4352},[3946,9172,6406],{"class":3959},[3946,9174,8109],{"class":4808},[3946,9176,4812],{"class":3955},[3946,9178,7009],{"class":4808},[3946,9180,4061],{"class":3955},[3946,9182,8118],{"class":4818},[3946,9184,4822],{"class":3955},[3946,9186,4848],{"class":4837},[3946,9188,4061],{"class":3955},[3946,9190,5271],{"class":4837},[3946,9192,4828],{"class":3955},[3946,9194,9195,9197,9199,9201,9203,9206,9208,9211],{"class":3948,"line":4358},[3946,9196,5258],{"class":3952},[3946,9198,3823],{"class":3955},[3946,9200,8155],{"class":4837},[3946,9202,4061],{"class":3955},[3946,9204,9205],{"class":4837},"type",[3946,9207,5274],{"class":3955},[3946,9209,9210],{"class":3980},"'ack'",[3946,9212,5280],{"class":3955},[3946,9214,9215,9218,9220,9223,9225,9227,9229,9231,9233],{"class":3948,"line":4364},[3946,9216,9217],{"class":4837},"    pendingMessages",[3946,9219,4061],{"class":3955},[3946,9221,9222],{"class":4818},"delete",[3946,9224,4822],{"class":3955},[3946,9226,8155],{"class":4837},[3946,9228,4061],{"class":3955},[3946,9230,7193],{"class":4837},[3946,9232,7295],{"class":3955},[3946,9234,9235],{"class":4891},"\u002F\u002F Підтверджено\n",[3946,9237,9238],{"class":3948,"line":4369},[3946,9239,5417],{"class":3955},[3946,9241,9242],{"class":3948,"line":4375},[3946,9243,4914],{"class":3955},[3817,9245,9246],{},"Сервер відправляє підтвердження після успішної обробки:",[3937,9248,9250],{"className":4796,"code":9249,"language":4798,"meta":3942,"style":3942},"ws.on('message', (data) => {\n  const msg = JSON.parse(data);\n  \n  \u002F\u002F Обробка повідомлення\n  processMessage(msg.data);\n  \n  \u002F\u002F Відправка підтвердження\n  ws.send(JSON.stringify({ type: 'ack', id: msg.id }));\n});\n",[3911,9251,9252,9274,9294,9298,9303,9318,9322,9327,9364],{"__ignoreMap":3942},[3946,9253,9254,9256,9258,9260,9262,9264,9266,9268,9270,9272],{"class":3948,"line":3949},[3946,9255,4920],{"class":4837},[3946,9257,4061],{"class":3955},[3946,9259,4992],{"class":4818},[3946,9261,4822],{"class":3955},[3946,9263,8087],{"class":3980},[3946,9265,5000],{"class":3955},[3946,9267,5271],{"class":4837},[3946,9269,4851],{"class":3955},[3946,9271,4854],{"class":3959},[3946,9273,4857],{"class":3955},[3946,9275,9276,9278,9280,9282,9284,9286,9288,9290,9292],{"class":3948,"line":3970},[3946,9277,6406],{"class":3959},[3946,9279,8109],{"class":4808},[3946,9281,4812],{"class":3955},[3946,9283,7009],{"class":4808},[3946,9285,4061],{"class":3955},[3946,9287,8118],{"class":4818},[3946,9289,4822],{"class":3955},[3946,9291,5271],{"class":4837},[3946,9293,4828],{"class":3955},[3946,9295,9296],{"class":3948,"line":3984},[3946,9297,5922],{"class":3955},[3946,9299,9300],{"class":3948,"line":3995},[3946,9301,9302],{"class":4891},"  \u002F\u002F Обробка повідомлення\n",[3946,9304,9305,9308,9310,9312,9314,9316],{"class":3948,"line":4006},[3946,9306,9307],{"class":4818},"  processMessage",[3946,9309,4822],{"class":3955},[3946,9311,8155],{"class":4837},[3946,9313,4061],{"class":3955},[3946,9315,5271],{"class":4837},[3946,9317,4828],{"class":3955},[3946,9319,9320],{"class":3948,"line":4017},[3946,9321,5922],{"class":3955},[3946,9323,9324],{"class":3948,"line":4028},[3946,9325,9326],{"class":4891},"  \u002F\u002F Відправка підтвердження\n",[3946,9328,9329,9331,9333,9335,9337,9339,9341,9343,9345,9347,9350,9352,9355,9357,9359,9361],{"class":3948,"line":4265},[3946,9330,5046],{"class":4837},[3946,9332,4061],{"class":3955},[3946,9334,4902],{"class":4818},[3946,9336,4822],{"class":3955},[3946,9338,7009],{"class":4808},[3946,9340,4061],{"class":3955},[3946,9342,7014],{"class":4818},[3946,9344,4969],{"class":3955},[3946,9346,5204],{"class":4837},[3946,9348,9349],{"class":3980}," 'ack'",[3946,9351,4882],{"class":3955},[3946,9353,9354],{"class":4837},"id:",[3946,9356,8109],{"class":4837},[3946,9358,4061],{"class":3955},[3946,9360,7193],{"class":4837},[3946,9362,9363],{"class":3955}," }));\n",[3946,9365,9366],{"class":3948,"line":4271},[3946,9367,5062],{"class":3955},[3812,9369,9371],{"id":9370},"безпека-websocket-зєднань","Безпека WebSocket-з'єднань",[3817,9373,9374],{},"WebSocket, як і будь-який мережевий протокол, має власні вектори атак та вимагає комплексного підходу до забезпечення безпеки. Розглянемо ключові загрози та методи захисту.",[3836,9376,9378],{"id":9377},"шифрування-wss-websocket-secure","Шифрування: WSS (WebSocket Secure)",[3817,9380,9381,9382,9384,9385,9388,9389,3823,9392,9394],{},"Незашифровані WebSocket-з'єднання (",[3911,9383,6554],{},") передають дані у відкритому вигляді, що дозволяє зловмисникам прослуховувати трафік або підробляти повідомлення методом атаки ",[3775,9386,9387],{},"Man-in-the-Middle (MITM)",". Для захисту конфіденційності та цілісності даних обов'язково використовуйте ",[3775,9390,9391],{},"WSS",[3911,9393,6558],{},") — WebSocket поверх TLS\u002FSSL, аналогічно HTTPS для HTTP.",[3817,9396,9397],{},"WSS працює ідентично WS, але рукостискання та всі фрейми передаються через зашифроване TLS-з'єднання. У браузері це виглядає так:",[3937,9399,9401],{"className":4796,"code":9400,"language":4798,"meta":3942,"style":3942},"\u002F\u002F Незахищене з'єднання (лише для локальної розробки!)\nconst socket = new WebSocket('ws:\u002F\u002Flocalhost:3000');\n\n\u002F\u002F Захищене з'єднання (продакшн)\nconst socket = new WebSocket('wss:\u002F\u002Fapi.example.com\u002Fchat');\n",[3911,9402,9403,9408,9427,9431,9436],{"__ignoreMap":3942},[3946,9404,9405],{"class":3948,"line":3949},[3946,9406,9407],{"class":4891},"\u002F\u002F Незахищене з'єднання (лише для локальної розробки!)\n",[3946,9409,9410,9412,9414,9416,9418,9420,9422,9425],{"class":3948,"line":3970},[3946,9411,4805],{"class":3959},[3946,9413,4809],{"class":4808},[3946,9415,4812],{"class":3955},[3946,9417,4815],{"class":3959},[3946,9419,4819],{"class":4818},[3946,9421,4822],{"class":3955},[3946,9423,9424],{"class":3980},"'ws:\u002F\u002Flocalhost:3000'",[3946,9426,4828],{"class":3955},[3946,9428,9429],{"class":3948,"line":3984},[3946,9430,4257],{"emptyLinePlaceholder":4256},[3946,9432,9433],{"class":3948,"line":3995},[3946,9434,9435],{"class":4891},"\u002F\u002F Захищене з'єднання (продакшн)\n",[3946,9437,9438,9440,9442,9444,9446,9448,9450,9453],{"class":3948,"line":4006},[3946,9439,4805],{"class":3959},[3946,9441,4809],{"class":4808},[3946,9443,4812],{"class":3955},[3946,9445,4815],{"class":3959},[3946,9447,4819],{"class":4818},[3946,9449,4822],{"class":3955},[3946,9451,9452],{"class":3980},"'wss:\u002F\u002Fapi.example.com\u002Fchat'",[3946,9454,4828],{"class":3955},[3817,9456,9457],{},"На серверній стороні WSS вимагає сертифіката TLS (Let's Encrypt, комерційний CA) та конфігурації HTTPS-сервера:",[3937,9459,9461],{"className":4796,"code":9460,"language":4798,"meta":3942,"style":3942},"const https = require('https');\nconst fs = require('fs');\nconst WebSocket = require('ws');\n\n\u002F\u002F Створення HTTPS-сервера з сертифікатом\nconst server = https.createServer({\n  cert: fs.readFileSync('\u002Fpath\u002Fto\u002Fcert.pem'),\n  key: fs.readFileSync('\u002Fpath\u002Fto\u002Fkey.pem')\n});\n\n\u002F\u002F WebSocket-сервер поверх HTTPS\nconst wss = new WebSocket.Server({ server });\n\nwss.on('connection', (ws) => {\n  console.log('Захищене WSS з\\'єднання встановлено');\n});\n\nserver.listen(443); \u002F\u002F Стандартний порт HTTPS\n",[3911,9462,9463,9481,9499,9515,9519,9524,9543,9563,9582,9586,9590,9595,9618,9622,9644,9664,9668,9672],{"__ignoreMap":3942},[3946,9464,9465,9467,9470,9472,9474,9476,9479],{"class":3948,"line":3949},[3946,9466,4805],{"class":3959},[3946,9468,9469],{"class":4808}," https",[3946,9471,4812],{"class":3955},[3946,9473,4941],{"class":4818},[3946,9475,4822],{"class":3955},[3946,9477,9478],{"class":3980},"'https'",[3946,9480,4828],{"class":3955},[3946,9482,9483,9485,9488,9490,9492,9494,9497],{"class":3948,"line":3970},[3946,9484,4805],{"class":3959},[3946,9486,9487],{"class":4808}," fs",[3946,9489,4812],{"class":3955},[3946,9491,4941],{"class":4818},[3946,9493,4822],{"class":3955},[3946,9495,9496],{"class":3980},"'fs'",[3946,9498,4828],{"class":3955},[3946,9500,9501,9503,9505,9507,9509,9511,9513],{"class":3948,"line":3984},[3946,9502,4805],{"class":3959},[3946,9504,4819],{"class":4808},[3946,9506,4812],{"class":3955},[3946,9508,4941],{"class":4818},[3946,9510,4822],{"class":3955},[3946,9512,4946],{"class":3980},[3946,9514,4828],{"class":3955},[3946,9516,9517],{"class":3948,"line":3995},[3946,9518,4257],{"emptyLinePlaceholder":4256},[3946,9520,9521],{"class":3948,"line":4006},[3946,9522,9523],{"class":4891},"\u002F\u002F Створення HTTPS-сервера з сертифікатом\n",[3946,9525,9526,9528,9531,9533,9536,9538,9541],{"class":3948,"line":4017},[3946,9527,4805],{"class":3959},[3946,9529,9530],{"class":4808}," server",[3946,9532,4812],{"class":3955},[3946,9534,9535],{"class":4837},"https",[3946,9537,4061],{"class":3955},[3946,9539,9540],{"class":4818},"createServer",[3946,9542,7017],{"class":3955},[3946,9544,9545,9548,9550,9552,9555,9557,9560],{"class":3948,"line":4028},[3946,9546,9547],{"class":4837},"  cert:",[3946,9549,9487],{"class":4837},[3946,9551,4061],{"class":3955},[3946,9553,9554],{"class":4818},"readFileSync",[3946,9556,4822],{"class":3955},[3946,9558,9559],{"class":3980},"'\u002Fpath\u002Fto\u002Fcert.pem'",[3946,9561,9562],{"class":3955},"),\n",[3946,9564,9565,9568,9570,9572,9574,9576,9579],{"class":3948,"line":4265},[3946,9566,9567],{"class":4837},"  key:",[3946,9569,9487],{"class":4837},[3946,9571,4061],{"class":3955},[3946,9573,9554],{"class":4818},[3946,9575,4822],{"class":3955},[3946,9577,9578],{"class":3980},"'\u002Fpath\u002Fto\u002Fkey.pem'",[3946,9580,9581],{"class":3955},")\n",[3946,9583,9584],{"class":3948,"line":4271},[3946,9585,5062],{"class":3955},[3946,9587,9588],{"class":3948,"line":4277},[3946,9589,4257],{"emptyLinePlaceholder":4256},[3946,9591,9592],{"class":3948,"line":4282},[3946,9593,9594],{"class":4891},"\u002F\u002F WebSocket-сервер поверх HTTPS\n",[3946,9596,9597,9599,9601,9603,9605,9607,9609,9611,9613,9616],{"class":3948,"line":4288},[3946,9598,4805],{"class":3959},[3946,9600,4955],{"class":4808},[3946,9602,4812],{"class":3955},[3946,9604,4815],{"class":3959},[3946,9606,4819],{"class":4837},[3946,9608,4061],{"class":3955},[3946,9610,4966],{"class":4818},[3946,9612,4969],{"class":3955},[3946,9614,9615],{"class":4837},"server",[3946,9617,4978],{"class":3955},[3946,9619,9620],{"class":3948,"line":4294},[3946,9621,4257],{"emptyLinePlaceholder":4256},[3946,9623,9624,9626,9628,9630,9632,9634,9636,9638,9640,9642],{"class":3948,"line":4300},[3946,9625,4987],{"class":4837},[3946,9627,4061],{"class":3955},[3946,9629,4992],{"class":4818},[3946,9631,4822],{"class":3955},[3946,9633,4997],{"class":3980},[3946,9635,5000],{"class":3955},[3946,9637,4920],{"class":4837},[3946,9639,4851],{"class":3955},[3946,9641,4854],{"class":3959},[3946,9643,4857],{"class":3955},[3946,9645,9646,9648,9650,9652,9654,9657,9659,9662],{"class":3948,"line":4306},[3946,9647,4862],{"class":4837},[3946,9649,4061],{"class":3955},[3946,9651,4867],{"class":4818},[3946,9653,4822],{"class":3955},[3946,9655,9656],{"class":3980},"'Захищене WSS з",[3946,9658,4876],{"class":4875},[3946,9660,9661],{"class":3980},"єднання встановлено'",[3946,9663,4828],{"class":3955},[3946,9665,9666],{"class":3948,"line":4312},[3946,9667,5062],{"class":3955},[3946,9669,9670],{"class":3948,"line":4317},[3946,9671,4257],{"emptyLinePlaceholder":4256},[3946,9673,9674,9676,9678,9681,9683,9686,9688],{"class":3948,"line":4323},[3946,9675,9615],{"class":4837},[3946,9677,4061],{"class":3955},[3946,9679,9680],{"class":4818},"listen",[3946,9682,4822],{"class":3955},[3946,9684,9685],{"class":3966},"443",[3946,9687,7295],{"class":3955},[3946,9689,9690],{"class":4891},"\u002F\u002F Стандартний порт HTTPS\n",[9692,9693,9694,9695,9697,9698,9701,9702,9704,9705,4061],"caution",{},"У продакшн-середовищі ніколи не використовуйте незашифровані ",[3911,9696,6554],{}," з'єднання для передачі чутливих даних (паролі, токени, особисті повідомлення). Сучасні браузери можуть блокувати WebSocket-з'єднання з незахищених сторінок (",[3911,9699,9700],{},"http:\u002F\u002F",") до захищених серверів (",[3911,9703,6558],{},") згідно з політикою ",[3775,9706,9707],{},"Mixed Content",[3836,9709,9711],{"id":9710},"автентифікація-та-авторизація","Автентифікація та авторизація",[3817,9713,9714],{},"WebSocket-протокол не має вбудованого механізму автентифікації — це відповідальність застосунку. Існує кілька стратегій:",[3817,9716,9717],{},[3775,9718,9719],{},"1. Автентифікація через токен у URL:",[3937,9721,9723],{"className":4796,"code":9722,"language":4798,"meta":3942,"style":3942},"const token = getUserAuthToken(); \u002F\u002F JWT або session token\nconst socket = new WebSocket(`wss:\u002F\u002Fapi.example.com\u002Fchat?token=${token}`);\n",[3911,9724,9725,9742],{"__ignoreMap":3942},[3946,9726,9727,9729,9732,9734,9737,9739],{"class":3948,"line":3949},[3946,9728,4805],{"class":3959},[3946,9730,9731],{"class":4808}," token",[3946,9733,4812],{"class":3955},[3946,9735,9736],{"class":4818},"getUserAuthToken",[3946,9738,5572],{"class":3955},[3946,9740,9741],{"class":4891},"\u002F\u002F JWT або session token\n",[3946,9743,9744,9746,9748,9750,9752,9754,9756,9759,9762,9765,9768,9771],{"class":3948,"line":3970},[3946,9745,4805],{"class":3959},[3946,9747,4809],{"class":4808},[3946,9749,4812],{"class":3955},[3946,9751,4815],{"class":3959},[3946,9753,4819],{"class":4818},[3946,9755,4822],{"class":3955},[3946,9757,9758],{"class":3980},"`wss:\u002F\u002Fapi.example.com\u002Fchat?token=",[3946,9760,9761],{"class":3959},"${",[3946,9763,9764],{"class":4837},"token",[3946,9766,9767],{"class":3959},"}",[3946,9769,9770],{"class":3980},"`",[3946,9772,4828],{"class":3955},[3817,9774,9775],{},"Сервер витягує токен з URL при рукостисканні:",[3937,9777,9779],{"className":4796,"code":9778,"language":4798,"meta":3942,"style":3942},"wss.on('connection', (ws, req) => {\n  const token = new URL(req.url, 'wss:\u002F\u002Fapi.example.com').searchParams.get('token');\n  \n  if (!verifyToken(token)) {\n    ws.close(1008, 'Неавторизований доступ'); \u002F\u002F Policy Violation\n    return;\n  }\n  \n  \u002F\u002F Токен дійсний — асоціюємо з'єднання з користувачем\n  ws.userId = extractUserId(token);\n});\n",[3911,9780,9781,9807,9849,9853,9868,9890,9897,9901,9905,9910,9930],{"__ignoreMap":3942},[3946,9782,9783,9785,9787,9789,9791,9793,9795,9797,9799,9801,9803,9805],{"class":3948,"line":3949},[3946,9784,4987],{"class":4837},[3946,9786,4061],{"class":3955},[3946,9788,4992],{"class":4818},[3946,9790,4822],{"class":3955},[3946,9792,4997],{"class":3980},[3946,9794,5000],{"class":3955},[3946,9796,4920],{"class":4837},[3946,9798,4882],{"class":3955},[3946,9800,5007],{"class":4837},[3946,9802,4851],{"class":3955},[3946,9804,4854],{"class":3959},[3946,9806,4857],{"class":3955},[3946,9808,9809,9811,9813,9815,9817,9820,9822,9824,9826,9828,9830,9833,9835,9838,9840,9842,9844,9847],{"class":3948,"line":3970},[3946,9810,6406],{"class":3959},[3946,9812,9731],{"class":4808},[3946,9814,4812],{"class":3955},[3946,9816,4815],{"class":3959},[3946,9818,9819],{"class":4818}," URL",[3946,9821,4822],{"class":3955},[3946,9823,5007],{"class":4837},[3946,9825,4061],{"class":3955},[3946,9827,8410],{"class":4837},[3946,9829,4882],{"class":3955},[3946,9831,9832],{"class":3980},"'wss:\u002F\u002Fapi.example.com'",[3946,9834,3834],{"class":3955},[3946,9836,9837],{"class":4837},"searchParams",[3946,9839,4061],{"class":3955},[3946,9841,8150],{"class":4818},[3946,9843,4822],{"class":3955},[3946,9845,9846],{"class":3980},"'token'",[3946,9848,4828],{"class":3955},[3946,9850,9851],{"class":3948,"line":3984},[3946,9852,5922],{"class":3955},[3946,9854,9855,9857,9859,9862,9864,9866],{"class":3948,"line":3995},[3946,9856,5258],{"class":3952},[3946,9858,5929],{"class":3955},[3946,9860,9861],{"class":4818},"verifyToken",[3946,9863,4822],{"class":3955},[3946,9865,9764],{"class":4837},[3946,9867,9093],{"class":3955},[3946,9869,9870,9872,9874,9876,9878,9880,9882,9885,9887],{"class":3948,"line":4006},[3946,9871,5549],{"class":4837},[3946,9873,4061],{"class":3955},[3946,9875,4589],{"class":4818},[3946,9877,4822],{"class":3955},[3946,9879,5787],{"class":3966},[3946,9881,4882],{"class":3955},[3946,9883,9884],{"class":3980},"'Неавторизований доступ'",[3946,9886,7295],{"class":3955},[3946,9888,9889],{"class":4891},"\u002F\u002F Policy Violation\n",[3946,9891,9892,9895],{"class":3948,"line":4017},[3946,9893,9894],{"class":3952},"    return",[3946,9896,5560],{"class":3955},[3946,9898,9899],{"class":3948,"line":4028},[3946,9900,5417],{"class":3955},[3946,9902,9903],{"class":3948,"line":4265},[3946,9904,5922],{"class":3955},[3946,9906,9907],{"class":3948,"line":4271},[3946,9908,9909],{"class":4891},"  \u002F\u002F Токен дійсний — асоціюємо з'єднання з користувачем\n",[3946,9911,9912,9914,9916,9919,9921,9924,9926,9928],{"class":3948,"line":4277},[3946,9913,5046],{"class":4837},[3946,9915,4061],{"class":3955},[3946,9917,9918],{"class":4837},"userId",[3946,9920,4812],{"class":3955},[3946,9922,9923],{"class":4818},"extractUserId",[3946,9925,4822],{"class":3955},[3946,9927,9764],{"class":4837},[3946,9929,4828],{"class":3955},[3946,9931,9932],{"class":3948,"line":4282},[3946,9933,5062],{"class":3955},[3817,9935,9936,9939],{},[3775,9937,9938],{},"Недоліки:"," токен видимий у логах сервера, проксі та історії браузера. Підходить лише для короткоживучих токенів.",[3817,9941,9942],{},[3775,9943,9944],{},"2. Автентифікація через HTTP Cookie:",[3937,9946,9948],{"className":4796,"code":9947,"language":4798,"meta":3942,"style":3942},"\u002F\u002F Клієнт спочатку логінується через HTTP API\nfetch('\u002Fapi\u002Fauth\u002Flogin', {\n  method: 'POST',\n  body: JSON.stringify({ email, password }),\n  credentials: 'include' \u002F\u002F Дозволити cookies\n});\n\n\u002F\u002F Браузер зберігає HttpOnly cookie з session ID або JWT\n\u002F\u002F При WebSocket-рукостисканні браузер автоматично відправляє cookies\nconst socket = new WebSocket('wss:\u002F\u002Fapi.example.com\u002Fchat');\n",[3911,9949,9950,9955,9966,9975,10000,10011,10015,10019,10024,10029],{"__ignoreMap":3942},[3946,9951,9952],{"class":3948,"line":3949},[3946,9953,9954],{"class":4891},"\u002F\u002F Клієнт спочатку логінується через HTTP API\n",[3946,9956,9957,9959,9961,9964],{"class":3948,"line":3970},[3946,9958,6357],{"class":4818},[3946,9960,4822],{"class":3955},[3946,9962,9963],{"class":3980},"'\u002Fapi\u002Fauth\u002Flogin'",[3946,9965,5864],{"class":3955},[3946,9967,9968,9971,9973],{"class":3948,"line":3984},[3946,9969,9970],{"class":4837},"  method:",[3946,9972,6371],{"class":3980},[3946,9974,7028],{"class":3955},[3946,9976,9977,9980,9983,9985,9987,9989,9992,9994,9997],{"class":3948,"line":3995},[3946,9978,9979],{"class":4837},"  body:",[3946,9981,9982],{"class":4808}," JSON",[3946,9984,4061],{"class":3955},[3946,9986,7014],{"class":4818},[3946,9988,4969],{"class":3955},[3946,9990,9991],{"class":4837},"email",[3946,9993,4882],{"class":3955},[3946,9995,9996],{"class":4837},"password",[3946,9998,9999],{"class":3955}," }),\n",[3946,10001,10002,10005,10008],{"class":3948,"line":4006},[3946,10003,10004],{"class":4837},"  credentials:",[3946,10006,10007],{"class":3980}," 'include'",[3946,10009,10010],{"class":4891}," \u002F\u002F Дозволити cookies\n",[3946,10012,10013],{"class":3948,"line":4017},[3946,10014,5062],{"class":3955},[3946,10016,10017],{"class":3948,"line":4028},[3946,10018,4257],{"emptyLinePlaceholder":4256},[3946,10020,10021],{"class":3948,"line":4265},[3946,10022,10023],{"class":4891},"\u002F\u002F Браузер зберігає HttpOnly cookie з session ID або JWT\n",[3946,10025,10026],{"class":3948,"line":4271},[3946,10027,10028],{"class":4891},"\u002F\u002F При WebSocket-рукостисканні браузер автоматично відправляє cookies\n",[3946,10030,10031,10033,10035,10037,10039,10041,10043,10045],{"class":3948,"line":4277},[3946,10032,4805],{"class":3959},[3946,10034,4809],{"class":4808},[3946,10036,4812],{"class":3955},[3946,10038,4815],{"class":3959},[3946,10040,4819],{"class":4818},[3946,10042,4822],{"class":3955},[3946,10044,9452],{"class":3980},[3946,10046,4828],{"class":3955},[3817,10048,10049],{},"Сервер перевіряє cookie:",[3937,10051,10053],{"className":4796,"code":10052,"language":4798,"meta":3942,"style":3942},"const cookie = require('cookie');\n\nwss.on('connection', (ws, req) => {\n  const cookies = cookie.parse(req.headers.cookie || '');\n  const sessionId = cookies['session_id'];\n  \n  if (!isValidSession(sessionId)) {\n    ws.close(1008, 'Недійсна сесія');\n    return;\n  }\n  \n  ws.userId = getSessionUserId(sessionId);\n});\n",[3911,10054,10055,10073,10077,10103,10140,10160,10164,10180,10199,10205,10209,10213,10232],{"__ignoreMap":3942},[3946,10056,10057,10059,10062,10064,10066,10068,10071],{"class":3948,"line":3949},[3946,10058,4805],{"class":3959},[3946,10060,10061],{"class":4808}," cookie",[3946,10063,4812],{"class":3955},[3946,10065,4941],{"class":4818},[3946,10067,4822],{"class":3955},[3946,10069,10070],{"class":3980},"'cookie'",[3946,10072,4828],{"class":3955},[3946,10074,10075],{"class":3948,"line":3970},[3946,10076,4257],{"emptyLinePlaceholder":4256},[3946,10078,10079,10081,10083,10085,10087,10089,10091,10093,10095,10097,10099,10101],{"class":3948,"line":3984},[3946,10080,4987],{"class":4837},[3946,10082,4061],{"class":3955},[3946,10084,4992],{"class":4818},[3946,10086,4822],{"class":3955},[3946,10088,4997],{"class":3980},[3946,10090,5000],{"class":3955},[3946,10092,4920],{"class":4837},[3946,10094,4882],{"class":3955},[3946,10096,5007],{"class":4837},[3946,10098,4851],{"class":3955},[3946,10100,4854],{"class":3959},[3946,10102,4857],{"class":3955},[3946,10104,10105,10107,10110,10112,10115,10117,10119,10121,10123,10125,10128,10130,10132,10135,10138],{"class":3948,"line":3995},[3946,10106,6406],{"class":3959},[3946,10108,10109],{"class":4808}," cookies",[3946,10111,4812],{"class":3955},[3946,10113,10114],{"class":4837},"cookie",[3946,10116,4061],{"class":3955},[3946,10118,8118],{"class":4818},[3946,10120,4822],{"class":3955},[3946,10122,5007],{"class":4837},[3946,10124,4061],{"class":3955},[3946,10126,10127],{"class":4837},"headers",[3946,10129,4061],{"class":3955},[3946,10131,10114],{"class":4837},[3946,10133,10134],{"class":3955}," || ",[3946,10136,10137],{"class":3980},"''",[3946,10139,4828],{"class":3955},[3946,10141,10142,10144,10147,10149,10152,10154,10157],{"class":3948,"line":4006},[3946,10143,6406],{"class":3959},[3946,10145,10146],{"class":4808}," sessionId",[3946,10148,4812],{"class":3955},[3946,10150,10151],{"class":4837},"cookies",[3946,10153,7577],{"class":3955},[3946,10155,10156],{"class":3980},"'session_id'",[3946,10158,10159],{"class":3955},"];\n",[3946,10161,10162],{"class":3948,"line":4017},[3946,10163,5922],{"class":3955},[3946,10165,10166,10168,10170,10173,10175,10178],{"class":3948,"line":4028},[3946,10167,5258],{"class":3952},[3946,10169,5929],{"class":3955},[3946,10171,10172],{"class":4818},"isValidSession",[3946,10174,4822],{"class":3955},[3946,10176,10177],{"class":4837},"sessionId",[3946,10179,9093],{"class":3955},[3946,10181,10182,10184,10186,10188,10190,10192,10194,10197],{"class":3948,"line":4265},[3946,10183,5549],{"class":4837},[3946,10185,4061],{"class":3955},[3946,10187,4589],{"class":4818},[3946,10189,4822],{"class":3955},[3946,10191,5787],{"class":3966},[3946,10193,4882],{"class":3955},[3946,10195,10196],{"class":3980},"'Недійсна сесія'",[3946,10198,4828],{"class":3955},[3946,10200,10201,10203],{"class":3948,"line":4271},[3946,10202,9894],{"class":3952},[3946,10204,5560],{"class":3955},[3946,10206,10207],{"class":3948,"line":4277},[3946,10208,5417],{"class":3955},[3946,10210,10211],{"class":3948,"line":4282},[3946,10212,5922],{"class":3955},[3946,10214,10215,10217,10219,10221,10223,10226,10228,10230],{"class":3948,"line":4288},[3946,10216,5046],{"class":4837},[3946,10218,4061],{"class":3955},[3946,10220,9918],{"class":4837},[3946,10222,4812],{"class":3955},[3946,10224,10225],{"class":4818},"getSessionUserId",[3946,10227,4822],{"class":3955},[3946,10229,10177],{"class":4837},[3946,10231,4828],{"class":3955},[3946,10233,10234],{"class":3948,"line":4294},[3946,10235,5062],{"class":3955},[3817,10237,10238,10241,10242,10245,10246,10249],{},[3775,10239,10240],{},"Переваги:"," токен не передається у URL, захищений атрибутами ",[3911,10243,10244],{},"HttpOnly"," (недоступний JavaScript) і ",[3911,10247,10248],{},"Secure"," (лише HTTPS).",[3817,10251,10252],{},[3775,10253,10254],{},"3. Автентифікація через перше повідомлення:",[3937,10256,10258],{"className":4796,"code":10257,"language":4798,"meta":3942,"style":3942},"const socket = new WebSocket('wss:\u002F\u002Fapi.example.com\u002Fchat');\n\nsocket.onopen = () => {\n  \u002F\u002F Відправляємо токен як перше повідомлення\n  socket.send(JSON.stringify({ type: 'auth', token: getUserAuthToken() }));\n};\n",[3911,10259,10260,10278,10282,10296,10301,10335],{"__ignoreMap":3942},[3946,10261,10262,10264,10266,10268,10270,10272,10274,10276],{"class":3948,"line":3949},[3946,10263,4805],{"class":3959},[3946,10265,4809],{"class":4808},[3946,10267,4812],{"class":3955},[3946,10269,4815],{"class":3959},[3946,10271,4819],{"class":4818},[3946,10273,4822],{"class":3955},[3946,10275,9452],{"class":3980},[3946,10277,4828],{"class":3955},[3946,10279,10280],{"class":3948,"line":3970},[3946,10281,4257],{"emptyLinePlaceholder":4256},[3946,10283,10284,10286,10288,10290,10292,10294],{"class":3948,"line":3984},[3946,10285,4838],{"class":4837},[3946,10287,4061],{"class":3955},[3946,10289,4792],{"class":4818},[3946,10291,8534],{"class":3955},[3946,10293,4854],{"class":3959},[3946,10295,4857],{"class":3955},[3946,10297,10298],{"class":3948,"line":3995},[3946,10299,10300],{"class":4891},"  \u002F\u002F Відправляємо токен як перше повідомлення\n",[3946,10302,10303,10305,10307,10309,10311,10313,10315,10317,10319,10321,10324,10326,10329,10332],{"class":3948,"line":4006},[3946,10304,4897],{"class":4837},[3946,10306,4061],{"class":3955},[3946,10308,4902],{"class":4818},[3946,10310,4822],{"class":3955},[3946,10312,7009],{"class":4808},[3946,10314,4061],{"class":3955},[3946,10316,7014],{"class":4818},[3946,10318,4969],{"class":3955},[3946,10320,5204],{"class":4837},[3946,10322,10323],{"class":3980}," 'auth'",[3946,10325,4882],{"class":3955},[3946,10327,10328],{"class":4837},"token:",[3946,10330,10331],{"class":4818}," getUserAuthToken",[3946,10333,10334],{"class":3955},"() }));\n",[3946,10336,10337],{"class":3948,"line":4017},[3946,10338,4914],{"class":3955},[3817,10340,10341],{},"Сервер очікує автентифікаційне повідомлення:",[3937,10343,10345],{"className":4796,"code":10344,"language":4798,"meta":3942,"style":3942},"wss.on('connection', (ws) => {\n  let isAuthenticated = false;\n  \n  const authTimeout = setTimeout(() => {\n    if (!isAuthenticated) {\n      ws.close(1008, 'Таймаут автентифікації');\n    }\n  }, 5000); \u002F\u002F 5 секунд на автентифікацію\n\n  ws.on('message', (data) => {\n    const msg = JSON.parse(data);\n    \n    if (!isAuthenticated) {\n      if (msg.type === 'auth' && verifyToken(msg.token)) {\n        isAuthenticated = true;\n        clearTimeout(authTimeout);\n        ws.userId = extractUserId(msg.token);\n      } else {\n        ws.close(1008, 'Невірний токен');\n      }\n      return;\n    }\n    \n    \u002F\u002F Обробка звичайних повідомлень після автентифікації\n    handleMessage(ws, msg);\n  });\n});\n",[3911,10346,10347,10369,10383,10387,10405,10416,10436,10440,10451,10455,10477,10498,10502,10512,10544,10555,10567,10590,10599,10618,10622,10628,10632,10636,10641,10656,10660],{"__ignoreMap":3942},[3946,10348,10349,10351,10353,10355,10357,10359,10361,10363,10365,10367],{"class":3948,"line":3949},[3946,10350,4987],{"class":4837},[3946,10352,4061],{"class":3955},[3946,10354,4992],{"class":4818},[3946,10356,4822],{"class":3955},[3946,10358,4997],{"class":3980},[3946,10360,5000],{"class":3955},[3946,10362,4920],{"class":4837},[3946,10364,4851],{"class":3955},[3946,10366,4854],{"class":3959},[3946,10368,4857],{"class":3955},[3946,10370,10371,10374,10377,10379,10381],{"class":3948,"line":3970},[3946,10372,10373],{"class":3959},"  let",[3946,10375,10376],{"class":4837}," isAuthenticated",[3946,10378,4812],{"class":3955},[3946,10380,5516],{"class":3959},[3946,10382,5560],{"class":3955},[3946,10384,10385],{"class":3948,"line":3984},[3946,10386,5922],{"class":3955},[3946,10388,10389,10391,10394,10396,10399,10401,10403],{"class":3948,"line":3995},[3946,10390,6406],{"class":3959},[3946,10392,10393],{"class":4808}," authTimeout",[3946,10395,4812],{"class":3955},[3946,10397,10398],{"class":4818},"setTimeout",[3946,10400,5467],{"class":3955},[3946,10402,4854],{"class":3959},[3946,10404,4857],{"class":3955},[3946,10406,10407,10409,10411,10414],{"class":3948,"line":4006},[3946,10408,5502],{"class":3952},[3946,10410,5929],{"class":3955},[3946,10412,10413],{"class":4837},"isAuthenticated",[3946,10415,5280],{"class":3955},[3946,10417,10418,10421,10423,10425,10427,10429,10431,10434],{"class":3948,"line":4017},[3946,10419,10420],{"class":4837},"      ws",[3946,10422,4061],{"class":3955},[3946,10424,4589],{"class":4818},[3946,10426,4822],{"class":3955},[3946,10428,5787],{"class":3966},[3946,10430,4882],{"class":3955},[3946,10432,10433],{"class":3980},"'Таймаут автентифікації'",[3946,10435,4828],{"class":3955},[3946,10437,10438],{"class":3948,"line":4028},[3946,10439,5544],{"class":3955},[3946,10441,10442,10444,10446,10448],{"class":3948,"line":4265},[3946,10443,9137],{"class":3955},[3946,10445,9140],{"class":3966},[3946,10447,7295],{"class":3955},[3946,10449,10450],{"class":4891},"\u002F\u002F 5 секунд на автентифікацію\n",[3946,10452,10453],{"class":3948,"line":4271},[3946,10454,4257],{"emptyLinePlaceholder":4256},[3946,10456,10457,10459,10461,10463,10465,10467,10469,10471,10473,10475],{"class":3948,"line":4277},[3946,10458,5046],{"class":4837},[3946,10460,4061],{"class":3955},[3946,10462,4992],{"class":4818},[3946,10464,4822],{"class":3955},[3946,10466,8087],{"class":3980},[3946,10468,5000],{"class":3955},[3946,10470,5271],{"class":4837},[3946,10472,4851],{"class":3955},[3946,10474,4854],{"class":3959},[3946,10476,4857],{"class":3955},[3946,10478,10479,10482,10484,10486,10488,10490,10492,10494,10496],{"class":3948,"line":4282},[3946,10480,10481],{"class":3959},"    const",[3946,10483,8109],{"class":4808},[3946,10485,4812],{"class":3955},[3946,10487,7009],{"class":4808},[3946,10489,4061],{"class":3955},[3946,10491,8118],{"class":4818},[3946,10493,4822],{"class":3955},[3946,10495,5271],{"class":4837},[3946,10497,4828],{"class":3955},[3946,10499,10500],{"class":3948,"line":4288},[3946,10501,8519],{"class":3955},[3946,10503,10504,10506,10508,10510],{"class":3948,"line":4294},[3946,10505,5502],{"class":3952},[3946,10507,5929],{"class":3955},[3946,10509,10413],{"class":4837},[3946,10511,5280],{"class":3955},[3946,10513,10514,10516,10518,10520,10522,10524,10526,10529,10532,10534,10536,10538,10540,10542],{"class":3948,"line":4300},[3946,10515,8608],{"class":3952},[3946,10517,3823],{"class":3955},[3946,10519,8155],{"class":4837},[3946,10521,4061],{"class":3955},[3946,10523,9205],{"class":4837},[3946,10525,5274],{"class":3955},[3946,10527,10528],{"class":3980},"'auth'",[3946,10530,10531],{"class":3955}," && ",[3946,10533,9861],{"class":4818},[3946,10535,4822],{"class":3955},[3946,10537,8155],{"class":4837},[3946,10539,4061],{"class":3955},[3946,10541,9764],{"class":4837},[3946,10543,9093],{"class":3955},[3946,10545,10546,10549,10551,10553],{"class":3948,"line":4306},[3946,10547,10548],{"class":4837},"        isAuthenticated",[3946,10550,4812],{"class":3955},[3946,10552,5629],{"class":3959},[3946,10554,5560],{"class":3955},[3946,10556,10557,10560,10562,10565],{"class":3948,"line":4312},[3946,10558,10559],{"class":4818},"        clearTimeout",[3946,10561,4822],{"class":3955},[3946,10563,10564],{"class":4837},"authTimeout",[3946,10566,4828],{"class":3955},[3946,10568,10569,10572,10574,10576,10578,10580,10582,10584,10586,10588],{"class":3948,"line":4317},[3946,10570,10571],{"class":4837},"        ws",[3946,10573,4061],{"class":3955},[3946,10575,9918],{"class":4837},[3946,10577,4812],{"class":3955},[3946,10579,9923],{"class":4818},[3946,10581,4822],{"class":3955},[3946,10583,8155],{"class":4837},[3946,10585,4061],{"class":3955},[3946,10587,9764],{"class":4837},[3946,10589,4828],{"class":3955},[3946,10591,10592,10595,10597],{"class":3948,"line":4323},[3946,10593,10594],{"class":3955},"      } ",[3946,10596,5312],{"class":3952},[3946,10598,4857],{"class":3955},[3946,10600,10601,10603,10605,10607,10609,10611,10613,10616],{"class":3948,"line":4329},[3946,10602,10571],{"class":4837},[3946,10604,4061],{"class":3955},[3946,10606,4589],{"class":4818},[3946,10608,4822],{"class":3955},[3946,10610,5787],{"class":3966},[3946,10612,4882],{"class":3955},[3946,10614,10615],{"class":3980},"'Невірний токен'",[3946,10617,4828],{"class":3955},[3946,10619,10620],{"class":3948,"line":4335},[3946,10621,8737],{"class":3955},[3946,10623,10624,10626],{"class":3948,"line":4341},[3946,10625,5528],{"class":3952},[3946,10627,5560],{"class":3955},[3946,10629,10630],{"class":3948,"line":4347},[3946,10631,5544],{"class":3955},[3946,10633,10634],{"class":3948,"line":4352},[3946,10635,8519],{"class":3955},[3946,10637,10638],{"class":3948,"line":4358},[3946,10639,10640],{"class":4891},"    \u002F\u002F Обробка звичайних повідомлень після автентифікації\n",[3946,10642,10643,10646,10648,10650,10652,10654],{"class":3948,"line":4364},[3946,10644,10645],{"class":4818},"    handleMessage",[3946,10647,4822],{"class":3955},[3946,10649,4920],{"class":4837},[3946,10651,4882],{"class":3955},[3946,10653,8155],{"class":4837},[3946,10655,4828],{"class":3955},[3946,10657,10658],{"class":3948,"line":4369},[3946,10659,5580],{"class":3955},[3946,10661,10662],{"class":3948,"line":4375},[3946,10663,5062],{"class":3955},[3836,10665,10667],{"id":10666},"захист-від-cross-site-websocket-hijacking-cswsh","Захист від Cross-Site WebSocket Hijacking (CSWSH)",[3817,10669,10670,10671,10674,10675,10678,10679,10682],{},"Атака CSWSH полягає у тому, що зловмисний сайт ",[3911,10672,10673],{},"evil.com"," може створити WebSocket-з'єднання до вашого сервера ",[3911,10676,10677],{},"api.example.com"," від імені користувача, що має активну сесію у ",[3911,10680,10681],{},"example.com",". Якщо сервер не перевіряє походження запиту, зловмисник може отримати доступ до конфіденційних даних.",[3817,10684,10685],{},[3775,10686,10687,10688,3977],{},"Захист через перевірку заголовка ",[3911,10689,4031],{},[3937,10691,10693],{"className":4796,"code":10692,"language":4798,"meta":3942,"style":3942},"wss.on('connection', (ws, req) => {\n  const origin = req.headers.origin;\n  const allowedOrigins = ['https:\u002F\u002Fexample.com', 'https:\u002F\u002Fapp.example.com'];\n  \n  if (!allowedOrigins.includes(origin)) {\n    console.warn('Підозріле підключення з', origin);\n    ws.close(1008, 'Недозволене походження');\n    return;\n  }\n  \n  \u002F\u002F Походження дозволене — продовжуємо\n});\n",[3911,10694,10695,10721,10742,10762,10766,10786,10805,10824,10830,10834,10838,10843],{"__ignoreMap":3942},[3946,10696,10697,10699,10701,10703,10705,10707,10709,10711,10713,10715,10717,10719],{"class":3948,"line":3949},[3946,10698,4987],{"class":4837},[3946,10700,4061],{"class":3955},[3946,10702,4992],{"class":4818},[3946,10704,4822],{"class":3955},[3946,10706,4997],{"class":3980},[3946,10708,5000],{"class":3955},[3946,10710,4920],{"class":4837},[3946,10712,4882],{"class":3955},[3946,10714,5007],{"class":4837},[3946,10716,4851],{"class":3955},[3946,10718,4854],{"class":3959},[3946,10720,4857],{"class":3955},[3946,10722,10723,10725,10728,10730,10732,10734,10736,10738,10740],{"class":3948,"line":3970},[3946,10724,6406],{"class":3959},[3946,10726,10727],{"class":4808}," origin",[3946,10729,4812],{"class":3955},[3946,10731,5007],{"class":4837},[3946,10733,4061],{"class":3955},[3946,10735,10127],{"class":4837},[3946,10737,4061],{"class":3955},[3946,10739,4088],{"class":4837},[3946,10741,5560],{"class":3955},[3946,10743,10744,10746,10749,10752,10755,10757,10760],{"class":3948,"line":3984},[3946,10745,6406],{"class":3959},[3946,10747,10748],{"class":4808}," allowedOrigins",[3946,10750,10751],{"class":3955}," = [",[3946,10753,10754],{"class":3980},"'https:\u002F\u002Fexample.com'",[3946,10756,4882],{"class":3955},[3946,10758,10759],{"class":3980},"'https:\u002F\u002Fapp.example.com'",[3946,10761,10159],{"class":3955},[3946,10763,10764],{"class":3948,"line":3995},[3946,10765,5922],{"class":3955},[3946,10767,10768,10770,10772,10775,10777,10780,10782,10784],{"class":3948,"line":4006},[3946,10769,5258],{"class":3952},[3946,10771,5929],{"class":3955},[3946,10773,10774],{"class":4837},"allowedOrigins",[3946,10776,4061],{"class":3955},[3946,10778,10779],{"class":4818},"includes",[3946,10781,4822],{"class":3955},[3946,10783,4088],{"class":4837},[3946,10785,9093],{"class":3955},[3946,10787,10788,10790,10792,10794,10796,10799,10801,10803],{"class":3948,"line":4017},[3946,10789,5285],{"class":4837},[3946,10791,4061],{"class":3955},[3946,10793,8628],{"class":4818},[3946,10795,4822],{"class":3955},[3946,10797,10798],{"class":3980},"'Підозріле підключення з'",[3946,10800,4882],{"class":3955},[3946,10802,4088],{"class":4837},[3946,10804,4828],{"class":3955},[3946,10806,10807,10809,10811,10813,10815,10817,10819,10822],{"class":3948,"line":4028},[3946,10808,5549],{"class":4837},[3946,10810,4061],{"class":3955},[3946,10812,4589],{"class":4818},[3946,10814,4822],{"class":3955},[3946,10816,5787],{"class":3966},[3946,10818,4882],{"class":3955},[3946,10820,10821],{"class":3980},"'Недозволене походження'",[3946,10823,4828],{"class":3955},[3946,10825,10826,10828],{"class":3948,"line":4265},[3946,10827,9894],{"class":3952},[3946,10829,5560],{"class":3955},[3946,10831,10832],{"class":3948,"line":4271},[3946,10833,5417],{"class":3955},[3946,10835,10836],{"class":3948,"line":4277},[3946,10837,5922],{"class":3955},[3946,10839,10840],{"class":3948,"line":4282},[3946,10841,10842],{"class":4891},"  \u002F\u002F Походження дозволене — продовжуємо\n",[3946,10844,10845],{"class":3948,"line":4288},[3946,10846,5062],{"class":3955},[3903,10848,10849,10850,10852,10853,10855,10856,10858,10859,4061],{},"Заголовок ",[3911,10851,4031],{}," не може бути підроблений браузером — він встановлюється автоматично і недоступний для модифікації через JavaScript. Проте зловмисник може підключитися до WebSocket з не-браузерного клієнта (Node.js, Python), де ",[3911,10854,4031],{}," контролюється повністю. Тому перевірка ",[3911,10857,4031],{}," має комбінуватися з автентифікацією через токени або cookies з атрибутом ",[3911,10860,10861],{},"SameSite",[3836,10863,10865],{"id":10864},"rate-limiting-та-захист-від-dos","Rate Limiting та захист від DoS",[3817,10867,10868,10869,4061],{},"Оскільки WebSocket-з'єднання довготривалі, зловмисник може відкрити тисячі з'єднань для вичерпання ресурсів сервера (дескриптори файлів, пам'ять) — атака типу ",[3775,10870,10871],{},"Connection Exhaustion DoS",[3817,10873,10874],{},[3775,10875,10876],{},"Захисні міри:",[4169,10878,10879],{},[3745,10880,10881],{},[3775,10882,10883],{},"Обмеження кількості з'єднань з одного IP:",[3937,10885,10887],{"className":4796,"code":10886,"language":4798,"meta":3942,"style":3942},"const connectionsPerIP = new Map();\n\nwss.on('connection', (ws, req) => {\n  const ip = req.socket.remoteAddress;\n  const count = connectionsPerIP.get(ip) || 0;\n  \n  if (count >= 10) { \u002F\u002F Максимум 10 з'єднань з одного IP\n    ws.close(1008, 'Забагато з\\'єднань');\n    return;\n  }\n  \n  connectionsPerIP.set(ip, count + 1);\n  \n  ws.on('close', () => {\n    connectionsPerIP.set(ip, connectionsPerIP.get(ip) - 1);\n  });\n});\n",[3911,10888,10889,10904,10908,10934,10955,10983,10987,11008,11032,11038,11042,11046,11071,11075,11094,11126,11130],{"__ignoreMap":3942},[3946,10890,10891,10893,10896,10898,10900,10902],{"class":3948,"line":3949},[3946,10892,4805],{"class":3959},[3946,10894,10895],{"class":4808}," connectionsPerIP",[3946,10897,4812],{"class":3955},[3946,10899,4815],{"class":3959},[3946,10901,8948],{"class":4818},[3946,10903,5539],{"class":3955},[3946,10905,10906],{"class":3948,"line":3970},[3946,10907,4257],{"emptyLinePlaceholder":4256},[3946,10909,10910,10912,10914,10916,10918,10920,10922,10924,10926,10928,10930,10932],{"class":3948,"line":3984},[3946,10911,4987],{"class":4837},[3946,10913,4061],{"class":3955},[3946,10915,4992],{"class":4818},[3946,10917,4822],{"class":3955},[3946,10919,4997],{"class":3980},[3946,10921,5000],{"class":3955},[3946,10923,4920],{"class":4837},[3946,10925,4882],{"class":3955},[3946,10927,5007],{"class":4837},[3946,10929,4851],{"class":3955},[3946,10931,4854],{"class":3959},[3946,10933,4857],{"class":3955},[3946,10935,10936,10938,10941,10943,10945,10947,10949,10951,10953],{"class":3948,"line":3995},[3946,10937,6406],{"class":3959},[3946,10939,10940],{"class":4808}," ip",[3946,10942,4812],{"class":3955},[3946,10944,5007],{"class":4837},[3946,10946,4061],{"class":3955},[3946,10948,4838],{"class":4837},[3946,10950,4061],{"class":3955},[3946,10952,5039],{"class":4837},[3946,10954,5560],{"class":3955},[3946,10956,10957,10959,10962,10964,10967,10969,10971,10973,10976,10979,10981],{"class":3948,"line":4006},[3946,10958,6406],{"class":3959},[3946,10960,10961],{"class":4808}," count",[3946,10963,4812],{"class":3955},[3946,10965,10966],{"class":4837},"connectionsPerIP",[3946,10968,4061],{"class":3955},[3946,10970,8150],{"class":4818},[3946,10972,4822],{"class":3955},[3946,10974,10975],{"class":4837},"ip",[3946,10977,10978],{"class":3955},") || ",[3946,10980,7580],{"class":3966},[3946,10982,5560],{"class":3955},[3946,10984,10985],{"class":3948,"line":4017},[3946,10986,5922],{"class":3955},[3946,10988,10989,10991,10993,10996,10999,11002,11005],{"class":3948,"line":4028},[3946,10990,5258],{"class":3952},[3946,10992,3823],{"class":3955},[3946,10994,10995],{"class":4837},"count",[3946,10997,10998],{"class":3955}," >= ",[3946,11000,11001],{"class":3966},"10",[3946,11003,11004],{"class":3955},") { ",[3946,11006,11007],{"class":4891},"\u002F\u002F Максимум 10 з'єднань з одного IP\n",[3946,11009,11010,11012,11014,11016,11018,11020,11022,11025,11027,11030],{"class":3948,"line":4265},[3946,11011,5549],{"class":4837},[3946,11013,4061],{"class":3955},[3946,11015,4589],{"class":4818},[3946,11017,4822],{"class":3955},[3946,11019,5787],{"class":3966},[3946,11021,4882],{"class":3955},[3946,11023,11024],{"class":3980},"'Забагато з",[3946,11026,4876],{"class":4875},[3946,11028,11029],{"class":3980},"єднань'",[3946,11031,4828],{"class":3955},[3946,11033,11034,11036],{"class":3948,"line":4271},[3946,11035,9894],{"class":3952},[3946,11037,5560],{"class":3955},[3946,11039,11040],{"class":3948,"line":4277},[3946,11041,5417],{"class":3955},[3946,11043,11044],{"class":3948,"line":4282},[3946,11045,5922],{"class":3955},[3946,11047,11048,11051,11053,11055,11057,11059,11061,11063,11066,11069],{"class":3948,"line":4288},[3946,11049,11050],{"class":4837},"  connectionsPerIP",[3946,11052,4061],{"class":3955},[3946,11054,9047],{"class":4818},[3946,11056,4822],{"class":3955},[3946,11058,10975],{"class":4837},[3946,11060,4882],{"class":3955},[3946,11062,10995],{"class":4837},[3946,11064,11065],{"class":3955}," + ",[3946,11067,11068],{"class":3966},"1",[3946,11070,4828],{"class":3955},[3946,11072,11073],{"class":3948,"line":4294},[3946,11074,5922],{"class":3955},[3946,11076,11077,11079,11081,11083,11085,11088,11090,11092],{"class":3948,"line":4300},[3946,11078,5046],{"class":4837},[3946,11080,4061],{"class":3955},[3946,11082,4992],{"class":4818},[3946,11084,4822],{"class":3955},[3946,11086,11087],{"class":3980},"'close'",[3946,11089,5647],{"class":3955},[3946,11091,4854],{"class":3959},[3946,11093,4857],{"class":3955},[3946,11095,11096,11099,11101,11103,11105,11107,11109,11111,11113,11115,11117,11119,11122,11124],{"class":3948,"line":4306},[3946,11097,11098],{"class":4837},"    connectionsPerIP",[3946,11100,4061],{"class":3955},[3946,11102,9047],{"class":4818},[3946,11104,4822],{"class":3955},[3946,11106,10975],{"class":4837},[3946,11108,4882],{"class":3955},[3946,11110,10966],{"class":4837},[3946,11112,4061],{"class":3955},[3946,11114,8150],{"class":4818},[3946,11116,4822],{"class":3955},[3946,11118,10975],{"class":4837},[3946,11120,11121],{"class":3955},") - ",[3946,11123,11068],{"class":3966},[3946,11125,4828],{"class":3955},[3946,11127,11128],{"class":3948,"line":4312},[3946,11129,5580],{"class":3955},[3946,11131,11132],{"class":3948,"line":4317},[3946,11133,5062],{"class":3955},[4169,11135,11136],{"start":3970},[3745,11137,11138],{},[3775,11139,11140],{},"Rate Limiting повідомлень:",[3937,11142,11144],{"className":4796,"code":11143,"language":4798,"meta":3942,"style":3942},"const messageRates = new Map();\n\nws.on('message', (data) => {\n  const userId = ws.userId;\n  const now = Date.now();\n  const userRate = messageRates.get(userId) || { count: 0, resetAt: now + 10000 };\n  \n  if (now > userRate.resetAt) {\n    userRate.count = 0;\n    userRate.resetAt = now + 10000;\n  }\n  \n  if (userRate.count >= 100) { \u002F\u002F Максимум 100 повідомлень за 10 секунд\n    ws.send(JSON.stringify({ error: 'Rate limit exceeded' }));\n    return;\n  }\n  \n  userRate.count++;\n  messageRates.set(userId, userRate);\n  \n  \u002F\u002F Обробка повідомлення\n});\n",[3911,11145,11146,11161,11165,11187,11204,11222,11264,11268,11289,11304,11322,11326,11330,11352,11378,11384,11388,11392,11403,11422,11426,11430],{"__ignoreMap":3942},[3946,11147,11148,11150,11153,11155,11157,11159],{"class":3948,"line":3949},[3946,11149,4805],{"class":3959},[3946,11151,11152],{"class":4808}," messageRates",[3946,11154,4812],{"class":3955},[3946,11156,4815],{"class":3959},[3946,11158,8948],{"class":4818},[3946,11160,5539],{"class":3955},[3946,11162,11163],{"class":3948,"line":3970},[3946,11164,4257],{"emptyLinePlaceholder":4256},[3946,11166,11167,11169,11171,11173,11175,11177,11179,11181,11183,11185],{"class":3948,"line":3984},[3946,11168,4920],{"class":4837},[3946,11170,4061],{"class":3955},[3946,11172,4992],{"class":4818},[3946,11174,4822],{"class":3955},[3946,11176,8087],{"class":3980},[3946,11178,5000],{"class":3955},[3946,11180,5271],{"class":4837},[3946,11182,4851],{"class":3955},[3946,11184,4854],{"class":3959},[3946,11186,4857],{"class":3955},[3946,11188,11189,11191,11194,11196,11198,11200,11202],{"class":3948,"line":3995},[3946,11190,6406],{"class":3959},[3946,11192,11193],{"class":4808}," userId",[3946,11195,4812],{"class":3955},[3946,11197,4920],{"class":4837},[3946,11199,4061],{"class":3955},[3946,11201,9918],{"class":4837},[3946,11203,5560],{"class":3955},[3946,11205,11206,11208,11211,11213,11216,11218,11220],{"class":3948,"line":4006},[3946,11207,6406],{"class":3959},[3946,11209,11210],{"class":4808}," now",[3946,11212,4812],{"class":3955},[3946,11214,11215],{"class":4837},"Date",[3946,11217,4061],{"class":3955},[3946,11219,7074],{"class":4818},[3946,11221,5539],{"class":3955},[3946,11223,11224,11226,11229,11231,11234,11236,11238,11240,11242,11245,11248,11251,11253,11256,11258,11260,11262],{"class":3948,"line":4017},[3946,11225,6406],{"class":3959},[3946,11227,11228],{"class":4808}," userRate",[3946,11230,4812],{"class":3955},[3946,11232,11233],{"class":4837},"messageRates",[3946,11235,4061],{"class":3955},[3946,11237,8150],{"class":4818},[3946,11239,4822],{"class":3955},[3946,11241,9918],{"class":4837},[3946,11243,11244],{"class":3955},") || { ",[3946,11246,11247],{"class":4837},"count:",[3946,11249,11250],{"class":3966}," 0",[3946,11252,4882],{"class":3955},[3946,11254,11255],{"class":4837},"resetAt:",[3946,11257,11210],{"class":4837},[3946,11259,11065],{"class":3955},[3946,11261,7760],{"class":3966},[3946,11263,9002],{"class":3955},[3946,11265,11266],{"class":3948,"line":4028},[3946,11267,5922],{"class":3955},[3946,11269,11270,11272,11274,11276,11279,11282,11284,11287],{"class":3948,"line":4265},[3946,11271,5258],{"class":3952},[3946,11273,3823],{"class":3955},[3946,11275,7074],{"class":4837},[3946,11277,11278],{"class":3955}," > ",[3946,11280,11281],{"class":4837},"userRate",[3946,11283,4061],{"class":3955},[3946,11285,11286],{"class":4837},"resetAt",[3946,11288,5280],{"class":3955},[3946,11290,11291,11294,11296,11298,11300,11302],{"class":3948,"line":4271},[3946,11292,11293],{"class":4837},"    userRate",[3946,11295,4061],{"class":3955},[3946,11297,10995],{"class":4837},[3946,11299,4812],{"class":3955},[3946,11301,7580],{"class":3966},[3946,11303,5560],{"class":3955},[3946,11305,11306,11308,11310,11312,11314,11316,11318,11320],{"class":3948,"line":4277},[3946,11307,11293],{"class":4837},[3946,11309,4061],{"class":3955},[3946,11311,11286],{"class":4837},[3946,11313,4812],{"class":3955},[3946,11315,7074],{"class":4837},[3946,11317,11065],{"class":3955},[3946,11319,7760],{"class":3966},[3946,11321,5560],{"class":3955},[3946,11323,11324],{"class":3948,"line":4282},[3946,11325,5417],{"class":3955},[3946,11327,11328],{"class":3948,"line":4288},[3946,11329,5922],{"class":3955},[3946,11331,11332,11334,11336,11338,11340,11342,11344,11347,11349],{"class":3948,"line":4294},[3946,11333,5258],{"class":3952},[3946,11335,3823],{"class":3955},[3946,11337,11281],{"class":4837},[3946,11339,4061],{"class":3955},[3946,11341,10995],{"class":4837},[3946,11343,10998],{"class":3955},[3946,11345,11346],{"class":3966},"100",[3946,11348,11004],{"class":3955},[3946,11350,11351],{"class":4891},"\u002F\u002F Максимум 100 повідомлень за 10 секунд\n",[3946,11353,11354,11356,11358,11360,11362,11364,11366,11368,11370,11373,11376],{"class":3948,"line":4300},[3946,11355,5549],{"class":4837},[3946,11357,4061],{"class":3955},[3946,11359,4902],{"class":4818},[3946,11361,4822],{"class":3955},[3946,11363,7009],{"class":4808},[3946,11365,4061],{"class":3955},[3946,11367,7014],{"class":4818},[3946,11369,4969],{"class":3955},[3946,11371,11372],{"class":4837},"error:",[3946,11374,11375],{"class":3980}," 'Rate limit exceeded'",[3946,11377,9363],{"class":3955},[3946,11379,11380,11382],{"class":3948,"line":4306},[3946,11381,9894],{"class":3952},[3946,11383,5560],{"class":3955},[3946,11385,11386],{"class":3948,"line":4312},[3946,11387,5417],{"class":3955},[3946,11389,11390],{"class":3948,"line":4317},[3946,11391,5922],{"class":3955},[3946,11393,11394,11397,11399,11401],{"class":3948,"line":4323},[3946,11395,11396],{"class":4837},"  userRate",[3946,11398,4061],{"class":3955},[3946,11400,10995],{"class":4837},[3946,11402,8983],{"class":3955},[3946,11404,11405,11408,11410,11412,11414,11416,11418,11420],{"class":3948,"line":4329},[3946,11406,11407],{"class":4837},"  messageRates",[3946,11409,4061],{"class":3955},[3946,11411,9047],{"class":4818},[3946,11413,4822],{"class":3955},[3946,11415,9918],{"class":4837},[3946,11417,4882],{"class":3955},[3946,11419,11281],{"class":4837},[3946,11421,4828],{"class":3955},[3946,11423,11424],{"class":3948,"line":4335},[3946,11425,5922],{"class":3955},[3946,11427,11428],{"class":3948,"line":4341},[3946,11429,9302],{"class":4891},[3946,11431,11432],{"class":3948,"line":4347},[3946,11433,5062],{"class":3955},[4169,11435,11436],{"start":3984},[3745,11437,11438,11441],{},[3775,11439,11440],{},"Використання reverse proxy з Rate Limiting:"," Nginx, Cloudflare або AWS WAF можуть обмежувати кількість нових WebSocket-з'єднань на хвилину на рівні інфраструктури.",[3836,11443,11445],{"id":11444},"валідація-вхідних-даних","Валідація вхідних даних",[3817,11447,11448],{},"Оскільки клієнт може відправляти довільні дані, сервер має валідувати кожне вхідне повідомлення:",[3937,11450,11452],{"className":4796,"code":11451,"language":4798,"meta":3942,"style":3942},"const Ajv = require('ajv');\nconst ajv = new Ajv();\n\nconst messageSchema = {\n  type: 'object',\n  properties: {\n    type: { type: 'string', enum: ['chat', 'typing', 'reaction'] },\n    text: { type: 'string', maxLength: 1000 },\n    roomId: { type: 'string', pattern: '^[a-zA-Z0-9-]{1,50}$' }\n  },\n  required: ['type', 'roomId']\n};\n\nconst validate = ajv.compile(messageSchema);\n\nws.on('message', (data) => {\n  let msg;\n  try {\n    msg = JSON.parse(data);\n  } catch (e) {\n    ws.send(JSON.stringify({ error: 'Невірний JSON' }));\n    return;\n  }\n  \n  if (!validate(msg)) {\n    ws.send(JSON.stringify({ error: 'Невалідна структура повідомлення', details: validate.errors }));\n    return;\n  }\n  \n  \u002F\u002F Обробка валідного повідомлення\n});\n",[3911,11453,11454,11472,11487,11491,11500,11509,11516,11552,11574,11596,11601,11619,11623,11627,11651,11655,11677,11685,11692,11711,11725,11750,11756,11760,11764,11779,11816,11822,11826,11830,11835],{"__ignoreMap":3942},[3946,11455,11456,11458,11461,11463,11465,11467,11470],{"class":3948,"line":3949},[3946,11457,4805],{"class":3959},[3946,11459,11460],{"class":4808}," Ajv",[3946,11462,4812],{"class":3955},[3946,11464,4941],{"class":4818},[3946,11466,4822],{"class":3955},[3946,11468,11469],{"class":3980},"'ajv'",[3946,11471,4828],{"class":3955},[3946,11473,11474,11476,11479,11481,11483,11485],{"class":3948,"line":3970},[3946,11475,4805],{"class":3959},[3946,11477,11478],{"class":4808}," ajv",[3946,11480,4812],{"class":3955},[3946,11482,4815],{"class":3959},[3946,11484,11460],{"class":4818},[3946,11486,5539],{"class":3955},[3946,11488,11489],{"class":3948,"line":3984},[3946,11490,4257],{"emptyLinePlaceholder":4256},[3946,11492,11493,11495,11498],{"class":3948,"line":3995},[3946,11494,4805],{"class":3959},[3946,11496,11497],{"class":4808}," messageSchema",[3946,11499,7140],{"class":3955},[3946,11501,11502,11504,11507],{"class":3948,"line":4006},[3946,11503,7022],{"class":4837},[3946,11505,11506],{"class":3980}," 'object'",[3946,11508,7028],{"class":3955},[3946,11510,11511,11514],{"class":3948,"line":4017},[3946,11512,11513],{"class":4837},"  properties:",[3946,11515,4857],{"class":3955},[3946,11517,11518,11520,11523,11525,11528,11530,11533,11536,11539,11541,11544,11546,11549],{"class":3948,"line":4028},[3946,11519,7712],{"class":4837},[3946,11521,11522],{"class":3955}," { ",[3946,11524,5204],{"class":4837},[3946,11526,11527],{"class":3980}," 'string'",[3946,11529,4882],{"class":3955},[3946,11531,11532],{"class":4837},"enum:",[3946,11534,11535],{"class":3955}," [",[3946,11537,11538],{"class":3980},"'chat'",[3946,11540,4882],{"class":3955},[3946,11542,11543],{"class":3980},"'typing'",[3946,11545,4882],{"class":3955},[3946,11547,11548],{"class":3980},"'reaction'",[3946,11550,11551],{"class":3955},"] },\n",[3946,11553,11554,11557,11559,11561,11563,11565,11568,11571],{"class":3948,"line":4265},[3946,11555,11556],{"class":4837},"    text:",[3946,11558,11522],{"class":3955},[3946,11560,5204],{"class":4837},[3946,11562,11527],{"class":3980},[3946,11564,4882],{"class":3955},[3946,11566,11567],{"class":4837},"maxLength:",[3946,11569,11570],{"class":3966}," 1000",[3946,11572,11573],{"class":3955}," },\n",[3946,11575,11576,11579,11581,11583,11585,11587,11590,11593],{"class":3948,"line":4271},[3946,11577,11578],{"class":4837},"    roomId:",[3946,11580,11522],{"class":3955},[3946,11582,5204],{"class":4837},[3946,11584,11527],{"class":3980},[3946,11586,4882],{"class":3955},[3946,11588,11589],{"class":4837},"pattern:",[3946,11591,11592],{"class":3980}," '^[a-zA-Z0-9-]{1,50}$'",[3946,11594,11595],{"class":3955}," }\n",[3946,11597,11598],{"class":3948,"line":4277},[3946,11599,11600],{"class":3955},"  },\n",[3946,11602,11603,11606,11608,11611,11613,11616],{"class":3948,"line":4282},[3946,11604,11605],{"class":4837},"  required:",[3946,11607,11535],{"class":3955},[3946,11609,11610],{"class":3980},"'type'",[3946,11612,4882],{"class":3955},[3946,11614,11615],{"class":3980},"'roomId'",[3946,11617,11618],{"class":3955},"]\n",[3946,11620,11621],{"class":3948,"line":4288},[3946,11622,4914],{"class":3955},[3946,11624,11625],{"class":3948,"line":4294},[3946,11626,4257],{"emptyLinePlaceholder":4256},[3946,11628,11629,11631,11634,11636,11639,11641,11644,11646,11649],{"class":3948,"line":4300},[3946,11630,4805],{"class":3959},[3946,11632,11633],{"class":4808}," validate",[3946,11635,4812],{"class":3955},[3946,11637,11638],{"class":4837},"ajv",[3946,11640,4061],{"class":3955},[3946,11642,11643],{"class":4818},"compile",[3946,11645,4822],{"class":3955},[3946,11647,11648],{"class":4837},"messageSchema",[3946,11650,4828],{"class":3955},[3946,11652,11653],{"class":3948,"line":4306},[3946,11654,4257],{"emptyLinePlaceholder":4256},[3946,11656,11657,11659,11661,11663,11665,11667,11669,11671,11673,11675],{"class":3948,"line":4312},[3946,11658,4920],{"class":4837},[3946,11660,4061],{"class":3955},[3946,11662,4992],{"class":4818},[3946,11664,4822],{"class":3955},[3946,11666,8087],{"class":3980},[3946,11668,5000],{"class":3955},[3946,11670,5271],{"class":4837},[3946,11672,4851],{"class":3955},[3946,11674,4854],{"class":3959},[3946,11676,4857],{"class":3955},[3946,11678,11679,11681,11683],{"class":3948,"line":4317},[3946,11680,10373],{"class":3959},[3946,11682,8109],{"class":4837},[3946,11684,5560],{"class":3955},[3946,11686,11687,11690],{"class":3948,"line":4323},[3946,11688,11689],{"class":3952},"  try",[3946,11691,4857],{"class":3955},[3946,11693,11694,11697,11699,11701,11703,11705,11707,11709],{"class":3948,"line":4329},[3946,11695,11696],{"class":4837},"    msg",[3946,11698,4812],{"class":3955},[3946,11700,7009],{"class":4808},[3946,11702,4061],{"class":3955},[3946,11704,8118],{"class":4818},[3946,11706,4822],{"class":3955},[3946,11708,5271],{"class":4837},[3946,11710,4828],{"class":3955},[3946,11712,11713,11715,11718,11720,11723],{"class":3948,"line":4335},[3946,11714,5309],{"class":3955},[3946,11716,11717],{"class":3952},"catch",[3946,11719,3823],{"class":3955},[3946,11721,11722],{"class":4837},"e",[3946,11724,5280],{"class":3955},[3946,11726,11727,11729,11731,11733,11735,11737,11739,11741,11743,11745,11748],{"class":3948,"line":4341},[3946,11728,5549],{"class":4837},[3946,11730,4061],{"class":3955},[3946,11732,4902],{"class":4818},[3946,11734,4822],{"class":3955},[3946,11736,7009],{"class":4808},[3946,11738,4061],{"class":3955},[3946,11740,7014],{"class":4818},[3946,11742,4969],{"class":3955},[3946,11744,11372],{"class":4837},[3946,11746,11747],{"class":3980}," 'Невірний JSON'",[3946,11749,9363],{"class":3955},[3946,11751,11752,11754],{"class":3948,"line":4347},[3946,11753,9894],{"class":3952},[3946,11755,5560],{"class":3955},[3946,11757,11758],{"class":3948,"line":4352},[3946,11759,5417],{"class":3955},[3946,11761,11762],{"class":3948,"line":4358},[3946,11763,5922],{"class":3955},[3946,11765,11766,11768,11770,11773,11775,11777],{"class":3948,"line":4364},[3946,11767,5258],{"class":3952},[3946,11769,5929],{"class":3955},[3946,11771,11772],{"class":4818},"validate",[3946,11774,4822],{"class":3955},[3946,11776,8155],{"class":4837},[3946,11778,9093],{"class":3955},[3946,11780,11781,11783,11785,11787,11789,11791,11793,11795,11797,11799,11802,11804,11807,11809,11811,11814],{"class":3948,"line":4369},[3946,11782,5549],{"class":4837},[3946,11784,4061],{"class":3955},[3946,11786,4902],{"class":4818},[3946,11788,4822],{"class":3955},[3946,11790,7009],{"class":4808},[3946,11792,4061],{"class":3955},[3946,11794,7014],{"class":4818},[3946,11796,4969],{"class":3955},[3946,11798,11372],{"class":4837},[3946,11800,11801],{"class":3980}," 'Невалідна структура повідомлення'",[3946,11803,4882],{"class":3955},[3946,11805,11806],{"class":4837},"details:",[3946,11808,11633],{"class":4837},[3946,11810,4061],{"class":3955},[3946,11812,11813],{"class":4837},"errors",[3946,11815,9363],{"class":3955},[3946,11817,11818,11820],{"class":3948,"line":4375},[3946,11819,9894],{"class":3952},[3946,11821,5560],{"class":3955},[3946,11823,11824],{"class":3948,"line":4381},[3946,11825,5417],{"class":3955},[3946,11827,11828],{"class":3948,"line":4387},[3946,11829,5922],{"class":3955},[3946,11831,11832],{"class":3948,"line":4393},[3946,11833,11834],{"class":4891},"  \u002F\u002F Обробка валідного повідомлення\n",[3946,11836,11837],{"class":3948,"line":4398},[3946,11838,5062],{"class":3955},[4469,11840,11841,11848],{},[3775,11842,11843,11844,11847],{},"Ніколи не виконуйте ",[3911,11845,11846],{},"eval()"," або інші небезпечні операції над вхідними даними від клієнта."," Це відкриває вразливість до виконання довільного коду (Remote Code Execution, RCE). Валідуйте типи даних, довжину рядків, формати ідентифікаторів та діапазони числових значень.",[3812,11850,11852],{"id":11851},"інтеграція-websocket-у-серверні-фреймворки","Інтеграція WebSocket у серверні фреймворки",[3817,11854,11855],{},"Більшість сучасних серверних фреймворків надають вбудовану підтримку або спеціалізовані модулі для роботи з WebSocket. Розглянемо інтеграцію у контексті Node.js екосистеми та фреймворку NestJS, що вивчається у даному курсі.",[3836,11857,11859],{"id":11858},"nestjs-websocket-gateway","NestJS WebSocket Gateway",[3817,11861,11862,11863,3823,11866,11869],{},"NestJS пропонує абстракцію високого рівня для роботи з WebSocket через концепцію ",[3775,11864,11865],{},"Gateway",[3752,11867,11868],{},"шлюз","). Gateway є аналогом контролера для WebSocket-з'єднань і дозволяє декларативно оголошувати обробники подій за допомогою декораторів.",[3817,11871,11872],{},"Типова структура WebSocket Gateway у NestJS:",[3937,11874,11878],{"className":11875,"code":11876,"language":11877,"meta":3942,"style":3942},"language-typescript shiki shiki-themes light-plus dark-plus dark-plus","import {\n  WebSocketGateway,\n  WebSocketServer,\n  SubscribeMessage,\n  OnGatewayConnection,\n  OnGatewayDisconnect,\n  MessageBody,\n  ConnectedSocket\n} from '@nestjs\u002Fwebsockets';\nimport { Server, Socket } from 'socket.io';\n\n@WebSocketGateway({\n  cors: {\n    origin: 'https:\u002F\u002Fexample.com',\n    credentials: true\n  }\n})\nexport class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {\n  @WebSocketServer()\n  server: Server;\n\n  \u002F\u002F Викликається при новому підключенні\n  handleConnection(client: Socket) {\n    console.log('Новий клієнт підключився:', client.id);\n    \u002F\u002F Можна перевірити автентифікацію тут\n  }\n\n  \u002F\u002F Викликається при від'єднанні\n  handleDisconnect(client: Socket) {\n    console.log('Клієнт від\\'єднався:', client.id);\n    \u002F\u002F Очищення ресурсів, видалення з кімнат\n  }\n\n  \u002F\u002F Обробник події \"sendMessage\" від клієнта\n  @SubscribeMessage('sendMessage')\n  handleMessage(\n    @MessageBody() data: { roomId: string; text: string },\n    @ConnectedSocket() client: Socket\n  ) {\n    \u002F\u002F Валідація даних\n    if (!data.text || data.text.length > 1000) {\n      return { error: 'Невалідне повідомлення' };\n    }\n\n    \u002F\u002F Транслювання повідомлення всім у кімнаті\n    this.server.to(data.roomId).emit('newMessage', {\n      userId: client.data.userId,\n      text: data.text,\n      timestamp: Date.now()\n    });\n\n    return { success: true };\n  }\n\n  \u002F\u002F Приєднання до кімнати\n  @SubscribeMessage('joinRoom')\n  handleJoinRoom(\n    @MessageBody() roomId: string,\n    @ConnectedSocket() client: Socket\n  ) {\n    client.join(roomId);\n    console.log(`Клієнт ${client.id} приєднався до кімнати ${roomId}`);\n    \n    \u002F\u002F Повідомлення іншим учасникам\n    client.to(roomId).emit('userJoined', {\n      userId: client.data.userId\n    });\n  }\n}\n","typescript",[3911,11879,11880,11887,11894,11901,11908,11915,11922,11929,11934,11947,11970,11974,11984,11991,12001,12009,12013,12018,12042,12052,12064,12068,12073,12089,12112,12117,12121,12125,12130,12145,12173,12178,12182,12186,12191,12205,12213,12247,12263,12268,12273,12304,12317,12322,12327,12333,12365,12384,12398,12412,12418,12423,12438,12443,12448,12454,12468,12476,12493,12508,12513,12530,12568,12573,12579,12603,12619,12624,12629],{"__ignoreMap":3942},[3946,11881,11882,11885],{"class":3948,"line":3949},[3946,11883,11884],{"class":3952},"import",[3946,11886,4857],{"class":3955},[3946,11888,11889,11892],{"class":3948,"line":3970},[3946,11890,11891],{"class":4837},"  WebSocketGateway",[3946,11893,7028],{"class":3955},[3946,11895,11896,11899],{"class":3948,"line":3984},[3946,11897,11898],{"class":4837},"  WebSocketServer",[3946,11900,7028],{"class":3955},[3946,11902,11903,11906],{"class":3948,"line":3995},[3946,11904,11905],{"class":4837},"  SubscribeMessage",[3946,11907,7028],{"class":3955},[3946,11909,11910,11913],{"class":3948,"line":4006},[3946,11911,11912],{"class":4837},"  OnGatewayConnection",[3946,11914,7028],{"class":3955},[3946,11916,11917,11920],{"class":3948,"line":4017},[3946,11918,11919],{"class":4837},"  OnGatewayDisconnect",[3946,11921,7028],{"class":3955},[3946,11923,11924,11927],{"class":3948,"line":4028},[3946,11925,11926],{"class":4837},"  MessageBody",[3946,11928,7028],{"class":3955},[3946,11930,11931],{"class":3948,"line":4265},[3946,11932,11933],{"class":4837},"  ConnectedSocket\n",[3946,11935,11936,11939,11942,11945],{"class":3948,"line":4271},[3946,11937,11938],{"class":3955},"} ",[3946,11940,11941],{"class":3952},"from",[3946,11943,11944],{"class":3980}," '@nestjs\u002Fwebsockets'",[3946,11946,5560],{"class":3955},[3946,11948,11949,11951,11953,11955,11957,11960,11963,11965,11968],{"class":3948,"line":4277},[3946,11950,11884],{"class":3952},[3946,11952,11522],{"class":3955},[3946,11954,4966],{"class":4837},[3946,11956,4882],{"class":3955},[3946,11958,11959],{"class":4837},"Socket",[3946,11961,11962],{"class":3955}," } ",[3946,11964,11941],{"class":3952},[3946,11966,11967],{"class":3980}," 'socket.io'",[3946,11969,5560],{"class":3955},[3946,11971,11972],{"class":3948,"line":4282},[3946,11973,4257],{"emptyLinePlaceholder":4256},[3946,11975,11976,11979,11982],{"class":3948,"line":4288},[3946,11977,11978],{"class":3955},"@",[3946,11980,11981],{"class":4818},"WebSocketGateway",[3946,11983,7017],{"class":3955},[3946,11985,11986,11989],{"class":3948,"line":4294},[3946,11987,11988],{"class":4837},"  cors:",[3946,11990,4857],{"class":3955},[3946,11992,11993,11996,11999],{"class":3948,"line":4300},[3946,11994,11995],{"class":4837},"    origin:",[3946,11997,11998],{"class":3980}," 'https:\u002F\u002Fexample.com'",[3946,12000,7028],{"class":3955},[3946,12002,12003,12006],{"class":3948,"line":4306},[3946,12004,12005],{"class":4837},"    credentials:",[3946,12007,12008],{"class":3959}," true\n",[3946,12010,12011],{"class":3948,"line":4312},[3946,12012,5417],{"class":3955},[3946,12014,12015],{"class":3948,"line":4317},[3946,12016,12017],{"class":3955},"})\n",[3946,12019,12020,12023,12026,12029,12032,12035,12037,12040],{"class":3948,"line":4323},[3946,12021,12022],{"class":3952},"export",[3946,12024,12025],{"class":3959}," class",[3946,12027,12028],{"class":5329}," ChatGateway",[3946,12030,12031],{"class":3959}," implements",[3946,12033,12034],{"class":5329}," OnGatewayConnection",[3946,12036,4882],{"class":3955},[3946,12038,12039],{"class":5329},"OnGatewayDisconnect",[3946,12041,4857],{"class":3955},[3946,12043,12044,12047,12050],{"class":3948,"line":4329},[3946,12045,12046],{"class":3955},"  @",[3946,12048,12049],{"class":4818},"WebSocketServer",[3946,12051,7077],{"class":3955},[3946,12053,12054,12057,12060,12062],{"class":3948,"line":4335},[3946,12055,12056],{"class":4837},"  server",[3946,12058,12059],{"class":3955},": ",[3946,12061,4966],{"class":5329},[3946,12063,5560],{"class":3955},[3946,12065,12066],{"class":3948,"line":4341},[3946,12067,4257],{"emptyLinePlaceholder":4256},[3946,12069,12070],{"class":3948,"line":4347},[3946,12071,12072],{"class":4891},"  \u002F\u002F Викликається при новому підключенні\n",[3946,12074,12075,12078,12080,12083,12085,12087],{"class":3948,"line":4352},[3946,12076,12077],{"class":4818},"  handleConnection",[3946,12079,4822],{"class":3955},[3946,12081,12082],{"class":4837},"client",[3946,12084,12059],{"class":3955},[3946,12086,11959],{"class":5329},[3946,12088,5280],{"class":3955},[3946,12090,12091,12093,12095,12097,12099,12102,12104,12106,12108,12110],{"class":3948,"line":4358},[3946,12092,5285],{"class":4837},[3946,12094,4061],{"class":3955},[3946,12096,4867],{"class":4818},[3946,12098,4822],{"class":3955},[3946,12100,12101],{"class":3980},"'Новий клієнт підключився:'",[3946,12103,4882],{"class":3955},[3946,12105,12082],{"class":4837},[3946,12107,4061],{"class":3955},[3946,12109,7193],{"class":4837},[3946,12111,4828],{"class":3955},[3946,12113,12114],{"class":3948,"line":4364},[3946,12115,12116],{"class":4891},"    \u002F\u002F Можна перевірити автентифікацію тут\n",[3946,12118,12119],{"class":3948,"line":4369},[3946,12120,5417],{"class":3955},[3946,12122,12123],{"class":3948,"line":4375},[3946,12124,4257],{"emptyLinePlaceholder":4256},[3946,12126,12127],{"class":3948,"line":4381},[3946,12128,12129],{"class":4891},"  \u002F\u002F Викликається при від'єднанні\n",[3946,12131,12132,12135,12137,12139,12141,12143],{"class":3948,"line":4387},[3946,12133,12134],{"class":4818},"  handleDisconnect",[3946,12136,4822],{"class":3955},[3946,12138,12082],{"class":4837},[3946,12140,12059],{"class":3955},[3946,12142,11959],{"class":5329},[3946,12144,5280],{"class":3955},[3946,12146,12147,12149,12151,12153,12155,12158,12160,12163,12165,12167,12169,12171],{"class":3948,"line":4393},[3946,12148,5285],{"class":4837},[3946,12150,4061],{"class":3955},[3946,12152,4867],{"class":4818},[3946,12154,4822],{"class":3955},[3946,12156,12157],{"class":3980},"'Клієнт від",[3946,12159,4876],{"class":4875},[3946,12161,12162],{"class":3980},"єднався:'",[3946,12164,4882],{"class":3955},[3946,12166,12082],{"class":4837},[3946,12168,4061],{"class":3955},[3946,12170,7193],{"class":4837},[3946,12172,4828],{"class":3955},[3946,12174,12175],{"class":3948,"line":4398},[3946,12176,12177],{"class":4891},"    \u002F\u002F Очищення ресурсів, видалення з кімнат\n",[3946,12179,12180],{"class":3948,"line":4403},[3946,12181,5417],{"class":3955},[3946,12183,12184],{"class":3948,"line":4409},[3946,12185,4257],{"emptyLinePlaceholder":4256},[3946,12187,12188],{"class":3948,"line":4415},[3946,12189,12190],{"class":4891},"  \u002F\u002F Обробник події \"sendMessage\" від клієнта\n",[3946,12192,12193,12195,12198,12200,12203],{"class":3948,"line":4421},[3946,12194,12046],{"class":3955},[3946,12196,12197],{"class":4818},"SubscribeMessage",[3946,12199,4822],{"class":3955},[3946,12201,12202],{"class":3980},"'sendMessage'",[3946,12204,9581],{"class":3955},[3946,12206,12207,12210],{"class":3948,"line":4427},[3946,12208,12209],{"class":4818},"  handleMessage",[3946,12211,12212],{"class":3955},"(\n",[3946,12214,12215,12218,12221,12224,12226,12229,12232,12234,12237,12239,12241,12243,12245],{"class":3948,"line":4433},[3946,12216,12217],{"class":3955},"    @",[3946,12219,12220],{"class":4818},"MessageBody",[3946,12222,12223],{"class":3955},"() ",[3946,12225,5271],{"class":4837},[3946,12227,12228],{"class":3955},": { ",[3946,12230,12231],{"class":4837},"roomId",[3946,12233,12059],{"class":3955},[3946,12235,12236],{"class":5329},"string",[3946,12238,5666],{"class":3955},[3946,12240,4500],{"class":4837},[3946,12242,12059],{"class":3955},[3946,12244,12236],{"class":5329},[3946,12246,11573],{"class":3955},[3946,12248,12249,12251,12254,12256,12258,12260],{"class":3948,"line":4438},[3946,12250,12217],{"class":3955},[3946,12252,12253],{"class":4818},"ConnectedSocket",[3946,12255,12223],{"class":3955},[3946,12257,12082],{"class":4837},[3946,12259,12059],{"class":3955},[3946,12261,12262],{"class":5329},"Socket\n",[3946,12264,12265],{"class":3948,"line":4443},[3946,12266,12267],{"class":3955},"  ) {\n",[3946,12269,12270],{"class":3948,"line":4449},[3946,12271,12272],{"class":4891},"    \u002F\u002F Валідація даних\n",[3946,12274,12275,12277,12279,12281,12283,12285,12287,12289,12291,12293,12295,12298,12300,12302],{"class":3948,"line":4455},[3946,12276,5502],{"class":3952},[3946,12278,5929],{"class":3955},[3946,12280,5271],{"class":4837},[3946,12282,4061],{"class":3955},[3946,12284,4500],{"class":4837},[3946,12286,10134],{"class":3955},[3946,12288,5271],{"class":4837},[3946,12290,4061],{"class":3955},[3946,12292,4500],{"class":4837},[3946,12294,4061],{"class":3955},[3946,12296,12297],{"class":4837},"length",[3946,12299,11278],{"class":3955},[3946,12301,5735],{"class":3966},[3946,12303,5280],{"class":3955},[3946,12305,12306,12308,12310,12312,12315],{"class":3948,"line":4461},[3946,12307,5528],{"class":3952},[3946,12309,11522],{"class":3955},[3946,12311,11372],{"class":4837},[3946,12313,12314],{"class":3980}," 'Невалідне повідомлення'",[3946,12316,9002],{"class":3955},[3946,12318,12320],{"class":3948,"line":12319},43,[3946,12321,5544],{"class":3955},[3946,12323,12325],{"class":3948,"line":12324},44,[3946,12326,4257],{"emptyLinePlaceholder":4256},[3946,12328,12330],{"class":3948,"line":12329},45,[3946,12331,12332],{"class":4891},"    \u002F\u002F Транслювання повідомлення всім у кімнаті\n",[3946,12334,12336,12338,12340,12342,12344,12346,12348,12350,12352,12354,12356,12358,12360,12363],{"class":3948,"line":12335},46,[3946,12337,8417],{"class":3959},[3946,12339,4061],{"class":3955},[3946,12341,9615],{"class":4837},[3946,12343,4061],{"class":3955},[3946,12345,8160],{"class":4818},[3946,12347,4822],{"class":3955},[3946,12349,5271],{"class":4837},[3946,12351,4061],{"class":3955},[3946,12353,12231],{"class":4837},[3946,12355,3834],{"class":3955},[3946,12357,7410],{"class":4818},[3946,12359,4822],{"class":3955},[3946,12361,12362],{"class":3980},"'newMessage'",[3946,12364,5864],{"class":3955},[3946,12366,12368,12371,12374,12376,12378,12380,12382],{"class":3948,"line":12367},47,[3946,12369,12370],{"class":4837},"      userId:",[3946,12372,12373],{"class":4837}," client",[3946,12375,4061],{"class":3955},[3946,12377,5271],{"class":4837},[3946,12379,4061],{"class":3955},[3946,12381,9918],{"class":4837},[3946,12383,7028],{"class":3955},[3946,12385,12387,12390,12392,12394,12396],{"class":3948,"line":12386},48,[3946,12388,12389],{"class":4837},"      text:",[3946,12391,6379],{"class":4837},[3946,12393,4061],{"class":3955},[3946,12395,4500],{"class":4837},[3946,12397,7028],{"class":3955},[3946,12399,12401,12404,12406,12408,12410],{"class":3948,"line":12400},49,[3946,12402,12403],{"class":4837},"      timestamp:",[3946,12405,7069],{"class":4837},[3946,12407,4061],{"class":3955},[3946,12409,7074],{"class":4818},[3946,12411,7077],{"class":3955},[3946,12413,12415],{"class":3948,"line":12414},50,[3946,12416,12417],{"class":3955},"    });\n",[3946,12419,12421],{"class":3948,"line":12420},51,[3946,12422,4257],{"emptyLinePlaceholder":4256},[3946,12424,12426,12428,12430,12433,12436],{"class":3948,"line":12425},52,[3946,12427,9894],{"class":3952},[3946,12429,11522],{"class":3955},[3946,12431,12432],{"class":4837},"success:",[3946,12434,12435],{"class":3959}," true",[3946,12437,9002],{"class":3955},[3946,12439,12441],{"class":3948,"line":12440},53,[3946,12442,5417],{"class":3955},[3946,12444,12446],{"class":3948,"line":12445},54,[3946,12447,4257],{"emptyLinePlaceholder":4256},[3946,12449,12451],{"class":3948,"line":12450},55,[3946,12452,12453],{"class":4891},"  \u002F\u002F Приєднання до кімнати\n",[3946,12455,12457,12459,12461,12463,12466],{"class":3948,"line":12456},56,[3946,12458,12046],{"class":3955},[3946,12460,12197],{"class":4818},[3946,12462,4822],{"class":3955},[3946,12464,12465],{"class":3980},"'joinRoom'",[3946,12467,9581],{"class":3955},[3946,12469,12471,12474],{"class":3948,"line":12470},57,[3946,12472,12473],{"class":4818},"  handleJoinRoom",[3946,12475,12212],{"class":3955},[3946,12477,12479,12481,12483,12485,12487,12489,12491],{"class":3948,"line":12478},58,[3946,12480,12217],{"class":3955},[3946,12482,12220],{"class":4818},[3946,12484,12223],{"class":3955},[3946,12486,12231],{"class":4837},[3946,12488,12059],{"class":3955},[3946,12490,12236],{"class":5329},[3946,12492,7028],{"class":3955},[3946,12494,12496,12498,12500,12502,12504,12506],{"class":3948,"line":12495},59,[3946,12497,12217],{"class":3955},[3946,12499,12253],{"class":4818},[3946,12501,12223],{"class":3955},[3946,12503,12082],{"class":4837},[3946,12505,12059],{"class":3955},[3946,12507,12262],{"class":5329},[3946,12509,12511],{"class":3948,"line":12510},60,[3946,12512,12267],{"class":3955},[3946,12514,12516,12519,12521,12524,12526,12528],{"class":3948,"line":12515},61,[3946,12517,12518],{"class":4837},"    client",[3946,12520,4061],{"class":3955},[3946,12522,12523],{"class":4818},"join",[3946,12525,4822],{"class":3955},[3946,12527,12231],{"class":4837},[3946,12529,4828],{"class":3955},[3946,12531,12533,12535,12537,12539,12541,12544,12546,12548,12551,12553,12555,12558,12560,12562,12564,12566],{"class":3948,"line":12532},62,[3946,12534,5285],{"class":4837},[3946,12536,4061],{"class":3955},[3946,12538,4867],{"class":4818},[3946,12540,4822],{"class":3955},[3946,12542,12543],{"class":3980},"`Клієнт ",[3946,12545,9761],{"class":3959},[3946,12547,12082],{"class":4837},[3946,12549,4061],{"class":12550},"sD7JJ",[3946,12552,7193],{"class":4837},[3946,12554,9767],{"class":3959},[3946,12556,12557],{"class":3980}," приєднався до кімнати ",[3946,12559,9761],{"class":3959},[3946,12561,12231],{"class":4837},[3946,12563,9767],{"class":3959},[3946,12565,9770],{"class":3980},[3946,12567,4828],{"class":3955},[3946,12569,12571],{"class":3948,"line":12570},63,[3946,12572,8519],{"class":3955},[3946,12574,12576],{"class":3948,"line":12575},64,[3946,12577,12578],{"class":4891},"    \u002F\u002F Повідомлення іншим учасникам\n",[3946,12580,12582,12584,12586,12588,12590,12592,12594,12596,12598,12601],{"class":3948,"line":12581},65,[3946,12583,12518],{"class":4837},[3946,12585,4061],{"class":3955},[3946,12587,8160],{"class":4818},[3946,12589,4822],{"class":3955},[3946,12591,12231],{"class":4837},[3946,12593,3834],{"class":3955},[3946,12595,7410],{"class":4818},[3946,12597,4822],{"class":3955},[3946,12599,12600],{"class":3980},"'userJoined'",[3946,12602,5864],{"class":3955},[3946,12604,12606,12608,12610,12612,12614,12616],{"class":3948,"line":12605},66,[3946,12607,12370],{"class":4837},[3946,12609,12373],{"class":4837},[3946,12611,4061],{"class":3955},[3946,12613,5271],{"class":4837},[3946,12615,4061],{"class":3955},[3946,12617,12618],{"class":4837},"userId\n",[3946,12620,12622],{"class":3948,"line":12621},67,[3946,12623,12417],{"class":3955},[3946,12625,12627],{"class":3948,"line":12626},68,[3946,12628,5417],{"class":3955},[3946,12630,12632],{"class":3948,"line":12631},69,[3946,12633,6483],{"class":3955},[3817,12635,12636],{},[3775,12637,12638],{},"Ключові декоратори:",[3742,12640,12641,12649,12657,12665,12673],{},[3745,12642,12643,12648],{},[3775,12644,12645,3977],{},[3911,12646,12647],{},"@WebSocketGateway()"," оголошує клас як WebSocket-шлюз з конфігурацією (порт, CORS, namespace).",[3745,12650,12651,12656],{},[3775,12652,12653,3977],{},[3911,12654,12655],{},"@WebSocketServer()"," ін'єктує екземпляр Socket.IO Server для ручного керування.",[3745,12658,12659,12664],{},[3775,12660,12661,3977],{},[3911,12662,12663],{},"@SubscribeMessage('eventName')"," реєструє обробник для події від клієнта.",[3745,12666,12667,12672],{},[3775,12668,12669,3977],{},[3911,12670,12671],{},"@MessageBody()"," витягує корисне навантаження повідомлення.",[3745,12674,12675,12680],{},[3775,12676,12677,3977],{},[3911,12678,12679],{},"@ConnectedSocket()"," надає доступ до об'єкта сокета конкретного клієнта.",[3836,12682,12684],{"id":12683},"автентифікація-у-nestjs-websocket","Автентифікація у NestJS WebSocket",[3817,12686,12687,12688,3823,12691,12694],{},"Для захисту WebSocket-з'єднань у NestJS застосовують ",[3775,12689,12690],{},"Guards",[3752,12692,12693],{},"гварди",") — механізм авторизації, що перевіряє токени або сесії перед допуском до обробників:",[3937,12696,12698],{"className":11875,"code":12697,"language":11877,"meta":3942,"style":3942},"import { CanActivate, ExecutionContext, Injectable } from '@nestjs\u002Fcommon';\nimport { JwtService } from '@nestjs\u002Fjwt';\nimport { WsException } from '@nestjs\u002Fwebsockets';\n\n@Injectable()\nexport class WsAuthGuard implements CanActivate {\n  constructor(private jwtService: JwtService) {}\n\n  canActivate(context: ExecutionContext): boolean {\n    const client = context.switchToWs().getClient();\n    const token = client.handshake.auth.token || client.handshake.headers.authorization?.split(' ')[1];\n\n    if (!token) {\n      throw new WsException('Токен автентифікації відсутній');\n    }\n\n    try {\n      const payload = this.jwtService.verify(token);\n      client.data.userId = payload.sub; \u002F\u002F Зберігаємо userId у сокеті\n      return true;\n    } catch (e) {\n      throw new WsException('Невалідний токен');\n    }\n  }\n}\n",[3911,12699,12700,12728,12746,12763,12767,12775,12791,12810,12814,12836,12858,12916,12920,12930,12948,12952,12956,12963,12991,13018,13026,13038,13053,13057,13061],{"__ignoreMap":3942},[3946,12701,12702,12704,12706,12709,12711,12714,12716,12719,12721,12723,12726],{"class":3948,"line":3949},[3946,12703,11884],{"class":3952},[3946,12705,11522],{"class":3955},[3946,12707,12708],{"class":4837},"CanActivate",[3946,12710,4882],{"class":3955},[3946,12712,12713],{"class":4837},"ExecutionContext",[3946,12715,4882],{"class":3955},[3946,12717,12718],{"class":4837},"Injectable",[3946,12720,11962],{"class":3955},[3946,12722,11941],{"class":3952},[3946,12724,12725],{"class":3980}," '@nestjs\u002Fcommon'",[3946,12727,5560],{"class":3955},[3946,12729,12730,12732,12734,12737,12739,12741,12744],{"class":3948,"line":3970},[3946,12731,11884],{"class":3952},[3946,12733,11522],{"class":3955},[3946,12735,12736],{"class":4837},"JwtService",[3946,12738,11962],{"class":3955},[3946,12740,11941],{"class":3952},[3946,12742,12743],{"class":3980}," '@nestjs\u002Fjwt'",[3946,12745,5560],{"class":3955},[3946,12747,12748,12750,12752,12755,12757,12759,12761],{"class":3948,"line":3984},[3946,12749,11884],{"class":3952},[3946,12751,11522],{"class":3955},[3946,12753,12754],{"class":4837},"WsException",[3946,12756,11962],{"class":3955},[3946,12758,11941],{"class":3952},[3946,12760,11944],{"class":3980},[3946,12762,5560],{"class":3955},[3946,12764,12765],{"class":3948,"line":3995},[3946,12766,4257],{"emptyLinePlaceholder":4256},[3946,12768,12769,12771,12773],{"class":3948,"line":4006},[3946,12770,11978],{"class":3955},[3946,12772,12718],{"class":4818},[3946,12774,7077],{"class":3955},[3946,12776,12777,12779,12781,12784,12786,12789],{"class":3948,"line":4017},[3946,12778,12022],{"class":3952},[3946,12780,12025],{"class":3959},[3946,12782,12783],{"class":5329}," WsAuthGuard",[3946,12785,12031],{"class":3959},[3946,12787,12788],{"class":5329}," CanActivate",[3946,12790,4857],{"class":3955},[3946,12792,12793,12795,12797,12800,12803,12805,12807],{"class":3948,"line":4028},[3946,12794,8405],{"class":3959},[3946,12796,4822],{"class":3955},[3946,12798,12799],{"class":3959},"private",[3946,12801,12802],{"class":4837}," jwtService",[3946,12804,12059],{"class":3955},[3946,12806,12736],{"class":5329},[3946,12808,12809],{"class":3955},") {}\n",[3946,12811,12812],{"class":3948,"line":4265},[3946,12813,4257],{"emptyLinePlaceholder":4256},[3946,12815,12816,12819,12821,12824,12826,12828,12831,12834],{"class":3948,"line":4271},[3946,12817,12818],{"class":4818},"  canActivate",[3946,12820,4822],{"class":3955},[3946,12822,12823],{"class":4837},"context",[3946,12825,12059],{"class":3955},[3946,12827,12713],{"class":5329},[3946,12829,12830],{"class":3955},"): ",[3946,12832,12833],{"class":5329},"boolean",[3946,12835,4857],{"class":3955},[3946,12837,12838,12840,12842,12844,12846,12848,12851,12853,12856],{"class":3948,"line":4277},[3946,12839,10481],{"class":3959},[3946,12841,12373],{"class":4808},[3946,12843,4812],{"class":3955},[3946,12845,12823],{"class":4837},[3946,12847,4061],{"class":3955},[3946,12849,12850],{"class":4818},"switchToWs",[3946,12852,7167],{"class":3955},[3946,12854,12855],{"class":4818},"getClient",[3946,12857,5539],{"class":3955},[3946,12859,12860,12862,12864,12866,12868,12870,12872,12874,12877,12879,12881,12883,12885,12887,12889,12891,12893,12895,12898,12901,12904,12906,12909,12912,12914],{"class":3948,"line":4282},[3946,12861,10481],{"class":3959},[3946,12863,9731],{"class":4808},[3946,12865,4812],{"class":3955},[3946,12867,12082],{"class":4837},[3946,12869,4061],{"class":3955},[3946,12871,3754],{"class":4837},[3946,12873,4061],{"class":3955},[3946,12875,12876],{"class":4837},"auth",[3946,12878,4061],{"class":3955},[3946,12880,9764],{"class":4837},[3946,12882,10134],{"class":3955},[3946,12884,12082],{"class":4837},[3946,12886,4061],{"class":3955},[3946,12888,3754],{"class":4837},[3946,12890,4061],{"class":3955},[3946,12892,10127],{"class":4837},[3946,12894,4061],{"class":3955},[3946,12896,12897],{"class":4837},"authorization",[3946,12899,12900],{"class":3955},"?.",[3946,12902,12903],{"class":4818},"split",[3946,12905,4822],{"class":3955},[3946,12907,12908],{"class":3980},"' '",[3946,12910,12911],{"class":3955},")[",[3946,12913,11068],{"class":3966},[3946,12915,10159],{"class":3955},[3946,12917,12918],{"class":3948,"line":4288},[3946,12919,4257],{"emptyLinePlaceholder":4256},[3946,12921,12922,12924,12926,12928],{"class":3948,"line":4294},[3946,12923,5502],{"class":3952},[3946,12925,5929],{"class":3955},[3946,12927,9764],{"class":4837},[3946,12929,5280],{"class":3955},[3946,12931,12932,12935,12938,12941,12943,12946],{"class":3948,"line":4300},[3946,12933,12934],{"class":3952},"      throw",[3946,12936,12937],{"class":3959}," new",[3946,12939,12940],{"class":4818}," WsException",[3946,12942,4822],{"class":3955},[3946,12944,12945],{"class":3980},"'Токен автентифікації відсутній'",[3946,12947,4828],{"class":3955},[3946,12949,12950],{"class":3948,"line":4306},[3946,12951,5544],{"class":3955},[3946,12953,12954],{"class":3948,"line":4312},[3946,12955,4257],{"emptyLinePlaceholder":4256},[3946,12957,12958,12961],{"class":3948,"line":4317},[3946,12959,12960],{"class":3952},"    try",[3946,12962,4857],{"class":3955},[3946,12964,12965,12968,12971,12973,12975,12977,12980,12982,12985,12987,12989],{"class":3948,"line":4323},[3946,12966,12967],{"class":3959},"      const",[3946,12969,12970],{"class":4808}," payload",[3946,12972,4812],{"class":3955},[3946,12974,8508],{"class":3959},[3946,12976,4061],{"class":3955},[3946,12978,12979],{"class":4837},"jwtService",[3946,12981,4061],{"class":3955},[3946,12983,12984],{"class":4818},"verify",[3946,12986,4822],{"class":3955},[3946,12988,9764],{"class":4837},[3946,12990,4828],{"class":3955},[3946,12992,12993,12996,12998,13000,13002,13004,13006,13009,13011,13013,13015],{"class":3948,"line":4329},[3946,12994,12995],{"class":4837},"      client",[3946,12997,4061],{"class":3955},[3946,12999,5271],{"class":4837},[3946,13001,4061],{"class":3955},[3946,13003,9918],{"class":4837},[3946,13005,4812],{"class":3955},[3946,13007,13008],{"class":4837},"payload",[3946,13010,4061],{"class":3955},[3946,13012,8058],{"class":4837},[3946,13014,5666],{"class":3955},[3946,13016,13017],{"class":4891},"\u002F\u002F Зберігаємо userId у сокеті\n",[3946,13019,13020,13022,13024],{"class":3948,"line":4335},[3946,13021,5528],{"class":3952},[3946,13023,12435],{"class":3959},[3946,13025,5560],{"class":3955},[3946,13027,13028,13030,13032,13034,13036],{"class":3948,"line":4341},[3946,13029,8858],{"class":3955},[3946,13031,11717],{"class":3952},[3946,13033,3823],{"class":3955},[3946,13035,11722],{"class":4837},[3946,13037,5280],{"class":3955},[3946,13039,13040,13042,13044,13046,13048,13051],{"class":3948,"line":4347},[3946,13041,12934],{"class":3952},[3946,13043,12937],{"class":3959},[3946,13045,12940],{"class":4818},[3946,13047,4822],{"class":3955},[3946,13049,13050],{"class":3980},"'Невалідний токен'",[3946,13052,4828],{"class":3955},[3946,13054,13055],{"class":3948,"line":4352},[3946,13056,5544],{"class":3955},[3946,13058,13059],{"class":3948,"line":4358},[3946,13060,5417],{"class":3955},[3946,13062,13063],{"class":3948,"line":4364},[3946,13064,6483],{"class":3955},[3817,13066,13067],{},"Застосування гварда до Gateway:",[3937,13069,13071],{"className":11875,"code":13070,"language":11877,"meta":3942,"style":3942},"@WebSocketGateway()\n@UseGuards(WsAuthGuard)\nexport class ChatGateway {\n  \u002F\u002F Всі обробники захищені автентифікацією\n}\n",[3911,13072,13073,13081,13095,13105,13110],{"__ignoreMap":3942},[3946,13074,13075,13077,13079],{"class":3948,"line":3949},[3946,13076,11978],{"class":3955},[3946,13078,11981],{"class":4818},[3946,13080,7077],{"class":3955},[3946,13082,13083,13085,13088,13090,13093],{"class":3948,"line":3970},[3946,13084,11978],{"class":3955},[3946,13086,13087],{"class":4818},"UseGuards",[3946,13089,4822],{"class":3955},[3946,13091,13092],{"class":4837},"WsAuthGuard",[3946,13094,9581],{"class":3955},[3946,13096,13097,13099,13101,13103],{"class":3948,"line":3984},[3946,13098,12022],{"class":3952},[3946,13100,12025],{"class":3959},[3946,13102,12028],{"class":5329},[3946,13104,4857],{"class":3955},[3946,13106,13107],{"class":3948,"line":3995},[3946,13108,13109],{"class":4891},"  \u002F\u002F Всі обробники захищені автентифікацією\n",[3946,13111,13112],{"class":3948,"line":4006},[3946,13113,6483],{"class":3955},[3817,13115,13116],{},"Клієнт передає токен при підключенні:",[3937,13118,13120],{"className":4796,"code":13119,"language":4798,"meta":3942,"style":3942},"import { io } from 'socket.io-client';\n\nconst socket = io('wss:\u002F\u002Fapi.example.com', {\n  auth: {\n    token: localStorage.getItem('access_token')\n  }\n});\n",[3911,13121,13122,13140,13144,13160,13167,13187,13191],{"__ignoreMap":3942},[3946,13123,13124,13126,13128,13131,13133,13135,13138],{"class":3948,"line":3949},[3946,13125,11884],{"class":3952},[3946,13127,11522],{"class":3955},[3946,13129,13130],{"class":4837},"io",[3946,13132,11962],{"class":3955},[3946,13134,11941],{"class":3952},[3946,13136,13137],{"class":3980}," 'socket.io-client'",[3946,13139,5560],{"class":3955},[3946,13141,13142],{"class":3948,"line":3970},[3946,13143,4257],{"emptyLinePlaceholder":4256},[3946,13145,13146,13148,13150,13152,13154,13156,13158],{"class":3948,"line":3984},[3946,13147,4805],{"class":3959},[3946,13149,4809],{"class":4808},[3946,13151,4812],{"class":3955},[3946,13153,13130],{"class":4818},[3946,13155,4822],{"class":3955},[3946,13157,9832],{"class":3980},[3946,13159,5864],{"class":3955},[3946,13161,13162,13165],{"class":3948,"line":3995},[3946,13163,13164],{"class":4837},"  auth:",[3946,13166,4857],{"class":3955},[3946,13168,13169,13172,13175,13177,13180,13182,13185],{"class":3948,"line":4006},[3946,13170,13171],{"class":4837},"    token:",[3946,13173,13174],{"class":4837}," localStorage",[3946,13176,4061],{"class":3955},[3946,13178,13179],{"class":4818},"getItem",[3946,13181,4822],{"class":3955},[3946,13183,13184],{"class":3980},"'access_token'",[3946,13186,9581],{"class":3955},[3946,13188,13189],{"class":3948,"line":4017},[3946,13190,5417],{"class":3955},[3946,13192,13193],{"class":3948,"line":4028},[3946,13194,5062],{"class":3955},[3836,13196,13198],{"id":13197},"socketio-vs-нативний-websocket","Socket.IO vs нативний WebSocket",[3817,13200,13201,13202,13205],{},"NestJS за замовчуванням використовує бібліотеку ",[3775,13203,13204],{},"Socket.IO"," замість нативного WebSocket API. Socket.IO надає додаткові можливості:",[3742,13207,13208,13214,13220,13226],{},[3745,13209,13210,13213],{},[3775,13211,13212],{},"Автоматична реконнект:"," клієнт автоматично перепідключається при обриві з'єднання.",[3745,13215,13216,13219],{},[3775,13217,13218],{},"Rooms і Namespaces:"," логічне групування з'єднань для таргетованого broadcasting.",[3745,13221,13222,13225],{},[3775,13223,13224],{},"Fallback до Long Polling:"," якщо WebSocket недоступний (старі браузери, обмеження корпоративних проксі), Socket.IO автоматично переключається на HTTP Long Polling.",[3745,13227,13228,13231],{},[3775,13229,13230],{},"Acknowledgements:"," підтримка callback-функцій для підтвердження доставки повідомлень.",[3817,13233,13234],{},"Проте Socket.IO має недоліки:",[3742,13236,13237,13243,13253],{},[3745,13238,13239,13242],{},[3775,13240,13241],{},"Більший розмір бібліотеки:"," ~50 КБ (gzip) проти ~5 КБ нативного WebSocket API.",[3745,13244,13245,13248,13249,13252],{},[3775,13246,13247],{},"Несумісність з не-Socket.IO клієнтами:"," не можна підключитися звичайним ",[3911,13250,13251],{},"new WebSocket()"," до Socket.IO-сервера.",[3745,13254,13255,13258],{},[3775,13256,13257],{},"Додатковий overhead:"," Socket.IO додає власні метадані до кожного пакета.",[3817,13260,13261,13262,13265],{},"Для мікросервісної архітектури або високонавантажених систем можна використовувати нативний WebSocket через ",[3911,13263,13264],{},"@nestjs\u002Fplatform-ws"," адаптер:",[3937,13267,13269],{"className":11875,"code":13268,"language":11877,"meta":3942,"style":3942},"\u002F\u002F main.ts\nimport { WsAdapter } from '@nestjs\u002Fplatform-ws';\n\nconst app = await NestFactory.create(AppModule);\napp.useWebSocketAdapter(new WsAdapter(app));\n",[3911,13270,13271,13276,13294,13298,13324],{"__ignoreMap":3942},[3946,13272,13273],{"class":3948,"line":3949},[3946,13274,13275],{"class":4891},"\u002F\u002F main.ts\n",[3946,13277,13278,13280,13282,13285,13287,13289,13292],{"class":3948,"line":3970},[3946,13279,11884],{"class":3952},[3946,13281,11522],{"class":3955},[3946,13283,13284],{"class":4837},"WsAdapter",[3946,13286,11962],{"class":3955},[3946,13288,11941],{"class":3952},[3946,13290,13291],{"class":3980}," '@nestjs\u002Fplatform-ws'",[3946,13293,5560],{"class":3955},[3946,13295,13296],{"class":3948,"line":3984},[3946,13297,4257],{"emptyLinePlaceholder":4256},[3946,13299,13300,13302,13305,13307,13309,13312,13314,13317,13319,13322],{"class":3948,"line":3995},[3946,13301,4805],{"class":3959},[3946,13303,13304],{"class":4808}," app",[3946,13306,4812],{"class":3955},[3946,13308,6414],{"class":3952},[3946,13310,13311],{"class":4837}," NestFactory",[3946,13313,4061],{"class":3955},[3946,13315,13316],{"class":4818},"create",[3946,13318,4822],{"class":3955},[3946,13320,13321],{"class":4837},"AppModule",[3946,13323,4828],{"class":3955},[3946,13325,13326,13329,13331,13334,13336,13338,13341,13343,13345],{"class":3948,"line":4006},[3946,13327,13328],{"class":4837},"app",[3946,13330,4061],{"class":3955},[3946,13332,13333],{"class":4818},"useWebSocketAdapter",[3946,13335,4822],{"class":3955},[3946,13337,4815],{"class":3959},[3946,13339,13340],{"class":4818}," WsAdapter",[3946,13342,4822],{"class":3955},[3946,13344,13328],{"class":4837},[3946,13346,5412],{"class":3955},[3817,13348,13349],{},"Gateway з нативним WebSocket:",[3937,13351,13353],{"className":11875,"code":13352,"language":11877,"meta":3942,"style":3942},"import { WebSocketGateway, WebSocketServer, OnGatewayConnection } from '@nestjs\u002Fwebsockets';\nimport { Server } from 'ws';\n\n@WebSocketGateway({ transports: ['websocket'] })\nexport class NativeWsGateway implements OnGatewayConnection {\n  @WebSocketServer()\n  server: Server;\n\n  handleConnection(client: WebSocket, req: Request) {\n    client.on('message', (data) => {\n      \u002F\u002F Обробка бінарних або текстових даних\n      console.log('Отримано:', data.toString());\n    });\n  }\n}\n",[3911,13354,13355,13380,13397,13401,13420,13435,13443,13453,13457,13480,13502,13507,13531,13535,13539],{"__ignoreMap":3942},[3946,13356,13357,13359,13361,13363,13365,13367,13369,13372,13374,13376,13378],{"class":3948,"line":3949},[3946,13358,11884],{"class":3952},[3946,13360,11522],{"class":3955},[3946,13362,11981],{"class":4837},[3946,13364,4882],{"class":3955},[3946,13366,12049],{"class":4837},[3946,13368,4882],{"class":3955},[3946,13370,13371],{"class":4837},"OnGatewayConnection",[3946,13373,11962],{"class":3955},[3946,13375,11941],{"class":3952},[3946,13377,11944],{"class":3980},[3946,13379,5560],{"class":3955},[3946,13381,13382,13384,13386,13388,13390,13392,13395],{"class":3948,"line":3970},[3946,13383,11884],{"class":3952},[3946,13385,11522],{"class":3955},[3946,13387,4966],{"class":4837},[3946,13389,11962],{"class":3955},[3946,13391,11941],{"class":3952},[3946,13393,13394],{"class":3980}," 'ws'",[3946,13396,5560],{"class":3955},[3946,13398,13399],{"class":3948,"line":3984},[3946,13400,4257],{"emptyLinePlaceholder":4256},[3946,13402,13403,13405,13407,13409,13412,13414,13417],{"class":3948,"line":3995},[3946,13404,11978],{"class":3955},[3946,13406,11981],{"class":4818},[3946,13408,4969],{"class":3955},[3946,13410,13411],{"class":4837},"transports:",[3946,13413,11535],{"class":3955},[3946,13415,13416],{"class":3980},"'websocket'",[3946,13418,13419],{"class":3955},"] })\n",[3946,13421,13422,13424,13426,13429,13431,13433],{"class":3948,"line":4006},[3946,13423,12022],{"class":3952},[3946,13425,12025],{"class":3959},[3946,13427,13428],{"class":5329}," NativeWsGateway",[3946,13430,12031],{"class":3959},[3946,13432,12034],{"class":5329},[3946,13434,4857],{"class":3955},[3946,13436,13437,13439,13441],{"class":3948,"line":4017},[3946,13438,12046],{"class":3955},[3946,13440,12049],{"class":4818},[3946,13442,7077],{"class":3955},[3946,13444,13445,13447,13449,13451],{"class":3948,"line":4028},[3946,13446,12056],{"class":4837},[3946,13448,12059],{"class":3955},[3946,13450,4966],{"class":5329},[3946,13452,5560],{"class":3955},[3946,13454,13455],{"class":3948,"line":4265},[3946,13456,4257],{"emptyLinePlaceholder":4256},[3946,13458,13459,13461,13463,13465,13467,13469,13471,13473,13475,13478],{"class":3948,"line":4271},[3946,13460,12077],{"class":4818},[3946,13462,4822],{"class":3955},[3946,13464,12082],{"class":4837},[3946,13466,12059],{"class":3955},[3946,13468,3884],{"class":5329},[3946,13470,4882],{"class":3955},[3946,13472,5007],{"class":4837},[3946,13474,12059],{"class":3955},[3946,13476,13477],{"class":5329},"Request",[3946,13479,5280],{"class":3955},[3946,13481,13482,13484,13486,13488,13490,13492,13494,13496,13498,13500],{"class":3948,"line":4277},[3946,13483,12518],{"class":4837},[3946,13485,4061],{"class":3955},[3946,13487,4992],{"class":4818},[3946,13489,4822],{"class":3955},[3946,13491,8087],{"class":3980},[3946,13493,5000],{"class":3955},[3946,13495,5271],{"class":4837},[3946,13497,4851],{"class":3955},[3946,13499,4854],{"class":3959},[3946,13501,4857],{"class":3955},[3946,13503,13504],{"class":3948,"line":4282},[3946,13505,13506],{"class":4891},"      \u002F\u002F Обробка бінарних або текстових даних\n",[3946,13508,13509,13511,13513,13515,13517,13520,13522,13524,13526,13529],{"class":3948,"line":4288},[3946,13510,8543],{"class":4837},[3946,13512,4061],{"class":3955},[3946,13514,4867],{"class":4818},[3946,13516,4822],{"class":3955},[3946,13518,13519],{"class":3980},"'Отримано:'",[3946,13521,4882],{"class":3955},[3946,13523,5271],{"class":4837},[3946,13525,4061],{"class":3955},[3946,13527,13528],{"class":4818},"toString",[3946,13530,7561],{"class":3955},[3946,13532,13533],{"class":3948,"line":4294},[3946,13534,12417],{"class":3955},[3946,13536,13537],{"class":3948,"line":4300},[3946,13538,5417],{"class":3955},[3946,13540,13541],{"class":3948,"line":4306},[3946,13542,6483],{"class":3955},[5423,13544,13545,13546,13548],{},"Якщо ваш застосунок вимагає лише базової функціональності WebSocket без rooms\u002Fnamespaces і ви не потребуєте підтримки Internet Explorer 10, використовуйте нативний WebSocket через ",[3911,13547,13264],{}," для зменшення overhead та спрощення архітектури.",[3812,13550,13552],{"id":13551},"практичні-рекомендації-та-best-practices","Практичні рекомендації та best practices",[3817,13554,13555],{},"На завершення узагальнімо рекомендації для проєктування надійних та ефективних WebSocket-застосунків.",[3836,13557,13559],{"id":13558},"структурування-повідомлень","Структурування повідомлень",[3817,13561,13562,13563,13565],{},"Визначте чіткий формат структури повідомлень з обов'язковим полем ",[3911,13564,9205],{}," для розрізнення типів подій:",[3937,13567,13569],{"className":11875,"code":13568,"language":11877,"meta":3942,"style":3942},"\u002F\u002F Типи повідомлень клієнт → сервер\ntype ClientMessage =\n  | { type: 'chat.send'; roomId: string; text: string }\n  | { type: 'chat.typing'; roomId: string; isTyping: boolean }\n  | { type: 'room.join'; roomId: string }\n  | { type: 'room.leave'; roomId: string };\n\n\u002F\u002F Типи повідомлень сервер → клієнт\ntype ServerMessage =\n  | { type: 'chat.message'; roomId: string; userId: string; text: string; timestamp: number }\n  | { type: 'chat.typing'; roomId: string; userId: string; isTyping: boolean }\n  | { type: 'room.userJoined'; roomId: string; userId: string }\n  | { type: 'error'; code: string; message: string };\n",[3911,13570,13571,13576,13586,13616,13646,13667,13688,13692,13697,13706,13752,13788,13817],{"__ignoreMap":3942},[3946,13572,13573],{"class":3948,"line":3949},[3946,13574,13575],{"class":4891},"\u002F\u002F Типи повідомлень клієнт → сервер\n",[3946,13577,13578,13580,13583],{"class":3948,"line":3970},[3946,13579,9205],{"class":3959},[3946,13581,13582],{"class":5329}," ClientMessage",[3946,13584,13585],{"class":3955}," =\n",[3946,13587,13588,13591,13593,13595,13598,13600,13602,13604,13606,13608,13610,13612,13614],{"class":3948,"line":3984},[3946,13589,13590],{"class":3955},"  | { ",[3946,13592,9205],{"class":4837},[3946,13594,12059],{"class":3955},[3946,13596,13597],{"class":3980},"'chat.send'",[3946,13599,5666],{"class":3955},[3946,13601,12231],{"class":4837},[3946,13603,12059],{"class":3955},[3946,13605,12236],{"class":5329},[3946,13607,5666],{"class":3955},[3946,13609,4500],{"class":4837},[3946,13611,12059],{"class":3955},[3946,13613,12236],{"class":5329},[3946,13615,11595],{"class":3955},[3946,13617,13618,13620,13622,13624,13627,13629,13631,13633,13635,13637,13640,13642,13644],{"class":3948,"line":3995},[3946,13619,13590],{"class":3955},[3946,13621,9205],{"class":4837},[3946,13623,12059],{"class":3955},[3946,13625,13626],{"class":3980},"'chat.typing'",[3946,13628,5666],{"class":3955},[3946,13630,12231],{"class":4837},[3946,13632,12059],{"class":3955},[3946,13634,12236],{"class":5329},[3946,13636,5666],{"class":3955},[3946,13638,13639],{"class":4837},"isTyping",[3946,13641,12059],{"class":3955},[3946,13643,12833],{"class":5329},[3946,13645,11595],{"class":3955},[3946,13647,13648,13650,13652,13654,13657,13659,13661,13663,13665],{"class":3948,"line":4006},[3946,13649,13590],{"class":3955},[3946,13651,9205],{"class":4837},[3946,13653,12059],{"class":3955},[3946,13655,13656],{"class":3980},"'room.join'",[3946,13658,5666],{"class":3955},[3946,13660,12231],{"class":4837},[3946,13662,12059],{"class":3955},[3946,13664,12236],{"class":5329},[3946,13666,11595],{"class":3955},[3946,13668,13669,13671,13673,13675,13678,13680,13682,13684,13686],{"class":3948,"line":4017},[3946,13670,13590],{"class":3955},[3946,13672,9205],{"class":4837},[3946,13674,12059],{"class":3955},[3946,13676,13677],{"class":3980},"'room.leave'",[3946,13679,5666],{"class":3955},[3946,13681,12231],{"class":4837},[3946,13683,12059],{"class":3955},[3946,13685,12236],{"class":5329},[3946,13687,9002],{"class":3955},[3946,13689,13690],{"class":3948,"line":4028},[3946,13691,4257],{"emptyLinePlaceholder":4256},[3946,13693,13694],{"class":3948,"line":4265},[3946,13695,13696],{"class":4891},"\u002F\u002F Типи повідомлень сервер → клієнт\n",[3946,13698,13699,13701,13704],{"class":3948,"line":4271},[3946,13700,9205],{"class":3959},[3946,13702,13703],{"class":5329}," ServerMessage",[3946,13705,13585],{"class":3955},[3946,13707,13708,13710,13712,13714,13717,13719,13721,13723,13725,13727,13729,13731,13733,13735,13737,13739,13741,13743,13745,13747,13750],{"class":3948,"line":4277},[3946,13709,13590],{"class":3955},[3946,13711,9205],{"class":4837},[3946,13713,12059],{"class":3955},[3946,13715,13716],{"class":3980},"'chat.message'",[3946,13718,5666],{"class":3955},[3946,13720,12231],{"class":4837},[3946,13722,12059],{"class":3955},[3946,13724,12236],{"class":5329},[3946,13726,5666],{"class":3955},[3946,13728,9918],{"class":4837},[3946,13730,12059],{"class":3955},[3946,13732,12236],{"class":5329},[3946,13734,5666],{"class":3955},[3946,13736,4500],{"class":4837},[3946,13738,12059],{"class":3955},[3946,13740,12236],{"class":5329},[3946,13742,5666],{"class":3955},[3946,13744,7553],{"class":4837},[3946,13746,12059],{"class":3955},[3946,13748,13749],{"class":5329},"number",[3946,13751,11595],{"class":3955},[3946,13753,13754,13756,13758,13760,13762,13764,13766,13768,13770,13772,13774,13776,13778,13780,13782,13784,13786],{"class":3948,"line":4282},[3946,13755,13590],{"class":3955},[3946,13757,9205],{"class":4837},[3946,13759,12059],{"class":3955},[3946,13761,13626],{"class":3980},[3946,13763,5666],{"class":3955},[3946,13765,12231],{"class":4837},[3946,13767,12059],{"class":3955},[3946,13769,12236],{"class":5329},[3946,13771,5666],{"class":3955},[3946,13773,9918],{"class":4837},[3946,13775,12059],{"class":3955},[3946,13777,12236],{"class":5329},[3946,13779,5666],{"class":3955},[3946,13781,13639],{"class":4837},[3946,13783,12059],{"class":3955},[3946,13785,12833],{"class":5329},[3946,13787,11595],{"class":3955},[3946,13789,13790,13792,13794,13796,13799,13801,13803,13805,13807,13809,13811,13813,13815],{"class":3948,"line":4288},[3946,13791,13590],{"class":3955},[3946,13793,9205],{"class":4837},[3946,13795,12059],{"class":3955},[3946,13797,13798],{"class":3980},"'room.userJoined'",[3946,13800,5666],{"class":3955},[3946,13802,12231],{"class":4837},[3946,13804,12059],{"class":3955},[3946,13806,12236],{"class":5329},[3946,13808,5666],{"class":3955},[3946,13810,9918],{"class":4837},[3946,13812,12059],{"class":3955},[3946,13814,12236],{"class":5329},[3946,13816,11595],{"class":3955},[3946,13818,13819,13821,13823,13825,13827,13829,13831,13833,13835,13837,13839,13841,13843],{"class":3948,"line":4294},[3946,13820,13590],{"class":3955},[3946,13822,9205],{"class":4837},[3946,13824,12059],{"class":3955},[3946,13826,6055],{"class":3980},[3946,13828,5666],{"class":3955},[3946,13830,3911],{"class":4837},[3946,13832,12059],{"class":3955},[3946,13834,12236],{"class":5329},[3946,13836,5666],{"class":3955},[3946,13838,6092],{"class":4837},[3946,13840,12059],{"class":3955},[3946,13842,12236],{"class":5329},[3946,13844,9002],{"class":3955},[3817,13846,13847],{},"Це дозволяє використовувати TypeScript для безпеки типів та спрощує розширення функціоналу без ламання зворотної сумісності.",[3836,13849,13851],{"id":13850},"версіонування-протоколу","Версіонування протоколу",[3817,13853,13854],{},"Для еволюції API без порушення роботи старих клієнтів додайте поле версії:",[3937,13856,13858],{"className":4796,"code":13857,"language":4798,"meta":3942,"style":3942},"socket.send(JSON.stringify({\n  version: 1,\n  type: 'chat.send',\n  data: { roomId: 'room-123', text: 'Привіт!' }\n}));\n",[3911,13859,13860,13878,13888,13897,13920],{"__ignoreMap":3942},[3946,13861,13862,13864,13866,13868,13870,13872,13874,13876],{"class":3948,"line":3949},[3946,13863,4838],{"class":4837},[3946,13865,4061],{"class":3955},[3946,13867,4902],{"class":4818},[3946,13869,4822],{"class":3955},[3946,13871,7009],{"class":4808},[3946,13873,4061],{"class":3955},[3946,13875,7014],{"class":4818},[3946,13877,7017],{"class":3955},[3946,13879,13880,13883,13886],{"class":3948,"line":3970},[3946,13881,13882],{"class":4837},"  version:",[3946,13884,13885],{"class":3966}," 1",[3946,13887,7028],{"class":3955},[3946,13889,13890,13892,13895],{"class":3948,"line":3984},[3946,13891,7022],{"class":4837},[3946,13893,13894],{"class":3980}," 'chat.send'",[3946,13896,7028],{"class":3955},[3946,13898,13899,13902,13904,13907,13910,13912,13915,13918],{"class":3948,"line":3995},[3946,13900,13901],{"class":4837},"  data:",[3946,13903,11522],{"class":3955},[3946,13905,13906],{"class":4837},"roomId:",[3946,13908,13909],{"class":3980}," 'room-123'",[3946,13911,4882],{"class":3955},[3946,13913,13914],{"class":4837},"text:",[3946,13916,13917],{"class":3980}," 'Привіт!'",[3946,13919,11595],{"class":3955},[3946,13921,13922],{"class":3948,"line":4006},[3946,13923,7082],{"class":3955},[3817,13925,13926],{},"Сервер може підтримувати кілька версій одночасно:",[3937,13928,13930],{"className":4796,"code":13929,"language":4798,"meta":3942,"style":3942},"ws.on('message', (raw) => {\n  const msg = JSON.parse(raw);\n  \n  if (msg.version === 1) {\n    handleV1Message(msg);\n  } else if (msg.version === 2) {\n    handleV2Message(msg);\n  } else {\n    ws.send(JSON.stringify({ error: 'Непідтримувана версія протоколу' }));\n  }\n});\n",[3911,13931,13932,13955,13975,13979,13998,14009,14031,14042,14050,14075,14079],{"__ignoreMap":3942},[3946,13933,13934,13936,13938,13940,13942,13944,13946,13949,13951,13953],{"class":3948,"line":3949},[3946,13935,4920],{"class":4837},[3946,13937,4061],{"class":3955},[3946,13939,4992],{"class":4818},[3946,13941,4822],{"class":3955},[3946,13943,8087],{"class":3980},[3946,13945,5000],{"class":3955},[3946,13947,13948],{"class":4837},"raw",[3946,13950,4851],{"class":3955},[3946,13952,4854],{"class":3959},[3946,13954,4857],{"class":3955},[3946,13956,13957,13959,13961,13963,13965,13967,13969,13971,13973],{"class":3948,"line":3970},[3946,13958,6406],{"class":3959},[3946,13960,8109],{"class":4808},[3946,13962,4812],{"class":3955},[3946,13964,7009],{"class":4808},[3946,13966,4061],{"class":3955},[3946,13968,8118],{"class":4818},[3946,13970,4822],{"class":3955},[3946,13972,13948],{"class":4837},[3946,13974,4828],{"class":3955},[3946,13976,13977],{"class":3948,"line":3984},[3946,13978,5922],{"class":3955},[3946,13980,13981,13983,13985,13987,13989,13992,13994,13996],{"class":3948,"line":3995},[3946,13982,5258],{"class":3952},[3946,13984,3823],{"class":3955},[3946,13986,8155],{"class":4837},[3946,13988,4061],{"class":3955},[3946,13990,13991],{"class":4837},"version",[3946,13993,5274],{"class":3955},[3946,13995,11068],{"class":3966},[3946,13997,5280],{"class":3955},[3946,13999,14000,14003,14005,14007],{"class":3948,"line":4006},[3946,14001,14002],{"class":4818},"    handleV1Message",[3946,14004,4822],{"class":3955},[3946,14006,8155],{"class":4837},[3946,14008,4828],{"class":3955},[3946,14010,14011,14013,14015,14017,14019,14021,14023,14025,14027,14029],{"class":3948,"line":4017},[3946,14012,5309],{"class":3955},[3946,14014,5312],{"class":3952},[3946,14016,5315],{"class":3952},[3946,14018,3823],{"class":3955},[3946,14020,8155],{"class":4837},[3946,14022,4061],{"class":3955},[3946,14024,13991],{"class":4837},[3946,14026,5274],{"class":3955},[3946,14028,8722],{"class":3966},[3946,14030,5280],{"class":3955},[3946,14032,14033,14036,14038,14040],{"class":3948,"line":4028},[3946,14034,14035],{"class":4818},"    handleV2Message",[3946,14037,4822],{"class":3955},[3946,14039,8155],{"class":4837},[3946,14041,4828],{"class":3955},[3946,14043,14044,14046,14048],{"class":3948,"line":4265},[3946,14045,5309],{"class":3955},[3946,14047,5312],{"class":3952},[3946,14049,4857],{"class":3955},[3946,14051,14052,14054,14056,14058,14060,14062,14064,14066,14068,14070,14073],{"class":3948,"line":4271},[3946,14053,5549],{"class":4837},[3946,14055,4061],{"class":3955},[3946,14057,4902],{"class":4818},[3946,14059,4822],{"class":3955},[3946,14061,7009],{"class":4808},[3946,14063,4061],{"class":3955},[3946,14065,7014],{"class":4818},[3946,14067,4969],{"class":3955},[3946,14069,11372],{"class":4837},[3946,14071,14072],{"class":3980}," 'Непідтримувана версія протоколу'",[3946,14074,9363],{"class":3955},[3946,14076,14077],{"class":3948,"line":4277},[3946,14078,5417],{"class":3955},[3946,14080,14081],{"class":3948,"line":4282},[3946,14082,5062],{"class":3955},[3836,14084,14086],{"id":14085},"моніторинг-та-логування","Моніторинг та логування",[3817,14088,14089],{},"Впровадьте детальне логування ключових подій для діагностики проблем:",[3937,14091,14093],{"className":4796,"code":14092,"language":4798,"meta":3942,"style":3942},"wss.on('connection', (ws, req) => {\n  const connectionId = generateId();\n  const ip = req.socket.remoteAddress;\n  const userAgent = req.headers['user-agent'];\n  \n  logger.info('WebSocket connection established', {\n    connectionId,\n    ip,\n    userAgent,\n    timestamp: Date.now()\n  });\n\n  ws.on('message', (data) => {\n    logger.debug('Message received', { connectionId, size: data.length });\n  });\n\n  ws.on('close', (code, reason) => {\n    logger.info('WebSocket connection closed', {\n      connectionId,\n      code,\n      reason: reason.toString(),\n      duration: Date.now() - ws.connectedAt\n    });\n  });\n\n  ws.on('error', (error) => {\n    logger.error('WebSocket error', {\n      connectionId,\n      error: error.message,\n      stack: error.stack\n    });\n  });\n});\n",[3911,14094,14095,14121,14135,14155,14177,14181,14198,14205,14212,14219,14231,14235,14239,14261,14294,14298,14302,14328,14343,14350,14357,14371,14392,14396,14400,14404,14426,14441,14447,14461,14473,14477,14481],{"__ignoreMap":3942},[3946,14096,14097,14099,14101,14103,14105,14107,14109,14111,14113,14115,14117,14119],{"class":3948,"line":3949},[3946,14098,4987],{"class":4837},[3946,14100,4061],{"class":3955},[3946,14102,4992],{"class":4818},[3946,14104,4822],{"class":3955},[3946,14106,4997],{"class":3980},[3946,14108,5000],{"class":3955},[3946,14110,4920],{"class":4837},[3946,14112,4882],{"class":3955},[3946,14114,5007],{"class":4837},[3946,14116,4851],{"class":3955},[3946,14118,4854],{"class":3959},[3946,14120,4857],{"class":3955},[3946,14122,14123,14125,14128,14130,14133],{"class":3948,"line":3970},[3946,14124,6406],{"class":3959},[3946,14126,14127],{"class":4808}," connectionId",[3946,14129,4812],{"class":3955},[3946,14131,14132],{"class":4818},"generateId",[3946,14134,5539],{"class":3955},[3946,14136,14137,14139,14141,14143,14145,14147,14149,14151,14153],{"class":3948,"line":3984},[3946,14138,6406],{"class":3959},[3946,14140,10940],{"class":4808},[3946,14142,4812],{"class":3955},[3946,14144,5007],{"class":4837},[3946,14146,4061],{"class":3955},[3946,14148,4838],{"class":4837},[3946,14150,4061],{"class":3955},[3946,14152,5039],{"class":4837},[3946,14154,5560],{"class":3955},[3946,14156,14157,14159,14162,14164,14166,14168,14170,14172,14175],{"class":3948,"line":3995},[3946,14158,6406],{"class":3959},[3946,14160,14161],{"class":4808}," userAgent",[3946,14163,4812],{"class":3955},[3946,14165,5007],{"class":4837},[3946,14167,4061],{"class":3955},[3946,14169,10127],{"class":4837},[3946,14171,7577],{"class":3955},[3946,14173,14174],{"class":3980},"'user-agent'",[3946,14176,10159],{"class":3955},[3946,14178,14179],{"class":3948,"line":4006},[3946,14180,5922],{"class":3955},[3946,14182,14183,14186,14188,14191,14193,14196],{"class":3948,"line":4017},[3946,14184,14185],{"class":4837},"  logger",[3946,14187,4061],{"class":3955},[3946,14189,14190],{"class":4818},"info",[3946,14192,4822],{"class":3955},[3946,14194,14195],{"class":3980},"'WebSocket connection established'",[3946,14197,5864],{"class":3955},[3946,14199,14200,14203],{"class":3948,"line":4028},[3946,14201,14202],{"class":4837},"    connectionId",[3946,14204,7028],{"class":3955},[3946,14206,14207,14210],{"class":3948,"line":4265},[3946,14208,14209],{"class":4837},"    ip",[3946,14211,7028],{"class":3955},[3946,14213,14214,14217],{"class":3948,"line":4271},[3946,14215,14216],{"class":4837},"    userAgent",[3946,14218,7028],{"class":3955},[3946,14220,14221,14223,14225,14227,14229],{"class":3948,"line":4277},[3946,14222,7422],{"class":4837},[3946,14224,7069],{"class":4837},[3946,14226,4061],{"class":3955},[3946,14228,7074],{"class":4818},[3946,14230,7077],{"class":3955},[3946,14232,14233],{"class":3948,"line":4282},[3946,14234,5580],{"class":3955},[3946,14236,14237],{"class":3948,"line":4288},[3946,14238,4257],{"emptyLinePlaceholder":4256},[3946,14240,14241,14243,14245,14247,14249,14251,14253,14255,14257,14259],{"class":3948,"line":4294},[3946,14242,5046],{"class":4837},[3946,14244,4061],{"class":3955},[3946,14246,4992],{"class":4818},[3946,14248,4822],{"class":3955},[3946,14250,8087],{"class":3980},[3946,14252,5000],{"class":3955},[3946,14254,5271],{"class":4837},[3946,14256,4851],{"class":3955},[3946,14258,4854],{"class":3959},[3946,14260,4857],{"class":3955},[3946,14262,14263,14266,14268,14271,14273,14276,14278,14281,14283,14286,14288,14290,14292],{"class":3948,"line":4300},[3946,14264,14265],{"class":4837},"    logger",[3946,14267,4061],{"class":3955},[3946,14269,14270],{"class":4818},"debug",[3946,14272,4822],{"class":3955},[3946,14274,14275],{"class":3980},"'Message received'",[3946,14277,6365],{"class":3955},[3946,14279,14280],{"class":4837},"connectionId",[3946,14282,4882],{"class":3955},[3946,14284,14285],{"class":4837},"size:",[3946,14287,6379],{"class":4837},[3946,14289,4061],{"class":3955},[3946,14291,12297],{"class":4837},[3946,14293,4978],{"class":3955},[3946,14295,14296],{"class":3948,"line":4306},[3946,14297,5580],{"class":3955},[3946,14299,14300],{"class":3948,"line":4312},[3946,14301,4257],{"emptyLinePlaceholder":4256},[3946,14303,14304,14306,14308,14310,14312,14314,14316,14318,14320,14322,14324,14326],{"class":3948,"line":4317},[3946,14305,5046],{"class":4837},[3946,14307,4061],{"class":3955},[3946,14309,4992],{"class":4818},[3946,14311,4822],{"class":3955},[3946,14313,11087],{"class":3980},[3946,14315,5000],{"class":3955},[3946,14317,3911],{"class":4837},[3946,14319,4882],{"class":3955},[3946,14321,5893],{"class":4837},[3946,14323,4851],{"class":3955},[3946,14325,4854],{"class":3959},[3946,14327,4857],{"class":3955},[3946,14329,14330,14332,14334,14336,14338,14341],{"class":3948,"line":4323},[3946,14331,14265],{"class":4837},[3946,14333,4061],{"class":3955},[3946,14335,14190],{"class":4818},[3946,14337,4822],{"class":3955},[3946,14339,14340],{"class":3980},"'WebSocket connection closed'",[3946,14342,5864],{"class":3955},[3946,14344,14345,14348],{"class":3948,"line":4329},[3946,14346,14347],{"class":4837},"      connectionId",[3946,14349,7028],{"class":3955},[3946,14351,14352,14355],{"class":3948,"line":4335},[3946,14353,14354],{"class":4837},"      code",[3946,14356,7028],{"class":3955},[3946,14358,14359,14362,14365,14367,14369],{"class":3948,"line":4341},[3946,14360,14361],{"class":4837},"      reason:",[3946,14363,14364],{"class":4837}," reason",[3946,14366,4061],{"class":3955},[3946,14368,13528],{"class":4818},[3946,14370,7431],{"class":3955},[3946,14372,14373,14376,14378,14380,14382,14385,14387,14389],{"class":3948,"line":4347},[3946,14374,14375],{"class":4837},"      duration:",[3946,14377,7069],{"class":4837},[3946,14379,4061],{"class":3955},[3946,14381,7074],{"class":4818},[3946,14383,14384],{"class":3955},"() - ",[3946,14386,4920],{"class":4837},[3946,14388,4061],{"class":3955},[3946,14390,14391],{"class":4837},"connectedAt\n",[3946,14393,14394],{"class":3948,"line":4352},[3946,14395,12417],{"class":3955},[3946,14397,14398],{"class":3948,"line":4358},[3946,14399,5580],{"class":3955},[3946,14401,14402],{"class":3948,"line":4364},[3946,14403,4257],{"emptyLinePlaceholder":4256},[3946,14405,14406,14408,14410,14412,14414,14416,14418,14420,14422,14424],{"class":3948,"line":4369},[3946,14407,5046],{"class":4837},[3946,14409,4061],{"class":3955},[3946,14411,4992],{"class":4818},[3946,14413,4822],{"class":3955},[3946,14415,6055],{"class":3980},[3946,14417,5000],{"class":3955},[3946,14419,5946],{"class":4837},[3946,14421,4851],{"class":3955},[3946,14423,4854],{"class":3959},[3946,14425,4857],{"class":3955},[3946,14427,14428,14430,14432,14434,14436,14439],{"class":3948,"line":4375},[3946,14429,14265],{"class":4837},[3946,14431,4061],{"class":3955},[3946,14433,5946],{"class":4818},[3946,14435,4822],{"class":3955},[3946,14437,14438],{"class":3980},"'WebSocket error'",[3946,14440,5864],{"class":3955},[3946,14442,14443,14445],{"class":3948,"line":4381},[3946,14444,14347],{"class":4837},[3946,14446,7028],{"class":3955},[3946,14448,14449,14452,14455,14457,14459],{"class":3948,"line":4387},[3946,14450,14451],{"class":4837},"      error:",[3946,14453,14454],{"class":4837}," error",[3946,14456,4061],{"class":3955},[3946,14458,6092],{"class":4837},[3946,14460,7028],{"class":3955},[3946,14462,14463,14466,14468,14470],{"class":3948,"line":4393},[3946,14464,14465],{"class":4837},"      stack:",[3946,14467,14454],{"class":4837},[3946,14469,4061],{"class":3955},[3946,14471,14472],{"class":4837},"stack\n",[3946,14474,14475],{"class":3948,"line":4398},[3946,14476,12417],{"class":3955},[3946,14478,14479],{"class":3948,"line":4403},[3946,14480,5580],{"class":3955},[3946,14482,14483],{"class":3948,"line":4409},[3946,14484,5062],{"class":3955},[3817,14486,14487],{},"Інтегруйте метрики (Prometheus, StatsD) для моніторингу:",[3742,14489,14490,14493,14496,14499],{},[3745,14491,14492],{},"Кількість активних з'єднань.",[3745,14494,14495],{},"Частота повідомлень (messages\u002Fsec).",[3745,14497,14498],{},"Середня затримка доставки.",[3745,14500,14501],{},"Частота помилок та закриттих з'єднань.",[3836,14503,14505],{"id":14504},"тестування-websocket","Тестування WebSocket",[3817,14507,14508],{},"Використовуйте бібліотеки для автоматизованого тестування:",[3937,14510,14512],{"className":4796,"code":14511,"language":4798,"meta":3942,"style":3942},"\u002F\u002F E2E-тест з Jest та ws\nconst WebSocket = require('ws');\n\ndescribe('ChatGateway', () => {\n  let ws;\n\n  beforeAll((done) => {\n    ws = new WebSocket('ws:\u002F\u002Flocalhost:3000');\n    ws.on('open', done);\n  });\n\n  afterAll(() => {\n    ws.close();\n  });\n\n  test('повинен отримати повідомлення після відправки', (done) => {\n    ws.on('message', (data) => {\n      const msg = JSON.parse(data);\n      expect(msg.type).toBe('chat.message');\n      expect(msg.text).toBe('Тестове повідомлення');\n      done();\n    });\n\n    ws.send(JSON.stringify({\n      type: 'chat.send',\n      roomId: 'test-room',\n      text: 'Тестове повідомлення'\n    }));\n  });\n});\n",[3911,14513,14514,14519,14535,14539,14555,14563,14567,14583,14599,14618,14622,14626,14637,14647,14651,14655,14675,14697,14717,14741,14764,14771,14775,14779,14797,14806,14816,14823,14828,14832],{"__ignoreMap":3942},[3946,14515,14516],{"class":3948,"line":3949},[3946,14517,14518],{"class":4891},"\u002F\u002F E2E-тест з Jest та ws\n",[3946,14520,14521,14523,14525,14527,14529,14531,14533],{"class":3948,"line":3970},[3946,14522,4805],{"class":3959},[3946,14524,4819],{"class":4808},[3946,14526,4812],{"class":3955},[3946,14528,4941],{"class":4818},[3946,14530,4822],{"class":3955},[3946,14532,4946],{"class":3980},[3946,14534,4828],{"class":3955},[3946,14536,14537],{"class":3948,"line":3984},[3946,14538,4257],{"emptyLinePlaceholder":4256},[3946,14540,14541,14544,14546,14549,14551,14553],{"class":3948,"line":3995},[3946,14542,14543],{"class":4818},"describe",[3946,14545,4822],{"class":3955},[3946,14547,14548],{"class":3980},"'ChatGateway'",[3946,14550,5647],{"class":3955},[3946,14552,4854],{"class":3959},[3946,14554,4857],{"class":3955},[3946,14556,14557,14559,14561],{"class":3948,"line":4006},[3946,14558,10373],{"class":3959},[3946,14560,5531],{"class":4837},[3946,14562,5560],{"class":3955},[3946,14564,14565],{"class":3948,"line":4017},[3946,14566,4257],{"emptyLinePlaceholder":4256},[3946,14568,14569,14572,14574,14577,14579,14581],{"class":3948,"line":4028},[3946,14570,14571],{"class":4818},"  beforeAll",[3946,14573,5489],{"class":3955},[3946,14575,14576],{"class":4837},"done",[3946,14578,4851],{"class":3955},[3946,14580,4854],{"class":3959},[3946,14582,4857],{"class":3955},[3946,14584,14585,14587,14589,14591,14593,14595,14597],{"class":3948,"line":4265},[3946,14586,5549],{"class":4837},[3946,14588,4812],{"class":3955},[3946,14590,4815],{"class":3959},[3946,14592,4819],{"class":4818},[3946,14594,4822],{"class":3955},[3946,14596,9424],{"class":3980},[3946,14598,4828],{"class":3955},[3946,14600,14601,14603,14605,14607,14609,14612,14614,14616],{"class":3948,"line":4271},[3946,14602,5549],{"class":4837},[3946,14604,4061],{"class":3955},[3946,14606,4992],{"class":4818},[3946,14608,4822],{"class":3955},[3946,14610,14611],{"class":3980},"'open'",[3946,14613,4882],{"class":3955},[3946,14615,14576],{"class":4837},[3946,14617,4828],{"class":3955},[3946,14619,14620],{"class":3948,"line":4277},[3946,14621,5580],{"class":3955},[3946,14623,14624],{"class":3948,"line":4282},[3946,14625,4257],{"emptyLinePlaceholder":4256},[3946,14627,14628,14631,14633,14635],{"class":3948,"line":4288},[3946,14629,14630],{"class":4818},"  afterAll",[3946,14632,5467],{"class":3955},[3946,14634,4854],{"class":3959},[3946,14636,4857],{"class":3955},[3946,14638,14639,14641,14643,14645],{"class":3948,"line":4294},[3946,14640,5549],{"class":4837},[3946,14642,4061],{"class":3955},[3946,14644,4589],{"class":4818},[3946,14646,5539],{"class":3955},[3946,14648,14649],{"class":3948,"line":4300},[3946,14650,5580],{"class":3955},[3946,14652,14653],{"class":3948,"line":4306},[3946,14654,4257],{"emptyLinePlaceholder":4256},[3946,14656,14657,14660,14662,14665,14667,14669,14671,14673],{"class":3948,"line":4312},[3946,14658,14659],{"class":4818},"  test",[3946,14661,4822],{"class":3955},[3946,14663,14664],{"class":3980},"'повинен отримати повідомлення після відправки'",[3946,14666,5000],{"class":3955},[3946,14668,14576],{"class":4837},[3946,14670,4851],{"class":3955},[3946,14672,4854],{"class":3959},[3946,14674,4857],{"class":3955},[3946,14676,14677,14679,14681,14683,14685,14687,14689,14691,14693,14695],{"class":3948,"line":4317},[3946,14678,5549],{"class":4837},[3946,14680,4061],{"class":3955},[3946,14682,4992],{"class":4818},[3946,14684,4822],{"class":3955},[3946,14686,8087],{"class":3980},[3946,14688,5000],{"class":3955},[3946,14690,5271],{"class":4837},[3946,14692,4851],{"class":3955},[3946,14694,4854],{"class":3959},[3946,14696,4857],{"class":3955},[3946,14698,14699,14701,14703,14705,14707,14709,14711,14713,14715],{"class":3948,"line":4323},[3946,14700,12967],{"class":3959},[3946,14702,8109],{"class":4808},[3946,14704,4812],{"class":3955},[3946,14706,7009],{"class":4808},[3946,14708,4061],{"class":3955},[3946,14710,8118],{"class":4818},[3946,14712,4822],{"class":3955},[3946,14714,5271],{"class":4837},[3946,14716,4828],{"class":3955},[3946,14718,14719,14722,14724,14726,14728,14730,14732,14735,14737,14739],{"class":3948,"line":4329},[3946,14720,14721],{"class":4818},"      expect",[3946,14723,4822],{"class":3955},[3946,14725,8155],{"class":4837},[3946,14727,4061],{"class":3955},[3946,14729,9205],{"class":4837},[3946,14731,3834],{"class":3955},[3946,14733,14734],{"class":4818},"toBe",[3946,14736,4822],{"class":3955},[3946,14738,13716],{"class":3980},[3946,14740,4828],{"class":3955},[3946,14742,14743,14745,14747,14749,14751,14753,14755,14757,14759,14762],{"class":3948,"line":4335},[3946,14744,14721],{"class":4818},[3946,14746,4822],{"class":3955},[3946,14748,8155],{"class":4837},[3946,14750,4061],{"class":3955},[3946,14752,4500],{"class":4837},[3946,14754,3834],{"class":3955},[3946,14756,14734],{"class":4818},[3946,14758,4822],{"class":3955},[3946,14760,14761],{"class":3980},"'Тестове повідомлення'",[3946,14763,4828],{"class":3955},[3946,14765,14766,14769],{"class":3948,"line":4341},[3946,14767,14768],{"class":4818},"      done",[3946,14770,5539],{"class":3955},[3946,14772,14773],{"class":3948,"line":4347},[3946,14774,12417],{"class":3955},[3946,14776,14777],{"class":3948,"line":4352},[3946,14778,4257],{"emptyLinePlaceholder":4256},[3946,14780,14781,14783,14785,14787,14789,14791,14793,14795],{"class":3948,"line":4358},[3946,14782,5549],{"class":4837},[3946,14784,4061],{"class":3955},[3946,14786,4902],{"class":4818},[3946,14788,4822],{"class":3955},[3946,14790,7009],{"class":4808},[3946,14792,4061],{"class":3955},[3946,14794,7014],{"class":4818},[3946,14796,7017],{"class":3955},[3946,14798,14799,14802,14804],{"class":3948,"line":4364},[3946,14800,14801],{"class":4837},"      type:",[3946,14803,13894],{"class":3980},[3946,14805,7028],{"class":3955},[3946,14807,14808,14811,14814],{"class":3948,"line":4369},[3946,14809,14810],{"class":4837},"      roomId:",[3946,14812,14813],{"class":3980}," 'test-room'",[3946,14815,7028],{"class":3955},[3946,14817,14818,14820],{"class":3948,"line":4375},[3946,14819,12389],{"class":4837},[3946,14821,14822],{"class":3980}," 'Тестове повідомлення'\n",[3946,14824,14825],{"class":3948,"line":4381},[3946,14826,14827],{"class":3955},"    }));\n",[3946,14829,14830],{"class":3948,"line":4387},[3946,14831,5580],{"class":3955},[3946,14833,14834],{"class":3948,"line":4393},[3946,14835,5062],{"class":3955},[3903,14837,14838,14839,14842,14843,14846,14847,6968,14850,14853],{},"Для навантажувального тестування WebSocket використовуйте спеціалізовані інструменти: ",[3775,14840,14841],{},"Artillery"," (підтримує WebSocket та Socket.IO), ",[3775,14844,14845],{},"k6"," (розширення ",[3911,14848,14849],{},"xk6-websockets",[3775,14851,14852],{},"JMeter"," з WebSocket Sampler.",[3812,14855,14857],{"id":14856},"питання-для-самоперевірки","Питання для самоперевірки",[14859,14860,14861,14895,14952,15001,15028,15383,15448],"accordion",{},[14862,14863,14866,14872,14892],"accordion-item",{"icon":14864,"label":14865},"i-lucide-help-circle","❓ Чому протокол HTTP не підходить для застосунків реального часу, таких як онлайн-чати?",[3817,14867,14868,14869,14871],{},"Протокол HTTP заснований на парадигмі ",[3775,14870,3822],{},", де кожна взаємодія ініціюється клієнтом. Сервер не може самостійно відправити дані клієнту без попереднього запиту. Для обходу цього обмеження застосовують polling або long polling, що призводить до:",[4169,14873,14874,14880,14886],{},[3745,14875,14876,14879],{},[3775,14877,14878],{},"Надлишкового трафіку:"," кожен HTTP-запит містить 500–1000 байт заголовків, навіть якщо корисне навантаження — лише кілька байт.",[3745,14881,14882,14885],{},[3775,14883,14884],{},"Високої затримки:"," при polling затримка дорівнює інтервалу опитування (1–5 секунд), а при long polling — часу встановлення нового з'єднання після отримання відповіді (200–500 мс).",[3745,14887,14888,14891],{},[3775,14889,14890],{},"Навантаження на сервер:"," тисячі клієнтів генерують постійний потік запитів, навіть коли немає нових даних.",[3817,14893,14894],{},"WebSocket вирішує ці проблеми через встановлення довготривалого повнодуплексного з'єднання з мінімальним overhead (2–14 байт на фрейм) та затримкою 10–50 мс.",[14862,14896,14898,14901,14949],{"icon":14864,"label":14897},"❓ Що відбувається під час процесу WebSocket Handshake і чому він необхідний?",[3817,14899,14900],{},"WebSocket Handshake — це процес рукостискання, що перетворює стандартне HTTP-з'єднання на WebSocket. Він необхідний для забезпечення сумісності з існуючою веб-інфраструктурою (проксі, балансувальники, брандмауери):",[4169,14902,14903,14917,14926,14934,14943],{},[3745,14904,14905,14908,14909,4882,14911,14913,14914,14916],{},[3775,14906,14907],{},"Клієнт відправляє HTTP Upgrade-запит"," з заголовками ",[3911,14910,4046],{},[3911,14912,4057],{}," та ",[3911,14915,4009],{}," (випадковий Base64-ключ).",[3745,14918,14919,14922,14923,14925],{},[3775,14920,14921],{},"Сервер перевіряє заголовки"," та обчислює відповідь ",[3911,14924,4144],{}," шляхом конкатенації ключа клієнта з магічним GUID, обчислення SHA-1 хешу та кодування у Base64.",[3745,14927,14928,14931,14932,4061],{},[3775,14929,14930],{},"Сервер повертає статус 101 Switching Protocols"," з заголовком ",[3911,14933,4144],{},[3745,14935,14936,14939,14940,14942],{},[3775,14937,14938],{},"Клієнт верифікує відповідь"," порівнюючи отриманий ",[3911,14941,4144],{}," з локально обчисленим значенням.",[3745,14944,14945,14948],{},[3775,14946,14947],{},"З'єднання переходить у режим WebSocket",", і всі подальші дані передаються у форматі фреймів, а не HTTP.",[3817,14950,14951],{},"Цей механізм гарантує, що обидві сторони розуміють протокол WebSocket і запобігає випадковому прийняттю WebSocket-з'єднання HTTP-сервером.",[14862,14953,14955,14962,14966,14983,14987,14998],{"icon":14864,"label":14954},"❓ У чому полягає різниця між Text Frame та Binary Frame у WebSocket?",[3817,14956,14957,14958,14961],{},"Фрейми WebSocket мають поле ",[3775,14959,14960],{},"opcode",", що визначає тип корисного навантаження:",[3817,14963,14964],{},[3775,14965,4665],{},[3742,14967,14968,14974,14977,14980],{},[3745,14969,14970,14971,4061],{},"Містить текстові дані у кодуванні ",[3775,14972,14973],{},"UTF-8",[3745,14975,14976],{},"Отримувач має перевірити коректність UTF-8 послідовності.",[3745,14978,14979],{},"Якщо виявлено некоректне кодування, з'єднання має бути закрите з кодом 1007 (Invalid Frame Payload Data).",[3745,14981,14982],{},"Використовується для JSON, XML, простого тексту.",[3817,14984,14985],{},[3775,14986,4671],{},[3742,14988,14989,14992,14995],{},[3745,14990,14991],{},"Містить довільні бінарні дані без обмежень на формат.",[3745,14993,14994],{},"Жодних перевірок кодування не виконується.",[3745,14996,14997],{},"Використовується для зображень, аудіо, серіалізованих об'єктів (Protocol Buffers, MessagePack, FlatBuffers).",[3817,14999,15000],{},"Вибір між типами залежить від природи даних: якщо дані текстові і читабельні людиною (JSON чат-повідомлень), використовуйте Text Frame. Якщо дані бінарні або потребують максимальної ефективності, використовуйте Binary Frame.",[14862,15002,15004,15010,15016,15022],{"icon":14864,"label":15003},"❓ Навіщо потрібне маскування даних у WebSocket і чому воно асиметричне (лише клієнт → сервер)?",[3817,15005,15006,15007,4061],{},"Маскування даних у WebSocket не є шифруванням і не забезпечує конфіденційності — його мета полягає у ",[3775,15008,15009],{},"захисті проксі-серверів від атак cache poisoning",[3817,15011,15012,15015],{},[3775,15013,15014],{},"Проблема:"," деякі старі HTTP-проксі могли кешувати будь-який трафік, що проходить через них, навіть якщо це WebSocket-дані. Зловмисник міг створити WebSocket-з'єднання і відправити спеціально сформовані байти, які проксі інтерпретував як валідну HTTP-відповідь і кешував її. Наступні користувачі, що запитували той самий ресурс, отримували б підроблену відповідь з кешу.",[3817,15017,15018,15021],{},[3775,15019,15020],{},"Рішення:"," RFC 6455 вимагає, щоб всі фрейми від клієнта були замасковані за допомогою випадкового 32-бітного ключа. Кожен байт корисного навантаження XOR-иться з відповідним байтом ключа. Це робить неможливим передбачення точного вмісту пакета і запобігає інтерпретації даних як HTTP.",[3817,15023,15024,15027],{},[3775,15025,15026],{},"Чому асиметрично:"," фрейми від сервера не маскуються, оскільки сервер контролюється адміністратором і не може бути скомпрометований так само легко, як клієнт (браузер). Це також зменшує обчислювальне навантаження на сервер при обробці тисяч з'єднань.",[14862,15029,15031,15043,15364,15369,15380],{"icon":14864,"label":15030},"❓ Як реалізувати автоматичне перепідключення WebSocket у випадку обриву з'єднання?",[3817,15032,15033,15034,15036,15037,3823,15040,15042],{},"Браузерний API ",[3911,15035,3884],{}," не має вбудованого механізму автоматичного перепідключення. Його потрібно реалізувати вручну з ",[3775,15038,15039],{},"експоненційною затримкою",[3752,15041,8385],{},") для запобігання перевантаження сервера при масовому обриві з'єднань:",[3937,15044,15046],{"className":4796,"code":15045,"language":4798,"meta":3942,"style":3942},"class ReconnectingWebSocket {\n  constructor(url) {\n    this.url = url;\n    this.reconnectDelay = 1000; \u002F\u002F Початкова затримка 1 сек\n    this.maxDelay = 30000; \u002F\u002F Максимум 30 сек\n    this.connect();\n  }\n\n  connect() {\n    this.ws = new WebSocket(this.url);\n    \n    this.ws.onopen = () => {\n      console.log('Підключено');\n      this.reconnectDelay = 1000; \u002F\u002F Скидаємо затримку\n    };\n    \n    this.ws.onclose = (event) => {\n      if (!event.wasClean) {\n        console.warn(`Реконнект через ${this.reconnectDelay}мс`);\n        setTimeout(() => this.connect(), this.reconnectDelay);\n        this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxDelay);\n      }\n    };\n  }\n}\n",[3911,15047,15048,15056,15066,15080,15096,15113,15123,15127,15131,15137,15161,15165,15183,15198,15214,15218,15222,15244,15258,15286,15310,15348,15352,15356,15360],{"__ignoreMap":3942},[3946,15049,15050,15052,15054],{"class":3948,"line":3949},[3946,15051,8395],{"class":3959},[3946,15053,8398],{"class":5329},[3946,15055,4857],{"class":3955},[3946,15057,15058,15060,15062,15064],{"class":3948,"line":3970},[3946,15059,8405],{"class":3959},[3946,15061,4822],{"class":3955},[3946,15063,8410],{"class":4837},[3946,15065,5280],{"class":3955},[3946,15067,15068,15070,15072,15074,15076,15078],{"class":3948,"line":3984},[3946,15069,8417],{"class":3959},[3946,15071,4061],{"class":3955},[3946,15073,8410],{"class":4837},[3946,15075,4812],{"class":3955},[3946,15077,8410],{"class":4837},[3946,15079,5560],{"class":3955},[3946,15081,15082,15084,15086,15088,15090,15092,15094],{"class":3948,"line":3995},[3946,15083,8417],{"class":3959},[3946,15085,4061],{"class":3955},[3946,15087,8436],{"class":4837},[3946,15089,4812],{"class":3955},[3946,15091,5735],{"class":3966},[3946,15093,5666],{"class":3955},[3946,15095,8445],{"class":4891},[3946,15097,15098,15100,15102,15105,15107,15109,15111],{"class":3948,"line":4006},[3946,15099,8417],{"class":3959},[3946,15101,4061],{"class":3955},[3946,15103,15104],{"class":4837},"maxDelay",[3946,15106,4812],{"class":3955},[3946,15108,5588],{"class":3966},[3946,15110,5666],{"class":3955},[3946,15112,8463],{"class":4891},[3946,15114,15115,15117,15119,15121],{"class":3948,"line":4017},[3946,15116,8417],{"class":3959},[3946,15118,4061],{"class":3955},[3946,15120,8472],{"class":4818},[3946,15122,5539],{"class":3955},[3946,15124,15125],{"class":3948,"line":4028},[3946,15126,5417],{"class":3955},[3946,15128,15129],{"class":3948,"line":4265},[3946,15130,4257],{"emptyLinePlaceholder":4256},[3946,15132,15133,15135],{"class":3948,"line":4271},[3946,15134,8487],{"class":4818},[3946,15136,6401],{"class":3955},[3946,15138,15139,15141,15143,15145,15147,15149,15151,15153,15155,15157,15159],{"class":3948,"line":4277},[3946,15140,8417],{"class":3959},[3946,15142,4061],{"class":3955},[3946,15144,4920],{"class":4837},[3946,15146,4812],{"class":3955},[3946,15148,4815],{"class":3959},[3946,15150,4819],{"class":4818},[3946,15152,4822],{"class":3955},[3946,15154,8508],{"class":3959},[3946,15156,4061],{"class":3955},[3946,15158,8410],{"class":4837},[3946,15160,4828],{"class":3955},[3946,15162,15163],{"class":3948,"line":4282},[3946,15164,8519],{"class":3955},[3946,15166,15167,15169,15171,15173,15175,15177,15179,15181],{"class":3948,"line":4288},[3946,15168,8417],{"class":3959},[3946,15170,4061],{"class":3955},[3946,15172,4920],{"class":4837},[3946,15174,4061],{"class":3955},[3946,15176,4792],{"class":4818},[3946,15178,8534],{"class":3955},[3946,15180,4854],{"class":3959},[3946,15182,4857],{"class":3955},[3946,15184,15185,15187,15189,15191,15193,15196],{"class":3948,"line":4294},[3946,15186,8543],{"class":4837},[3946,15188,4061],{"class":3955},[3946,15190,4867],{"class":4818},[3946,15192,4822],{"class":3955},[3946,15194,15195],{"class":3980},"'Підключено'",[3946,15197,4828],{"class":3955},[3946,15199,15200,15202,15204,15206,15208,15210,15212],{"class":3948,"line":4300},[3946,15201,8559],{"class":3959},[3946,15203,4061],{"class":3955},[3946,15205,8436],{"class":4837},[3946,15207,4812],{"class":3955},[3946,15209,5735],{"class":3966},[3946,15211,5666],{"class":3955},[3946,15213,8572],{"class":4891},[3946,15215,15216],{"class":3948,"line":4306},[3946,15217,8577],{"class":3955},[3946,15219,15220],{"class":3948,"line":4312},[3946,15221,8519],{"class":3955},[3946,15223,15224,15226,15228,15230,15232,15234,15236,15238,15240,15242],{"class":3948,"line":4317},[3946,15225,8417],{"class":3959},[3946,15227,4061],{"class":3955},[3946,15229,4920],{"class":4837},[3946,15231,4061],{"class":3955},[3946,15233,5820],{"class":4818},[3946,15235,4845],{"class":3955},[3946,15237,4848],{"class":4837},[3946,15239,4851],{"class":3955},[3946,15241,4854],{"class":3959},[3946,15243,4857],{"class":3955},[3946,15245,15246,15248,15250,15252,15254,15256],{"class":3948,"line":4323},[3946,15247,8608],{"class":3952},[3946,15249,5929],{"class":3955},[3946,15251,4848],{"class":4837},[3946,15253,4061],{"class":3955},[3946,15255,5910],{"class":4837},[3946,15257,5280],{"class":3955},[3946,15259,15260,15262,15264,15266,15268,15271,15273,15275,15277,15279,15281,15284],{"class":3948,"line":4329},[3946,15261,8623],{"class":4837},[3946,15263,4061],{"class":3955},[3946,15265,8628],{"class":4818},[3946,15267,4822],{"class":3955},[3946,15269,15270],{"class":3980},"`Реконнект через ",[3946,15272,9761],{"class":3959},[3946,15274,8508],{"class":3959},[3946,15276,4061],{"class":12550},[3946,15278,8436],{"class":4837},[3946,15280,9767],{"class":3959},[3946,15282,15283],{"class":3980},"мс`",[3946,15285,4828],{"class":3955},[3946,15287,15288,15290,15292,15294,15296,15298,15300,15302,15304,15306,15308],{"class":3948,"line":4335},[3946,15289,8657],{"class":4818},[3946,15291,5467],{"class":3955},[3946,15293,4854],{"class":3959},[3946,15295,8664],{"class":3959},[3946,15297,4061],{"class":3955},[3946,15299,8472],{"class":4818},[3946,15301,8671],{"class":3955},[3946,15303,8508],{"class":3959},[3946,15305,4061],{"class":3955},[3946,15307,8436],{"class":4837},[3946,15309,4828],{"class":3955},[3946,15311,15312,15314,15316,15318,15320,15322,15324,15326,15328,15330,15332,15334,15336,15338,15340,15342,15344,15346],{"class":3948,"line":4341},[3946,15313,8694],{"class":3959},[3946,15315,4061],{"class":3955},[3946,15317,8436],{"class":4837},[3946,15319,4812],{"class":3955},[3946,15321,8703],{"class":4837},[3946,15323,4061],{"class":3955},[3946,15325,8708],{"class":4818},[3946,15327,4822],{"class":3955},[3946,15329,8508],{"class":3959},[3946,15331,4061],{"class":3955},[3946,15333,8436],{"class":4837},[3946,15335,8719],{"class":3955},[3946,15337,8722],{"class":3966},[3946,15339,4882],{"class":3955},[3946,15341,8508],{"class":3959},[3946,15343,4061],{"class":3955},[3946,15345,15104],{"class":4837},[3946,15347,4828],{"class":3955},[3946,15349,15350],{"class":3948,"line":4347},[3946,15351,8737],{"class":3955},[3946,15353,15354],{"class":3948,"line":4352},[3946,15355,8577],{"class":3955},[3946,15357,15358],{"class":3948,"line":4358},[3946,15359,5417],{"class":3955},[3946,15361,15362],{"class":3948,"line":4364},[3946,15363,6483],{"class":3955},[3817,15365,15366],{},[3775,15367,15368],{},"Алгоритм:",[4169,15370,15371,15374,15377],{},[3745,15372,15373],{},"При обриві з'єднання чекаємо 1 секунду і намагаємося перепідключитися.",[3745,15375,15376],{},"Якщо не вдалося — чекаємо 2 секунди, потім 4, 8, 16, максимум 30 секунд.",[3745,15378,15379],{},"При успішному з'єднанні скидаємо затримку до початкового значення.",[3817,15381,15382],{},"Це запобігає негайним повторним спробам підключення, що можуть утворити DDoS на власний сервер.",[14862,15384,15386,15392,15397,15415,15420],{"icon":14864,"label":15385},"❓ Як забезпечити масштабування WebSocket-застосунку при горизонтальному розподілі навантаження на кілька серверів?",[3817,15387,15388,15389,15391],{},"WebSocket-з'єднання є ",[3775,15390,7785],{}," — воно прив'язане до конкретного серверного процесу. При горизонтальному масштабуванні виникає проблема: якщо користувач A підключений до сервера S1, а користувач B до сервера S2, як передати повідомлення від A до B?",[3817,15393,15394],{},[3775,15395,15396],{},"Рішення — Pub\u002FSub система (Redis, RabbitMQ, Kafka):",[4169,15398,15399,15402,15409,15412],{},[3745,15400,15401],{},"Кожен серверний процес підписується на спільний канал повідомлень (наприклад, Redis Pub\u002FSub).",[3745,15403,15404,15405,15408],{},"Коли сервер S1 отримує повідомлення від клієнта A, він ",[3775,15406,15407],{},"публікує"," його у канал Redis.",[3745,15410,15411],{},"Redis транслює повідомлення всім підписникам (S1, S2, S3, ...).",[3745,15413,15414],{},"Сервер S2, отримавши повідомлення з Redis, перевіряє, чи підключений клієнт B до нього, і якщо так — відправляє через WebSocket.",[3817,15416,15417],{},[3775,15418,15419],{},"Додатково:",[3742,15421,15422,15436],{},[3745,15423,15424,15425,15428,15429,15431,15432,15435],{},"Використовуйте ",[3775,15426,15427],{},"Sticky Sessions"," на балансувальнику (Nginx ",[3911,15430,7809],{},", HAProxy ",[3911,15433,15434],{},"balance source",") для спрямування всіх запитів від одного клієнта на той самий сервер.",[3745,15437,15438,15439,4882,15442,4882,15445,4061],{},"Для екстремального масштабування (мільйони з'єднань) застосовуйте керовані сервіси: ",[3775,15440,15441],{},"AWS API Gateway WebSocket",[3775,15443,15444],{},"Azure SignalR Service",[3775,15446,15447],{},"Pusher",[14862,15449,15451,15457,15484,15491,15502],{"icon":14864,"label":15450},"❓ Чому WebSocket-з'єднання має бути захищене через WSS (WebSocket Secure) у продакшн-середовищі?",[3817,15452,15453,15454,15456],{},"Незашифроване WebSocket-з'єднання (",[3911,15455,6554],{},") передає всі дані у відкритому вигляді, що робить його вразливим до:",[4169,15458,15459,15465,15471],{},[3745,15460,15461,15464],{},[3775,15462,15463],{},"Перехоплення трафіку (Sniffing):"," зловмисник у тій самій мережі (публічний Wi-Fi, скомпрометований маршрутизатор) може читати всі повідомлення, включаючи паролі, особисті дані, токени.",[3745,15466,15467,15470],{},[3775,15468,15469],{},"Man-in-the-Middle (MITM):"," атакуючий може перехопити з'єднання, змінити повідомлення або підробити відповіді сервера.",[3745,15472,15473,15476,15477,15479,15480,15483],{},[3775,15474,15475],{},"Політика браузерів Mixed Content:"," сучасні браузери блокують ",[3911,15478,6554],{}," з'єднання зі сторінок, завантажених через ",[3911,15481,15482],{},"https:\u002F\u002F",", що робить незахищені WebSocket непрацездатними на HTTPS-сайтах.",[3817,15485,15486,3823,15488,15490],{},[3775,15487,9391],{},[3911,15489,6558],{},") — це WebSocket поверх TLS\u002FSSL:",[3742,15492,15493,15496,15499],{},[3745,15494,15495],{},"Всі дані шифруються за допомогою сертифіката TLS (Let's Encrypt, комерційний CA).",[3745,15497,15498],{},"Гарантується автентичність сервера (клієнт перевіряє сертифікат).",[3745,15500,15501],{},"Захист від перехоплення та підробки даних.",[3817,15503,15504,15505,15508],{},"У продакшн-середовищі ",[3775,15506,15507],{},"завжди"," використовуйте WSS для будь-яких даних, що потребують конфіденційності або цілісності.",[3812,15510,15512],{"id":15511},"підсумок","Підсумок",[3817,15514,15515,15516,15518],{},"Протокол ",[3775,15517,3884],{}," революціонізував комунікацію у реальному часі для вебзастосунків, надаючи повнодуплексний канал зв'язку поверх одного TCP-з'єднання з мінімальним overhead. На відміну від традиційних підходів (polling, long polling, SSE), WebSocket дозволяє обом сторонам — клієнту і серверу — ініціювати передачу даних у будь-який момент без додаткових HTTP-запитів, що знижує затримку до 10–50 мілісекунд і зменшує навантаження на мережу та сервер.",[3817,15520,15521],{},"Ключові аспекти, розглянуті у лекції:",[3734,15523,15524,15542,15547,15554,15559,15569],{},[3737,15525,15528,15529,15531,15532,4882,15534,4882,15536,15538,15539,15541],{"icon":15526,"title":15527},"i-lucide-git-merge","🔄 Процес встановлення з'єднання","WebSocket використовує ",[3775,15530,3923],{}," для сумісності з існуючою інфраструктурою. Клієнт відправляє спеціальний HTTP-запит з заголовками ",[3911,15533,3987],{},[3911,15535,3998],{},[3911,15537,4009],{},". Сервер повертає статус 101 Switching Protocols з хешем ",[3911,15540,4144],{},", після чого з'єднання переходить у режим обміну фреймами.",[3737,15543,15546],{"icon":15544,"title":15545},"i-lucide-package","📦 Структура фреймів","Дані передаються у компактних бінарних фреймах (мінімум 2 байти заголовка) з полями: FIN (завершення), opcode (тип даних: text\u002Fbinary\u002Fping\u002Fpong\u002Fclose), MASK (обов'язкове маскування клієнт→сервер), payload length та корисне навантаження. Протокол підтримує фрагментацію великих повідомлень.",[3737,15548,15550,15551,15553],{"icon":793,"title":15549},"🔒 Безпека та автентифікація","Обов'язкове використання WSS (WebSocket Secure) для шифрування. Автентифікація реалізується через токени у URL, HTTP cookies або перше повідомлення після підключення. Захист від CSWSH через перевірку заголовка ",[3911,15552,4031],{},". Rate limiting та валідація вхідних даних для запобігання DoS та ін'єкціям.",[3737,15555,15558],{"icon":15556,"title":15557},"i-lucide-trending-up","📈 Масштабування","Горизонтальне масштабування через Pub\u002FSub системи (Redis, RabbitMQ) для міжсерверної комунікації. Sticky Sessions на балансувальнику для прив'язки клієнтів до серверів. Використання керованих сервісів (AWS API Gateway WebSocket, Azure SignalR) для мільйонів з'єднань.",[3737,15560,15562,15563,4882,15565,15568],{"icon":2882,"title":15561},"🛠️ Практичне застосування","Чат-застосунки, інтерактивні дошки для спільної роботи, фінансові тікери, багатокористувацькі ігри, системи моніторингу та IoT-телеметрія. Інтеграція у NestJS через Gateway API з декораторами ",[3911,15564,12647],{},[3911,15566,15567],{},"@SubscribeMessage()"," та підтримкою Socket.IO або нативного WebSocket.",[3737,15570,15573],{"icon":15571,"title":15572},"i-lucide-git-compare","🔍 Відмінності від альтернатив","WebSocket забезпечує повнодуплексну комунікацію на противагу однонаправленому SSE. Мінімальний overhead порівняно з Long Polling (2–14 байт vs 500–1000 байт HTTP-заголовків). Затримка 10–50 мс проти 500–2000 мс при polling. Підходить для високочастотного обміну даними.",[3817,15575,15576],{},"Протокол WebSocket є фундаментальною технологією сучасного вебу, що забезпечує інтерактивність та швидкість відгуку, недосяжні для традиційних HTTP-підходів. Розуміння його внутрішньої будови, життєвого циклу з'єднання, механізмів безпеки та архітектурних патернів масштабування є критично важливим для проєктування надійних серверних застосунків реального часу.",[5423,15578,15579,15580,15582,15583,15585,15586,15588],{},"Для поглибленого вивчення протоколу WebSocket рекомендується ознайомитися зі специфікацією ",[3775,15581,3908],{}," (The WebSocket Protocol), документацією ",[3775,15584,13204],{}," та офіційним гайдом ",[3775,15587,11859],{},". Практичне закріплення матеріалу доцільно здійснювати через розробку чат-застосунку або дашборду моніторингу з підтримкою горизонтального масштабування.",[15590,15591,15592],"style",{},"html pre.shiki code .s8xlr, html code.shiki .s8xlr{--shiki-light:#AF00DB;--shiki-default:#C586C0;--shiki-dark:#C586C0}html pre.shiki code .sHH4Y, html code.shiki .sHH4Y{--shiki-light:#000000;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .su1O8, html code.shiki .su1O8{--shiki-light:#0000FF;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .sJj4R, html code.shiki .sJj4R{--shiki-light:#098658;--shiki-default:#B5CEA8;--shiki-dark:#B5CEA8}html pre.shiki code .sKtos, html code.shiki .sKtos{--shiki-light:#800000;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .sbdoH, html code.shiki .sbdoH{--shiki-light:#A31515;--shiki-default:#CE9178;--shiki-dark:#CE9178}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-QsJ, html code.shiki .s-QsJ{--shiki-light:#0070C1;--shiki-default:#4FC1FF;--shiki-dark:#4FC1FF}html pre.shiki code .s8Opu, html code.shiki .s8Opu{--shiki-light:#795E26;--shiki-default:#DCDCAA;--shiki-dark:#DCDCAA}html pre.shiki code .siwwj, html code.shiki .siwwj{--shiki-light:#001080;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .sjcCO, html code.shiki .sjcCO{--shiki-light:#EE0000;--shiki-default:#D7BA7D;--shiki-dark:#D7BA7D}html pre.shiki code .spJ8K, html code.shiki .spJ8K{--shiki-light:#008000;--shiki-default:#6A9955;--shiki-dark:#6A9955}html pre.shiki code .sN1BT, html code.shiki .sN1BT{--shiki-light:#267F99;--shiki-default:#4EC9B0;--shiki-dark:#4EC9B0}html pre.shiki code .sD7JJ, html code.shiki .sD7JJ{--shiki-light:#000000FF;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}",{"title":3942,"searchDepth":3970,"depth":3970,"links":15594},[15595,15599,15604,15609,15616,15617,15625,15630,15637,15642,15648,15649],{"id":3814,"depth":3970,"text":3815,"children":15596},[15597,15598],{"id":3838,"depth":3984,"text":3839},{"id":3877,"depth":3984,"text":3878},{"id":3916,"depth":3970,"text":3917,"children":15600},[15601,15602,15603],{"id":3927,"depth":3984,"text":3928},{"id":4092,"depth":3984,"text":4093},{"id":4207,"depth":3984,"text":4208},{"id":4477,"depth":3970,"text":4478,"children":15605},[15606,15607,15608],{"id":4490,"depth":3984,"text":4491},{"id":4646,"depth":3984,"text":4647},{"id":4723,"depth":3984,"text":4724},{"id":4774,"depth":3970,"text":4775,"children":15610},[15611,15612,15613,15614,15615],{"id":4781,"depth":3984,"text":4782},{"id":5065,"depth":3984,"text":5066},{"id":5428,"depth":3984,"text":5429},{"id":5694,"depth":3984,"text":5695},{"id":5976,"depth":3984,"text":5977},{"id":6137,"depth":3970,"text":6138},{"id":6686,"depth":3970,"text":6687,"children":15618},[15619,15620,15621,15622,15623,15624],{"id":6693,"depth":3984,"text":6694},{"id":6910,"depth":3984,"text":6911},{"id":6979,"depth":3984,"text":6980},{"id":7099,"depth":3984,"text":7100},{"id":7340,"depth":3984,"text":7341},{"id":7618,"depth":3984,"text":7619},{"id":7771,"depth":3970,"text":7772,"children":15626},[15627,15628,15629],{"id":7778,"depth":3984,"text":7779},{"id":8299,"depth":3984,"text":8300},{"id":8371,"depth":3984,"text":8372},{"id":9370,"depth":3970,"text":9371,"children":15631},[15632,15633,15634,15635,15636],{"id":9377,"depth":3984,"text":9378},{"id":9710,"depth":3984,"text":9711},{"id":10666,"depth":3984,"text":10667},{"id":10864,"depth":3984,"text":10865},{"id":11444,"depth":3984,"text":11445},{"id":11851,"depth":3970,"text":11852,"children":15638},[15639,15640,15641],{"id":11858,"depth":3984,"text":11859},{"id":12683,"depth":3984,"text":12684},{"id":13197,"depth":3984,"text":13198},{"id":13551,"depth":3970,"text":13552,"children":15643},[15644,15645,15646,15647],{"id":13558,"depth":3984,"text":13559},{"id":13850,"depth":3984,"text":13851},{"id":14085,"depth":3984,"text":14086},{"id":14504,"depth":3984,"text":14505},{"id":14856,"depth":3970,"text":14857},{"id":15511,"depth":3970,"text":15512},"WebSocket: повнодуплексний зв'язок, рукостискання, структура фреймів та практичне застосування","md",null,{},{"title":3564,"description":15650},"vClo3_f_eoAqPDIPG83eGV6DFf1YtQez_di0o3WuiNc",[15657,15659],{"title":3560,"path":3561,"stem":3562,"description":15658,"children":-1},"Архітектура електронної пошти, SMTP для надсилання, POP3 та IMAP для отримання, MIME, безпека email",{"title":3568,"path":3569,"stem":3570,"description":15660,"children":-1},"Безпечний віддалений доступ, автентифікація за ключами, SSH-тунелювання та практичне використання",1788069974711]