{"id":482780,"date":"2026-06-08T09:01:38","date_gmt":"2026-06-08T09:01:38","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=482780"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=482780","title":{"rendered":"SSE \u0432 production: \u043f\u043e\u0447\u0435\u043c\u0443 \u043d\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e EventSource \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0438 \u0447\u0442\u043e \u0441 \u044d\u0442\u0438\u043c \u0434\u0435\u043b\u0430\u0442\u044c"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h3>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0413\u043e\u0434 \u043d\u0430\u0437\u0430\u0434 \u044f \u0441\u0442\u0440\u043e\u0438\u043b real-time \u0441\u043b\u043e\u0439 \u0434\u043b\u044f AI SaaS-\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b. \u041a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u044b, AI-\u0447\u0430\u0442\u044b \u0441\u043e \u0441\u0442\u0440\u0438\u043c\u0438\u043d\u0433\u043e\u043c \u043e\u0442\u0432\u0435\u0442\u043e\u0432, \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u043a\u043b\u0430\u0434\u043e\u043a \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u2014 \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0434\u043b\u044f \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430.<\/p>\n<p>\u0412\u044b\u0431\u043e\u0440 \u043f\u0430\u043b \u043d\u0430 SSE: \u0432 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 WebSocket, SSE \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u043e\u0432\u0435\u0440\u0445 \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e HTTP, \u0445\u043e\u0440\u043e\u0448\u043e \u0434\u0440\u0443\u0436\u0438\u0442 \u0441 \u043f\u0440\u043e\u043a\u0441\u0438 \u0438 \u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0430\u043c\u0438, \u0438 \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0442\u0440\u0438\u043c\u0438\u043d\u0433\u0430 \u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043a \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u044d\u0442\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e.<\/p>\n<p>\u041d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 <code>EventSource<\/code> \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043b \u0431\u044b\u0441\u0442\u0440\u043e. \u041f\u043e\u0442\u043e\u043c \u043d\u0430\u0447\u0430\u043b\u0438\u0441\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b.<\/p>\n<p>\u041f\u0435\u0440\u0432\u0430\u044f \u2014 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f. <code>EventSource<\/code> \u043d\u0435 \u0443\u043c\u0435\u0435\u0442 \u0441\u043b\u0430\u0442\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438. \u0412\u043e\u043e\u0431\u0449\u0435. \u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u2014 query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0432 URL, \u0447\u0442\u043e \u0434\u043b\u044f \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442.<\/p>\n<p>\u0412\u0442\u043e\u0440\u0430\u044f \u2014 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f. \u041d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 API \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 <code>MessageEvent<\/code> \u0431\u0435\u0437 \u043a\u0430\u043a\u043e\u0439-\u043b\u0438\u0431\u043e \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438. \u0412\u0435\u0441\u044c \u043f\u0430\u0440\u0441\u0438\u043d\u0433 \u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u043e\u0432 \u2014 \u043d\u0430 \u0441\u043e\u0432\u0435\u0441\u0442\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430.<\/p>\n<p>\u0422\u0440\u0435\u0442\u044c\u044f \u2014 \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442. <code>EventSource<\/code> \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0438\u0442\u0441\u044f, \u043d\u043e \u0431\u0435\u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f: \u043d\u0435\u0442 jitter, \u043d\u0435\u0442 exponential backoff, \u043d\u0435\u0442 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043f\u044b\u0442\u043e\u043a. \u0421\u0435\u0440\u0432\u0435\u0440 \u0443\u043f\u0430\u043b \u2014 \u043a\u043b\u0438\u0435\u043d\u0442\u044b \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e, \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u044f thundering herd.<\/p>\n<p>\u0427\u0435\u0442\u0432\u0451\u0440\u0442\u0430\u044f \u2014 \u0432\u043a\u043b\u0430\u0434\u043a\u0438. \u041a\u0430\u0436\u0434\u0430\u044f \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0441\u0432\u043e\u0451 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435. \u0422\u0440\u0438 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u2014 \u0442\u0440\u0438 \u043f\u043e\u0442\u043e\u043a\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041f\u0440\u0438 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u0445 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0435\u0440\u0436\u0430\u0442 \u043f\u0440\u043e\u0434\u0443\u043a\u0442 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c \u0432\u0435\u0441\u044c \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u0434\u0435\u043d\u044c \u044d\u0442\u043e \u043e\u0449\u0443\u0442\u0438\u043c\u043e.<\/p>\n<p>\u041d\u0430\u043f\u0438\u0441\u0430\u043b \u0432\u0441\u0451 \u0441\u0430\u043c. \u0420\u0430\u0431\u043e\u0442\u0430\u043b\u043e. \u041d\u043e \u0441\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u044d\u0442\u043e\u0442 \u043a\u043e\u0434 \u0441\u0442\u0430\u043b \u0441\u0430\u043c\u044b\u043c \u0445\u0440\u0443\u043f\u043a\u0438\u043c \u043c\u0435\u0441\u0442\u043e\u043c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u2014 \u0433\u043e\u043d\u043a\u0438, \u0440\u0443\u0447\u043d\u043e\u0439 leader election, reconnect-\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a, \u0441\u0447\u0451\u0442\u0447\u0438\u043a\u0438 \u043f\u043e\u043f\u044b\u0442\u043e\u043a. \u041a\u043e\u0433\u0434\u0430 \u0447\u0442\u043e-\u0442\u043e \u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a \u2014 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u043b \u043f\u044f\u0442\u044c \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u0447\u0430\u0441 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0441\u044f \u0433\u0434\u0435 \u043f\u043e\u0442\u0435\u0440\u044f\u043b\u0441\u044f event.<\/p>\n<p>\u041a\u043e\u0433\u0434\u0430 \u0438\u0441\u043a\u0430\u043b \u0433\u043e\u0442\u043e\u0432\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0435\u0448\u0430\u0435\u0442 \u0432\u0441\u0451 \u044d\u0442\u043e \u2014 \u043d\u0435 \u043d\u0430\u0448\u0451\u043b. \u041b\u0438\u0431\u043e \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u043e\u0431\u0451\u0440\u0442\u043a\u0438 \u043d\u0430\u0434 <code>EventSource<\/code>, \u043b\u0438\u0431\u043e \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0443, \u043b\u0438\u0431\u043e \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<p>\u0420\u0435\u0448\u0438\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c. \u0422\u0430\u043a \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f <a href=\"https:\/\/www.npmjs.com\/package\/@flamefrontend\/sse-runtime-core\" rel=\"noopener noreferrer nofollow\"><code>sse-runtime<\/code><\/a>.<\/p>\n<hr\/>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 sse-runtime<\/h3>\n<p>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0442\u0440\u0451\u0445 npm-\u043f\u0430\u043a\u0435\u0442\u043e\u0432 \u043f\u043e\u0434 <code>@flamefrontend\/*<\/code> \u0441\u043a\u043e\u0443\u043f\u043e\u043c:<\/p>\n<pre><code class=\"javascript\">@flamefrontend\/sse-runtime-core     \u2014 framework-agnostic \u044f\u0434\u0440\u043e, zero runtime dependencies@flamefrontend\/sse-runtime-react    \u2014 React-\u0430\u0434\u0430\u043f\u0442\u0435\u0440: useSSE \u0445\u0443\u043a \u0438 SSEProvider@flamefrontend\/sse-runtime-devtools \u2014 DevTools-\u043f\u0430\u043d\u0435\u043b\u044c \u0434\u043b\u044f \u0438\u043d\u0441\u043f\u0435\u043a\u0446\u0438\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0430\u043a\u043e\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u043e\u0435. Core \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043d\u0438 \u043e\u0442 \u0447\u0435\u0433\u043e \u2014 \u043d\u0438 \u043e\u0442 React, \u043d\u0438 \u043e\u0442 \u043a\u0430\u043a\u0438\u0445-\u043b\u0438\u0431\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a. \u0412\u0435\u0441\u044c runtime \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d \u043d\u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0445 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0445 API: <code>fetch<\/code>, <code>ReadableStream<\/code>, <code>AbortController<\/code>, <code>TextDecoder<\/code>, <code>BroadcastChannel<\/code>, Web Locks. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u0447\u0442\u043e core \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 Vue, Svelte, Angular \u0438\u043b\u0438 \u0432\u043e\u043e\u0431\u0449\u0435 \u0431\u0435\u0437 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u2014 React-\u043f\u0430\u043a\u0435\u0442 \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439.<\/p>\n<p>React-\u043f\u0430\u043a\u0435\u0442 \u2014 \u0442\u043e\u043d\u043a\u0438\u0439 \u0430\u0434\u0430\u043f\u0442\u0435\u0440 \u043f\u043e\u0432\u0435\u0440\u0445 core. \u041e\u043d \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 lifecycle management: auto-connect \u043f\u0440\u0438 \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438, auto-disconnect \u043f\u0440\u0438 \u0430\u043d\u043c\u0430\u0443\u043d\u0442\u0435, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0442 \u043f\u0435\u0440\u0435\u0440\u0438\u0441\u043e\u0432\u043a\u0443 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432.<\/p>\n<p>DevTools \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 drop-in \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442. \u041e\u043d \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 internals core \u2014 \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442 React-\u043f\u0430\u043a\u0435\u0442.<\/p>\n<h4>\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 core<\/h4>\n<p>\u0412\u043d\u0443\u0442\u0440\u0438 core \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u043e\u0434\u043d\u0443 \u0437\u0430\u0434\u0430\u0447\u0443:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th data-colwidth=\"300\" width=\"300\">\n<p align=\"left\">\u041c\u043e\u0434\u0443\u043b\u044c<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0417\u0430\u0434\u0430\u0447\u0430<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td data-colwidth=\"300\" width=\"300\">\n<p align=\"left\"><code>create-local-sse-client<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 connection loop, state machine, backoff<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"300\" width=\"300\">\n<p align=\"left\"><code>create-coordinated-sse-client<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Leader election + BroadcastChannel fan-out<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"300\" width=\"300\">\n<p align=\"left\"><code>client-state<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u0438 \u043e\u0448\u0438\u0431\u043e\u043a<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"300\" width=\"300\">\n<p align=\"left\"><code>read-sse-stream<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0427\u0442\u0435\u043d\u0438\u0435 \u0431\u0430\u0439\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430 \u0441 heartbeat timeout<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"300\" width=\"300\">\n<p align=\"left\"><code>parse-sse-chunk<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Stateful \u043f\u0430\u0440\u0441\u0435\u0440 SSE-\u0444\u0440\u0435\u0439\u043c\u043e\u0432, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u044b\u0435 \u0447\u0430\u043d\u043a\u0438<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"300\" width=\"300\">\n<p align=\"left\"><code>dispatch-sse-event<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">JSON-\u043f\u0430\u0440\u0441\u0438\u043d\u0433 + \u0432\u044b\u0437\u043e\u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"300\" width=\"300\">\n<p align=\"left\"><code>create-fetch-transport<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">fetch-\u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u0441 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c\u0438 SSE-\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u043c\u0438<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"300\" width=\"300\">\n<p align=\"left\"><code>calculate-reconnect-delay<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Jittered exponential backoff<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"300\" width=\"300\">\n<p align=\"left\"><code>refresh-auth<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0412\u044b\u0437\u043e\u0432 onUnauthorized, \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0444\u043b\u0430\u0433\u0430 retry<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"300\" width=\"300\">\n<p align=\"left\"><code>coordination-backend<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0410\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044f \u043d\u0430\u0434 BroadcastChannel + Web Locks<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<h4>State machine \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f<\/h4>\n<p>\u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f:<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9b8\/a72\/154\/9b8a721546d7c72dcd6029d2cd80fbdc.png\" width=\"1942\" height=\"809\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/9b8\/a72\/154\/9b8a721546d7c72dcd6029d2cd80fbdc.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9b8\/a72\/154\/9b8a721546d7c72dcd6029d2cd80fbdc.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p><code>idle<\/code> \u2014 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043d\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u043e. <br \/><code>connecting<\/code> \u2014 fetch \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d, \u0436\u0434\u0451\u043c \u043e\u0442\u0432\u0435\u0442\u0430. <br \/><code>open<\/code> \u2014 \u0441\u0442\u0440\u0438\u043c \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f, \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0434\u0438\u0441\u043f\u0430\u0442\u0447\u0430\u0442\u0441\u044f. <br \/><code>reconnecting<\/code> \u2014 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u043e\u0442\u0435\u0440\u044f\u043d\u043e, \u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d reconnect \u0441 backoff. <br \/><code>error<\/code> \u2014 \u0438\u0441\u0447\u0435\u0440\u043f\u0430\u043d\u044b \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u0438\u043b\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0430 \u0444\u0430\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430. <br \/><code>closed<\/code> \u2014 \u044f\u0432\u043d\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 <code>disconnect()<\/code>.<\/p>\n<hr\/>\n<h3>\u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f<\/h3>\n<h4>Leader election \u0447\u0435\u0440\u0435\u0437 Web Locks + BroadcastChannel<\/h4>\n<p>\u0417\u0430\u0434\u0430\u0447\u0430: \u0438\u0437 \u0432\u0441\u0435\u0445 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u0432\u043a\u043b\u0430\u0434\u043e\u043a \u043e\u0434\u043d\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0434\u0435\u0440\u0436\u0430\u0442\u044c SSE-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435, \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u2014 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u0451.<\/p>\n<hr\/>\n<p>\u041d\u0430\u0438\u0432\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0444\u043b\u0430\u0433 &#171;\u044f \u043b\u0438\u0434\u0435\u0440&#187; \u0432 <code>localStorage<\/code> \u0441 TTL \u0438 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438 \u0435\u0433\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c. \u041f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b: \u0433\u043e\u043d\u043a\u0430 \u043f\u0440\u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u043c \u0441\u0442\u0430\u0440\u0442\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0432\u043a\u043b\u0430\u0434\u043e\u043a, \u043d\u0443\u0436\u0435\u043d heartbeat, \u043d\u0443\u0436\u043d\u0430 \u043b\u043e\u0433\u0438\u043a\u0430 \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u0430 \u043f\u0440\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u043b\u0438\u0434\u0435\u0440\u0430.<\/p>\n<p>Web Locks \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u043e \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430:<\/p>\n<pre><code class=\"typescript\">const lock = await navigator.locks.request(  `sse-leader-${key}`,  { mode: 'exclusive' },  async () =&gt; {    \/\/ \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c SSE-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0438 \u0447\u0438\u0442\u0430\u0435\u043c \u0441\u0442\u0440\u0438\u043c    \/\/ \u041b\u043e\u043a \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u043a\u0430 \u043f\u0440\u043e\u043c\u0438\u0441 \u043d\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0441\u044f    await runAsLeader()  })<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0447\u0435\u043c\u0443 Web Locks, \u0430 \u043d\u0435 localStorage \u0441 TTL:<\/p>\n<ul>\n<li>\n<p><strong>\u0410\u0442\u043e\u043c\u0430\u0440\u043d\u043e\u0441\u0442\u044c<\/strong> \u2014 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u0447\u0442\u043e exclusive \u043b\u043e\u043a \u0434\u0435\u0440\u0436\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0430<\/p>\n<\/li>\n<li>\n<p><strong>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0435\u043d\u0438\u0435<\/strong> \u2014 \u0435\u0441\u043b\u0438 \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u0437\u0430\u043a\u0440\u044b\u043b\u0430\u0441\u044c \u0438\u043b\u0438 \u0443\u043f\u0430\u043b\u0430, \u043b\u043e\u043a \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0430\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438<\/p>\n<\/li>\n<li>\n<p><strong>\u0411\u0435\u0437 polling<\/strong> \u2014 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d heartbeat, \u043d\u0435 \u043d\u0443\u0436\u043d\u044b \u0442\u0430\u0439\u043c\u0435\u0440\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438<\/p>\n<\/li>\n<li>\n<p><strong>\u041e\u0447\u0435\u0440\u0435\u0434\u044c<\/strong> \u2014 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u0441\u0442\u0430\u044e\u0442 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043d\u0430 \u043b\u043e\u043a. \u041f\u0440\u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0438 \u043b\u0438\u0434\u0435\u0440\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043b\u043e\u043a \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f fan-out \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u043e\u0442 \u043b\u0438\u0434\u0435\u0440\u0430 \u043a follower-\u0432\u043a\u043b\u0430\u0434\u043a\u0430\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>BroadcastChannel<\/code>:<\/p>\n<pre><code class=\"typescript\">\/\/ \u041b\u0438\u0434\u0435\u0440 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0432\u0441\u0435\u043c \u0432\u043a\u043b\u0430\u0434\u043a\u0430\u043cchannel.postMessage({ type: 'event', name, payload })\/\/ Follower \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0438 \u0434\u0438\u0441\u043f\u0430\u0442\u0447\u0438\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043echannel.onmessage = ({ data }) =&gt; {  if (data.type === 'event') {    dispatchEvent(data.name, data.payload)  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0435\u0440\u0435\u0437 broadcast follower-\u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f, \u0438 \u043e\u0448\u0438\u0431\u043a\u0438.<\/p>\n<h4>Generation counters \u043f\u0440\u043e\u0442\u0438\u0432 race conditions<\/h4>\n<p>SSE-\u043a\u043b\u0438\u0435\u043d\u0442 \u2014 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043f\u043e \u043f\u0440\u0438\u0440\u043e\u0434\u0435. \u0422\u0438\u043f\u0438\u0447\u043d\u0430\u044f race condition: \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0447\u0430\u0442\u044b, \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 <code>connect()<\/code> \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 fetch. \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043e\u0442\u0432\u0435\u0442\u0438\u043b \u043f\u043e\u0437\u0436\u0435 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u2014 \u0435\u0433\u043e callback \u043f\u043e\u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0443\u0436\u0435 &#171;\u0447\u0443\u0436\u043e\u0433\u043e&#187; \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 generation counter:<\/p>\n<pre><code class=\"typescript\">let generation = 0function connect() {  const currentGeneration = ++generation  openStream().then(stream =&gt; {    \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0447\u0442\u043e \u043c\u044b \u0432\u0441\u0451 \u0435\u0449\u0451 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b    if (currentGeneration !== generation) return    readStream(stream, (event) =&gt; {      if (currentGeneration !== generation) return      dispatch(event)    })  })}function disconnect() {  generation++ \/\/ \u0418\u043d\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u0432\u0441\u0435 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 async \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438  abortController.abort()}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 <code>connect()<\/code> \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u0441\u0447\u0451\u0442\u0447\u0438\u043a \u0438 \u0437\u0430\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0435\u0433\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435. \u0412\u0441\u0435 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435 \u043a\u043e\u043b\u043b\u0431\u044d\u043a\u0438 \u2014 \u0447\u0442\u0435\u043d\u0438\u0435 \u0441\u0442\u0440\u0438\u043c\u0430, \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0438 reconnect, \u0432\u044b\u0437\u043e\u0432 auth refresh \u2014 \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u044e\u0442 \u0441\u0432\u043e\u0439 generation \u0441 \u0442\u0435\u043a\u0443\u0449\u0438\u043c \u043f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c \u043a\u0430\u043a \u043c\u0443\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435. <code>disconnect()<\/code> \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u0441\u0447\u0451\u0442\u0447\u0438\u043a, \u0438\u043d\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u044f \u0432\u0441\u0435 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p>Generation counters \u0437\u0430\u0449\u0438\u0449\u0430\u044e\u0442 \u043e\u0442 stale callbacks \u2014 \u044d\u0442\u043e \u0442\u043e \u0447\u0435\u0433\u043e <code>AbortController<\/code> \u043d\u0435 \u043f\u043e\u043a\u0440\u044b\u0432\u0430\u0435\u0442. \u0412 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043e\u0431\u0430: <code>AbortController<\/code> \u043f\u0440\u0435\u0440\u044b\u0432\u0430\u0435\u0442 fetch, generation counters \u0437\u0430\u0449\u0438\u0449\u0430\u044e\u0442 \u0432\u0441\u044e \u0446\u0435\u043f\u043e\u0447\u043a\u0443 async \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0433\u043e.<\/p>\n<hr\/>\n<h4>Stale-stream watchdog<\/h4>\n<p>\u0411\u0440\u0430\u0443\u0437\u0435\u0440 \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u0442 \u043e \u0441\u043c\u0435\u0440\u0442\u0438 SSE-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f. \u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0433\u0434\u0435 \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442:<\/p>\n<ul>\n<li>\n<p><strong>\u0421\u043e\u043d \u043d\u043e\u0443\u0442\u0431\u0443\u043a\u0430<\/strong> \u2014 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0430\u0435\u0442, \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043d\u0435 \u044d\u043c\u0438\u0442\u0438\u0442 error<\/p>\n<\/li>\n<li>\n<p><strong>\u041f\u043e\u0442\u0435\u0440\u044f Wi-Fi \u0431\u0435\u0437 \u0440\u0430\u0437\u0440\u044b\u0432\u0430 TCP<\/strong> \u2014 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0436\u0438\u0432\u044b\u043c, \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u043d\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442<\/p>\n<\/li>\n<li>\n<p><strong>Wake drift<\/strong> \u2014 \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0445\u043e\u0434\u0430 \u0438\u0437 \u0441\u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0435 \u0442\u0430\u0439\u043c\u0435\u0440\u044b \u043c\u043e\u0433\u0443\u0442 \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c\u0441\u044f, <code>setTimeout<\/code> \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u0437\u0436\u0435 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0433\u043e<\/p>\n<\/li>\n<\/ul>\n<p>Watchdog \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u043e \u0447\u0435\u0440\u0435\u0437 heartbeat timeout:<\/p>\n<pre><code class=\"typescript\">function readStreamWithWatchdog(  stream: ReadableStream&lt;Uint8Array&gt;,  onEvent,  timeout = 45_000) {  let watchdogTimer: ReturnType&lt;typeof setTimeout&gt;  const reader = stream.getReader()  const decoder = new TextDecoder()  function resetWatchdog() {    clearTimeout(watchdogTimer)    watchdogTimer = setTimeout(() =&gt; {      \/\/ reader.cancel() \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442 pump() \u2014 \u0431\u0435\u0437 \u043d\u0435\u0433\u043e read() \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0441\u0435\u0442\u044c \u0432\u0435\u0447\u043d\u043e      reader.cancel()      onStaleStream()    }, timeout)  }  resetWatchdog()  async function pump() {    while (true) {      const { done, value } = await reader.read()      if (done) break      resetWatchdog() \/\/ \u0421\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0447\u0430\u043d\u043a\u0435      processChunk(decoder.decode(value, { stream: true }))    }  }  return pump()}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u043c \u0447\u0430\u043d\u043a\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0430\u0439\u043c\u0435\u0440 \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f. \u0415\u0441\u043b\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u043b\u0447\u0438\u0442 \u0434\u043e\u043b\u044c\u0448\u0435 <code>timeout<\/code> \u2014 \u0441\u0447\u0438\u0442\u0430\u0435\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0448\u0438\u043c \u0438 \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u0435\u043c reconnect. \u0421\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0436\u0435\u0442 \u0441\u043b\u0430\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u0435 keep-alive \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 <code>:\\n\\n<\/code>, \u0447\u0442\u043e\u0431\u044b \u0434\u0435\u0440\u0436\u0430\u0442\u044c watchdog \u0436\u0438\u0432\u044b\u043c \u043c\u0435\u0436\u0434\u0443 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u043c\u0438.<\/p>\n<p>Wake drift \u0438 \u043f\u043e\u0442\u0435\u0440\u044f \u0441\u0435\u0442\u0438 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u2014 \u0447\u0435\u0440\u0435\u0437 \u0442\u0440\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u044f: <code>visibilitychange<\/code> \u2014 \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0438\u0437 \u0444\u043e\u043d\u043e\u0432\u043e\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430, <code>online<\/code> \u2014 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0435\u0442\u0438, <code>focus<\/code> \u2014 \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0444\u043e\u043a\u0443\u0441\u0430 \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0443. \u041f\u0440\u0438 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u043d\u0438\u0438 \u043b\u044e\u0431\u043e\u0433\u043e \u0438\u0437 \u043d\u0438\u0445 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043f\u0440\u043e\u0448\u043b\u043e \u0441 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438 \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043d\u0435 \u0434\u043e\u0436\u0438\u0434\u0430\u044f\u0441\u044c \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u0430.<\/p>\n<hr\/>\n<h3>React-\u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f<\/h3>\n<h4>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0445 \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0432<\/h4>\n<p>\u041d\u0430\u0438\u0432\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f <code>useSSE<\/code> \u0445\u0443\u043a\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">function useSSE({ url, headers, onMessage }) {  useEffect(() =&gt; {    const client = createSSEClient({      url,      headers,      events: {        message: onMessage      }    })    client.connect()    return () =&gt; client.disconnect()  }, [url, headers, onMessage]) \/\/ \u2190 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0437\u0434\u0435\u0441\u044c}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 <code>headers<\/code> \u0438\u043b\u0438 <code>onMessage<\/code> \u2014 \u044d\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0440\u0435\u043d\u0434\u0435\u0440\u0435, \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0435\u043d\u0434\u0435\u0440. <code>useEffect<\/code> \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043a\u043b\u0438\u0435\u043d\u0442, \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f. \u0411\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0446\u0438\u043a\u043b \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0432.<\/p>\n<p>\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0441\u043e\u0432\u0435\u0442 \u2014 \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u0432 <code>useCallback<\/code>. \u041d\u043e \u044d\u0442\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u043d\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0438 \u043e\u0434\u043d\u0430 \u0437\u0430\u0431\u044b\u0442\u0430\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u043b\u043e\u043c\u0430\u0435\u0442 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435.<\/p>\n<h4>Transport identity vs handler identity<\/h4>\n<p>\u041a\u043b\u044e\u0447\u0435\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0445\u0443\u043a\u0430: \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043e\u043f\u0446\u0438\u0438 \u043d\u0430 \u0434\u0432\u0430 \u043a\u043b\u0430\u0441\u0441\u0430.<\/p>\n<p><strong>Transport identity<\/strong> \u2014 \u043e\u043f\u0446\u0438\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438:<\/p>\n<ul>\n<li>\n<p><code>key<\/code> \u2014 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0442\u0440\u0438\u043c\u0430<\/p>\n<\/li>\n<li>\n<p><code>url<\/code> \u2014 endpoint<\/p>\n<\/li>\n<li>\n<p><code>enabled<\/code> \u2014 \u0432\u043a\u043b\u044e\u0447\u0451\u043d \u043b\u0438 \u0441\u0442\u0440\u0438\u043c<\/p>\n<\/li>\n<li>\n<p><code>credentials<\/code> \u2014 \u0440\u0435\u0436\u0438\u043c credentials \u0434\u043b\u044f fetch<\/p>\n<\/li>\n<li>\n<p><code>coordination.mode<\/code><\/p>\n<\/li>\n<\/ul>\n<p><strong>Handler identity<\/strong> \u2014 \u043e\u043f\u0446\u0438\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0431\u0435\u0437 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f:<\/p>\n<ul>\n<li>\n<p>\u0444\u0443\u043d\u043a\u0446\u0438\u0438-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439<\/p>\n<\/li>\n<li>\n<p><code>headers<\/code> \u2014 async \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430<\/p>\n<\/li>\n<li>\n<p><code>auth.onUnauthorized<\/code><\/p>\n<\/li>\n<li>\n<p>\u043e\u043f\u0446\u0438\u0438 <code>reconnect<\/code><\/p>\n<\/li>\n<li>\n<p>\u043a\u043e\u043b\u043b\u0431\u044d\u043a\u0438 <code>diagnostics<\/code><\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u043c\u0435\u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u043e \u0443\u0431\u0440\u0430\u043d\u044b \u0438\u0437 transport identity. \u0412 SSE \u0441\u0435\u0440\u0432\u0435\u0440 \u0448\u043b\u0451\u0442 \u0447\u0442\u043e \u0448\u043b\u0451\u0442 \u2014 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u0442 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u0442\u0438\u043f\u044b. \u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.<\/p>\n<p>\u0414\u043b\u044f handler identity \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>useLatestRef<\/code>:<\/p>\n<pre><code class=\"typescript\">function useLatestRef&lt;T&gt;(value: T): React.RefObject&lt;T&gt; {  const ref = useRef(value)  \/\/ \u0421\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0432 \u0442\u0435\u043b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u2014 \u0434\u043e \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0437\u0430\u043f\u0443\u0441\u0442\u044f\u0442\u0441\u044f.  \/\/ \u0415\u0441\u043b\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c ref \u0432 useEffect, \u043c\u0435\u0436\u0434\u0443 \u0440\u0435\u043d\u0434\u0435\u0440\u043e\u043c \u0438 \u044d\u0444\u0444\u0435\u043a\u0442\u043e\u043c \u0435\u0441\u0442\u044c \u043c\u043e\u043c\u0435\u043d\u0442  \/\/ \u043a\u043e\u0433\u0434\u0430 ref \u0443\u0441\u0442\u0430\u0440\u0435\u043b \u0438 \u0441\u0442\u0430\u0440\u044b\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u0435.  ref.current = value  return ref}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u043d\u0443\u0442\u0440\u0438 core \u0432\u0441\u0435 handler-\u043e\u043f\u0446\u0438\u0438 \u0447\u0438\u0442\u0430\u044e\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 ref \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0432\u044b\u0437\u043e\u0432\u0435 \u2014 \u043d\u0435 \u0437\u0430\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u0437\u0430\u043c\u044b\u043a\u0430\u043d\u0438\u0435 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u0447\u0442\u043e \u043d\u043e\u0432\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>onMessage<\/code> \u043e\u0442 \u0440\u0435\u043d\u0434\u0435\u0440\u0430 \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u0431\u0435\u0437 \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.<\/p>\n<p><code>buildEventProxies<\/code> \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043f\u0440\u043e\u043a\u0441\u0438-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f: \u043f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435 \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043d\u0438\u0445 \u043e\u0431\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043a <a href=\"http:\/\/optionsRef.current.events\" rel=\"noopener noreferrer nofollow\"><code>optionsRef.current.events<\/code><\/a><code>[name]<\/code> \u2014 \u0432\u0441\u0435\u0433\u0434\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0431\u0435\u0437 \u0437\u0430\u0445\u0432\u0430\u0442\u0430 \u0432 \u0437\u0430\u043c\u044b\u043a\u0430\u043d\u0438\u0435 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430.<\/p>\n<pre><code class=\"typescript\">function useSSE&lt;Events&gt;(options: SSEOptions&lt;Events&gt;) {  \/\/ Transport identity \u2014 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.  \/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u044b \u2014 JSON.stringify \u043d\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u0445 \u043d\u0435\u043d\u0430\u0434\u0451\u0436\u0435\u043d \u0438\u0437-\u0437\u0430 \u043f\u043e\u0440\u044f\u0434\u043a\u0430 \u043a\u043b\u044e\u0447\u0435\u0439.  const transportKey = useMemo(    () =&gt; JSON.stringify([      options.key,      options.url,      options.enabled,      options.credentials    ]),    [      options.key,      options.url,      options.enabled,      options.credentials    ]  )  \/\/ Handler identity \u2014 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0431\u0435\u0437 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 ref  const optionsRef = useLatestRef(options)  useEffect(() =&gt; {    const client = createSSEClient({      ...optionsRef.current,      headers: () =&gt; optionsRef.current.headers?.(),      events: buildEventProxies(optionsRef),    })    client.connect()    return () =&gt; client.disconnect()  }, [transportKey]) \/\/ \u2190 \u0442\u043e\u043b\u044c\u043a\u043e transport identity \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u0445}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0434\u0443\u043c\u0430\u0435\u0442 \u043e <code>useCallback<\/code> \u2014 \u0445\u0443\u043a \u0441\u0430\u043c \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0447\u0442\u043e \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0430, \u0430 \u0447\u0442\u043e \u043d\u0435\u0442.<\/p>\n<hr\/>\n<h3>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f<\/h3>\n<p>\u041a\u043e\u0433\u0434\u0430 <code>sse-runtime<\/code> \u0431\u044b\u043b \u0433\u043e\u0442\u043e\u0432, \u044f \u0432\u043d\u0435\u0434\u0440\u0438\u043b \u0435\u0433\u043e \u0432 \u0442\u043e \u0441\u0430\u043c\u043e\u0435 AI SaaS-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u0441\u0451 \u043d\u0430\u0447\u0430\u043b\u043e\u0441\u044c.<\/p>\n<p>\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430 PR:<\/p>\n<pre><code>\u0424\u0430\u0439\u043b\u043e\u0432 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043e:  23\u0421\u0442\u0440\u043e\u043a \u0443\u0434\u0430\u043b\u0435\u043d\u043e:    3 643\u0421\u0442\u0440\u043e\u043a \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e:  188\u0418\u0442\u043e\u0433\u043e:            \u22123 455\u0421\u043e\u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435:      1:19<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u043e \u0446\u0438\u0444\u0440\u044b \u2014 \u0441\u043b\u0435\u0434\u0441\u0442\u0432\u0438\u0435, \u0430 \u043d\u0435 \u0446\u0435\u043b\u044c. \u0412\u043e\u0442 \u0447\u0442\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c:<\/p>\n<p><strong>\u0423\u0434\u0430\u043b\u0435\u043d\u043e:<\/strong><\/p>\n<ul>\n<li>\n<p>\u0421\u0430\u043c\u043e\u043f\u0438\u0441\u043d\u044b\u0439 reconnect-\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a \u0441 \u0440\u0443\u0447\u043d\u044b\u043c backoff<\/p>\n<\/li>\n<li>\n<p>\u0420\u0443\u0447\u043d\u043e\u0439 leader election \u0447\u0435\u0440\u0435\u0437 <code>localStorage<\/code> + TTL + heartbeat<\/p>\n<\/li>\n<li>\n<p>\u0421\u0430\u043c\u043e\u043f\u0438\u0441\u043d\u044b\u0439 stale-stream \u0434\u0435\u0442\u0435\u043a\u0442\u043e\u0440<\/p>\n<\/li>\n<li>\n<p>DevTools-\u0432\u0438\u0434\u0436\u0435\u0442 \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439, \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e 1 000 \u0441\u0442\u0440\u043e\u043a<\/p>\n<\/li>\n<li>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u043c\u0435\u0436\u0434\u0443 \u0432\u043a\u043b\u0430\u0434\u043a\u0430\u043c\u0438<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e:<\/strong><\/p>\n<pre><code class=\"typescript\">const { status, error } = useSSE&lt;ChatEvents&gt;({  key: [\"chat\", chatId],  url: `\/api\/chats\/${chatId}\/stream`,  headers: async () =&gt; ({    Authorization: `Bearer ${await getToken()}`  }),  events: {    message: (msg) =&gt; appendMessage(msg),    done: (d) =&gt; markDone(d.chatId),  },  auth: {    onUnauthorized: refreshToken,    retryAfterRefresh: true  },  coordination: {    mode: \"single-tab\"  },})<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u0438\u043a\u043b\u0430\u0434\u043d\u043e\u0439 \u043a\u043e\u0434 \u043e\u0441\u0442\u0430\u043b\u0441\u044f \u0441 \u043e\u0434\u043d\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0435\u0439: \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u0435. \u0412\u0441\u044f \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043d\u0430\u044f \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u2014 \u0432 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435 \u0437\u0430 229 unit-\u0442\u0435\u0441\u0442\u0430\u043c\u0438.<\/p>\n<hr\/>\n<h3>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/h3>\n<p>229 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 23 \u0444\u0430\u0439\u043b\u0430\u0445 \u043f\u043e\u043a\u0440\u044b\u0432\u0430\u044e\u0442 \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th data-colwidth=\"211\" width=\"211\">\n<p align=\"left\">\u041e\u0431\u043b\u0430\u0441\u0442\u044c<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0427\u0442\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td data-colwidth=\"211\" width=\"211\">\n<p align=\"left\">SSE frame parsing<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0427\u0430\u0441\u0442\u0438\u0447\u043d\u044b\u0435 \u0447\u0430\u043d\u043a\u0438, multi-field \u0441\u043e\u0431\u044b\u0442\u0438\u044f, BOM, retry parsing<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"211\" width=\"211\">\n<p align=\"left\">Stream reading<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0428\u0442\u0430\u0442\u043d\u043e\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0441\u0442\u0440\u0438\u043c\u0430, heartbeat timeout, abort, transport errors<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"211\" width=\"211\">\n<p align=\"left\">Event dispatch<\/p>\n<\/td>\n<td>\n<p align=\"left\">JSON \u043f\u0430\u0440\u0441\u0438\u043d\u0433, \u043e\u0448\u0438\u0431\u043a\u0438 \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u0445, \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"211\" width=\"211\">\n<p align=\"left\">Reconnect backoff<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0424\u043e\u0440\u043c\u0443\u043b\u0430 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0438, server retry override, maxRetries, jitter bounds<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"211\" width=\"211\">\n<p align=\"left\">Auth refresh<\/p>\n<\/td>\n<td>\n<p align=\"left\">401 handling, retry flag, \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u044f refresh<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"211\" width=\"211\">\n<p align=\"left\">Coordination<\/p>\n<\/td>\n<td>\n<p align=\"left\">Leader election, follower state sync, broadcast messages<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"211\" width=\"211\">\n<p align=\"left\">Client state<\/p>\n<\/td>\n<td>\n<p align=\"left\">Subscribe\/unsubscribe, \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u044b \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u0432, error state<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"211\" width=\"211\">\n<p align=\"left\">React hook<\/p>\n<\/td>\n<td>\n<p align=\"left\">Mount\/unmount lifecycle, \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043e\u043f\u0446\u0438\u0439, event subscriptions<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"211\" width=\"211\">\n<p align=\"left\">React options<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0421\u0432\u0435\u0436\u0435\u0441\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 useLatestRef, \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0432\u0430\u0436\u043d\u044b \u0442\u0435\u0441\u0442\u044b \u043d\u0430 reconnect backoff \u2014 \u0444\u043e\u0440\u043c\u0443\u043b\u0430 <code>base * 2^(attempt - 1) + jitter<\/code> \u0441 \u0443\u0447\u0451\u0442\u043e\u043c server <code>retry:<\/code> \u043f\u043e\u043b\u044f \u0438 <code>maxRetries<\/code>. \u0411\u0435\u0437 \u0442\u0435\u0441\u0442\u043e\u0432 \u044d\u0442\u043e \u043c\u0435\u0441\u0442\u043e \u043b\u0435\u0433\u043a\u043e \u0441\u043b\u043e\u043c\u0430\u0442\u044c &#171;\u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435\u043c&#187;.<\/p>\n<hr\/>\n<h3>\u0427\u0442\u043e \u0434\u0430\u043b\u044c\u0448\u0435<\/h3>\n<p>\u0411\u043b\u0438\u0436\u0430\u0439\u0448\u0438\u0435 \u043f\u043b\u0430\u043d\u044b:<\/p>\n<p><strong>React Native \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430.<\/strong> \u0421\u0435\u0439\u0447\u0430\u0441 core \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <code>BroadcastChannel<\/code> \u0438 Web Locks \u2014 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0435 API \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0435\u0442 \u0432 React Native. \u041f\u043b\u0430\u043d\u0438\u0440\u0443\u044e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c\u044b\u0439 \u0431\u044d\u043a\u0435\u043d\u0434 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0446\u0438\u0438: \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u2014 \u0442\u0435\u043a\u0443\u0449\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0432 React Native \u2014 no-op \u0438\u043b\u0438 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u0447\u0435\u0440\u0435\u0437 AsyncStorage. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0434\u0438\u043d \u043f\u0430\u043a\u0435\u0442 \u0432 \u043e\u0431\u043e\u0438\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\u0445 \u0438 \u0443\u0431\u0440\u0430\u0442\u044c \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043b\u043e\u0433\u0438\u043a\u0438 \u043c\u0435\u0436\u0434\u0443 \u0434\u0432\u0443\u043c\u044f \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f\u043c\u0438.<\/p>\n<p><strong>SSE Server helpers.<\/strong> \u0423\u0442\u0438\u043b\u0438\u0442\u044b \u0434\u043b\u044f Node.js\/Edge \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 \u2014 \u0447\u0442\u043e\u0431\u044b \u0444\u043e\u0440\u043c\u0430\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u043b \u0441 \u0442\u0435\u043c \u0447\u0442\u043e \u043e\u0436\u0438\u0434\u0430\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442.<\/p>\n<hr\/>\n<h3>\u0421\u0441\u044b\u043b\u043a\u0438<\/h3>\n<ul>\n<li>\n<p>npm core: <a href=\"https:\/\/www.npmjs.com\/package\/@flamefrontend\/sse-runtime-core\" rel=\"noopener noreferrer nofollow\">https:\/\/www.npmjs.com\/package\/@flamefrontend\/sse-runtime-core<\/a><\/p>\n<\/li>\n<li>\n<p>npm react: <a href=\"https:\/\/www.npmjs.com\/package\/@flamefrontend\/sse-runtime-react\" rel=\"noopener noreferrer nofollow\">https:\/\/www.npmjs.com\/package\/@flamefrontend\/sse-runtime-react<\/a><\/p>\n<\/li>\n<li>\n<p>npm devtools: <a href=\"https:\/\/www.npmjs.com\/package\/@flamefrontend\/sse-runtime-devtools\" rel=\"noopener noreferrer nofollow\">https:\/\/www.npmjs.com\/package\/@flamefrontend\/sse-runtime-devtools<\/a><\/p>\n<\/li>\n<li>\n<p>GitHub: <a href=\"https:\/\/github.com\/FlameFront-end\/sse-runtime\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/FlameFront-end\/sse-runtime<\/a><\/p>\n<\/li>\n<\/ul>\n<p>\u0415\u0441\u043b\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442\u0435 \u0441 SSE \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d\u0435 \u0438 \u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u043b\u0438\u0441\u044c \u0441 \u043f\u043e\u0445\u043e\u0436\u0438\u043c\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c\u0438 \u2014 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0443\u0441\u043b\u044b\u0448\u0430\u0442\u044c \u043a\u0430\u043a \u0440\u0435\u0448\u0430\u043b\u0438.<\/p>\n<p>Telegram: <a href=\"https:\/\/t.me\/Artem_Kaliganov\" rel=\"noopener noreferrer nofollow\">@Artem_Kaliganov<\/a><\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1044808\/\">https:\/\/habr.com\/ru\/articles\/1044808\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435\u0413\u043e\u0434 \u043d\u0430\u0437\u0430\u0434 \u044f \u0441\u0442\u0440\u043e\u0438\u043b real-time \u0441\u043b\u043e\u0439 \u0434\u043b\u044f AI SaaS-\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b. \u041a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u044b, AI-\u0447\u0430\u0442\u044b \u0441\u043e \u0441\u0442\u0440\u0438\u043c\u0438\u043d\u0433\u043e\u043c \u043e\u0442\u0432\u0435\u0442\u043e\u0432, \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u043a\u043b\u0430\u0434\u043e\u043a \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u2014 \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0434\u043b\u044f \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430.\u0412\u044b\u0431\u043e\u0440 \u043f\u0430\u043b \u043d\u0430 SSE: \u0432 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 WebSocket, SSE \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u043e\u0432\u0435\u0440\u0445 \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e HTTP, \u0445\u043e\u0440\u043e\u0448\u043e \u0434\u0440\u0443\u0436\u0438\u0442 \u0441 \u043f\u0440\u043e\u043a\u0441\u0438 \u0438 \u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0430\u043c\u0438, \u0438 \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0442\u0440\u0438\u043c\u0438\u043d\u0433\u0430 \u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043a \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u044d\u0442\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e.\u041d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 EventSource \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043b \u0431\u044b\u0441\u0442\u0440\u043e. \u041f\u043e\u0442\u043e\u043c \u043d\u0430\u0447\u0430\u043b\u0438\u0441\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b.\u041f\u0435\u0440\u0432\u0430\u044f \u2014 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f. EventSource \u043d\u0435 \u0443\u043c\u0435\u0435\u0442 \u0441\u043b\u0430\u0442\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438. \u0412\u043e\u043e\u0431\u0449\u0435. \u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u2014 query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0432 URL, \u0447\u0442\u043e \u0434\u043b\u044f \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442.\u0412\u0442\u043e\u0440\u0430\u044f \u2014 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f. \u041d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 API \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 MessageEvent \u0431\u0435\u0437 \u043a\u0430\u043a\u043e\u0439-\u043b\u0438\u0431\u043e \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438. \u0412\u0435\u0441\u044c \u043f\u0430\u0440\u0441\u0438\u043d\u0433 \u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u043e\u0432 \u2014 \u043d\u0430 \u0441\u043e\u0432\u0435\u0441\u0442\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430.\u0422\u0440\u0435\u0442\u044c\u044f \u2014 \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442. EventSource \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0438\u0442\u0441\u044f, \u043d\u043e \u0431\u0435\u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f: \u043d\u0435\u0442 jitter, \u043d\u0435\u0442 exponential backoff, \u043d\u0435\u0442 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043f\u044b\u0442\u043e\u043a. \u0421\u0435\u0440\u0432\u0435\u0440 \u0443\u043f\u0430\u043b \u2014 \u043a\u043b\u0438\u0435\u043d\u0442\u044b \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e, \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u044f thundering herd.\u0427\u0435\u0442\u0432\u0451\u0440\u0442\u0430\u044f \u2014 \u0432\u043a\u043b\u0430\u0434\u043a\u0438. \u041a\u0430\u0436\u0434\u0430\u044f \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0441\u0432\u043e\u0451 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435. \u0422\u0440\u0438 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u2014 \u0442\u0440\u0438 \u043f\u043e\u0442\u043e\u043a\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041f\u0440\u0438 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u0445 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0435\u0440\u0436\u0430\u0442 \u043f\u0440\u043e\u0434\u0443\u043a\u0442 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c \u0432\u0435\u0441\u044c \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u0434\u0435\u043d\u044c \u044d\u0442\u043e \u043e\u0449\u0443\u0442\u0438\u043c\u043e.\u041d\u0430\u043f\u0438\u0441\u0430\u043b \u0432\u0441\u0451 \u0441\u0430\u043c. \u0420\u0430\u0431\u043e\u0442\u0430\u043b\u043e. \u041d\u043e \u0441\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u044d\u0442\u043e\u0442 \u043a\u043e\u0434 \u0441\u0442\u0430\u043b \u0441\u0430\u043c\u044b\u043c \u0445\u0440\u0443\u043f\u043a\u0438\u043c \u043c\u0435\u0441\u0442\u043e\u043c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u2014 \u0433\u043e\u043d\u043a\u0438, \u0440\u0443\u0447\u043d\u043e\u0439 leader election, reconnect-\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a, \u0441\u0447\u0451\u0442\u0447\u0438\u043a\u0438 \u043f\u043e\u043f\u044b\u0442\u043e\u043a. \u041a\u043e\u0433\u0434\u0430 \u0447\u0442\u043e-\u0442\u043e \u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a \u2014 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u043b \u043f\u044f\u0442\u044c \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u0447\u0430\u0441 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0441\u044f \u0433\u0434\u0435 \u043f\u043e\u0442\u0435\u0440\u044f\u043b\u0441\u044f event.\u041a\u043e\u0433\u0434\u0430 \u0438\u0441\u043a\u0430\u043b \u0433\u043e\u0442\u043e\u0432\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0435\u0448\u0430\u0435\u0442 \u0432\u0441\u0451 \u044d\u0442\u043e \u2014 \u043d\u0435 \u043d\u0430\u0448\u0451\u043b. \u041b\u0438\u0431\u043e \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u043e\u0431\u0451\u0440\u0442\u043a\u0438 \u043d\u0430\u0434 EventSource, \u043b\u0438\u0431\u043e \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0443, \u043b\u0438\u0431\u043e \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0438.\u0420\u0435\u0448\u0438\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c. \u0422\u0430\u043a \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f sse-runtime.\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 sse-runtime\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0442\u0440\u0451\u0445 npm-\u043f\u0430\u043a\u0435\u0442\u043e\u0432 \u043f\u043e\u0434 @flamefrontend\/* \u0441\u043a\u043e\u0443\u043f\u043e\u043c:@flamefrontend\/sse-runtime-core     \u2014 framework-agnostic \u044f\u0434\u0440\u043e, zero runtime dependencies@flamefrontend\/sse-runtime-react    \u2014 React-\u0430\u0434\u0430\u043f\u0442\u0435\u0440: useSSE \u0445\u0443\u043a \u0438 SSEProvider@flamefrontend\/sse-runtime-devtools \u2014 DevTools-\u043f\u0430\u043d\u0435\u043b\u044c \u0434\u043b\u044f \u0438\u043d\u0441\u043f\u0435\u043a\u0446\u0438\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439\u0422\u0430\u043a\u043e\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u043e\u0435. Core \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043d\u0438 \u043e\u0442 \u0447\u0435\u0433\u043e \u2014 \u043d\u0438 \u043e\u0442 React, \u043d\u0438 \u043e\u0442 \u043a\u0430\u043a\u0438\u0445-\u043b\u0438\u0431\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a. \u0412\u0435\u0441\u044c runtime \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d \u043d\u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0445 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0445 API: fetch, ReadableStream, AbortController, TextDecoder, BroadcastChannel, Web Locks. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u0447\u0442\u043e core \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 Vue, Svelte, Angular \u0438\u043b\u0438 \u0432\u043e\u043e\u0431\u0449\u0435 \u0431\u0435\u0437 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u2014 React-\u043f\u0430\u043a\u0435\u0442 \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439.React-\u043f\u0430\u043a\u0435\u0442 \u2014 \u0442\u043e\u043d\u043a\u0438\u0439 \u0430\u0434\u0430\u043f\u0442\u0435\u0440 \u043f\u043e\u0432\u0435\u0440\u0445 core. \u041e\u043d \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 lifecycle management: auto-connect \u043f\u0440\u0438 \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438, auto-disconnect \u043f\u0440\u0438 \u0430\u043d\u043c\u0430\u0443\u043d\u0442\u0435, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0442 \u043f\u0435\u0440\u0435\u0440\u0438\u0441\u043e\u0432\u043a\u0443 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432.DevTools \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 drop-in \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442. \u041e\u043d \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 internals core \u2014 \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442 React-\u043f\u0430\u043a\u0435\u0442.\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 core\u0412\u043d\u0443\u0442\u0440\u0438 core \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u043e\u0434\u043d\u0443 \u0437\u0430\u0434\u0430\u0447\u0443:\u041c\u043e\u0434\u0443\u043b\u044c\u0417\u0430\u0434\u0430\u0447\u0430create-local-sse-client\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 connection loop, state machine, backoffcreate-coordinated-sse-clientLeader election + BroadcastChannel fan-outclient-state\u0420\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u0438 \u043e\u0448\u0438\u0431\u043e\u043aread-sse-stream\u0427\u0442\u0435\u043d\u0438\u0435 \u0431\u0430\u0439\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430 \u0441 heartbeat timeoutparse-sse-chunkStateful \u043f\u0430\u0440\u0441\u0435\u0440 SSE-\u0444\u0440\u0435\u0439\u043c\u043e\u0432, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u044b\u0435 \u0447\u0430\u043d\u043a\u0438dispatch-sse-eventJSON-\u043f\u0430\u0440\u0441\u0438\u043d\u0433 + \u0432\u044b\u0437\u043e\u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432create-fetch-transportfetch-\u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u0441 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c\u0438 SSE-\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u043c\u0438calculate-reconnect-delayJittered exponential backoffrefresh-auth\u0412\u044b\u0437\u043e\u0432 onUnauthorized, \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0444\u043b\u0430\u0433\u0430 retrycoordination-backend\u0410\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044f \u043d\u0430\u0434 BroadcastChannel + Web LocksState machine \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f:idle \u2014 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043d\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u043e. connecting \u2014 fetch \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d, \u0436\u0434\u0451\u043c \u043e\u0442\u0432\u0435\u0442\u0430. open \u2014 \u0441\u0442\u0440\u0438\u043c \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f, \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0434\u0438\u0441\u043f\u0430\u0442\u0447\u0430\u0442\u0441\u044f. reconnecting \u2014 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u043e\u0442\u0435\u0440\u044f\u043d\u043e, \u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d reconnect \u0441 backoff. error \u2014 \u0438\u0441\u0447\u0435\u0440\u043f\u0430\u043d\u044b \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u0438\u043b\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0430 \u0444\u0430\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430. closed \u2014 \u044f\u0432\u043d\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 disconnect().\u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044fLeader election \u0447\u0435\u0440\u0435\u0437 Web Locks + BroadcastChannel\u0417\u0430\u0434\u0430\u0447\u0430: \u0438\u0437 \u0432\u0441\u0435\u0445 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u0432\u043a\u043b\u0430\u0434\u043e\u043a \u043e\u0434\u043d\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0434\u0435\u0440\u0436\u0430\u0442\u044c SSE-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435, \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u2014 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u0451.\u041d\u0430\u0438\u0432\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0444\u043b\u0430\u0433 &#171;\u044f \u043b\u0438\u0434\u0435\u0440&#187; \u0432 localStorage \u0441 TTL \u0438 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438 \u0435\u0433\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c. \u041f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b: \u0433\u043e\u043d\u043a\u0430 \u043f\u0440\u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u043c \u0441\u0442\u0430\u0440\u0442\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0432\u043a\u043b\u0430\u0434\u043e\u043a, \u043d\u0443\u0436\u0435\u043d heartbeat, \u043d\u0443\u0436\u043d\u0430 \u043b\u043e\u0433\u0438\u043a\u0430 \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u0430 \u043f\u0440\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u043b\u0438\u0434\u0435\u0440\u0430.Web Locks \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u043e \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430:const lock = await navigator.locks.request(  `sse-leader-${key}`,  { mode: &#8216;exclusive&#8217; },  async () =&gt; {    \/\/ \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c SSE-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0438 \u0447\u0438\u0442\u0430\u0435\u043c \u0441\u0442\u0440\u0438\u043c    \/\/ \u041b\u043e\u043a \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u043a\u0430 \u043f\u0440\u043e\u043c\u0438\u0441 \u043d\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0441\u044f    await runAsLeader()  })\u041f\u043e\u0447\u0435\u043c\u0443 Web Locks, \u0430 \u043d\u0435 localStorage \u0441 TTL:\u0410\u0442\u043e\u043c\u0430\u0440\u043d\u043e\u0441\u0442\u044c \u2014 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u0447\u0442\u043e exclusive \u043b\u043e\u043a \u0434\u0435\u0440\u0436\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0430\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u2014 \u0435\u0441\u043b\u0438 \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u0437\u0430\u043a\u0440\u044b\u043b\u0430\u0441\u044c \u0438\u043b\u0438 \u0443\u043f\u0430\u043b\u0430, \u043b\u043e\u043a \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0430\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0411\u0435\u0437 polling \u2014 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d heartbeat, \u043d\u0435 \u043d\u0443\u0436\u043d\u044b \u0442\u0430\u0439\u043c\u0435\u0440\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438\u041e\u0447\u0435\u0440\u0435\u0434\u044c \u2014 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u0441\u0442\u0430\u044e\u0442 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043d\u0430 \u043b\u043e\u043a. \u041f\u0440\u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0438 \u043b\u0438\u0434\u0435\u0440\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043b\u043e\u043a \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e\u0414\u043b\u044f fan-out \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u043e\u0442 \u043b\u0438\u0434\u0435\u0440\u0430 \u043a follower-\u0432\u043a\u043b\u0430\u0434\u043a\u0430\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f BroadcastChannel:\/\/ \u041b\u0438\u0434\u0435\u0440 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0432\u0441\u0435\u043c \u0432\u043a\u043b\u0430\u0434\u043a\u0430\u043cchannel.postMessage({ type: &#8216;event&#8217;, name, payload })\/\/ Follower \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0438 \u0434\u0438\u0441\u043f\u0430\u0442\u0447\u0438\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043echannel.onmessage = ({ data }) =&gt; {  if (data.type === &#8216;event&#8217;) {    dispatchEvent(data.name, data.payload)  }}\u0427\u0435\u0440\u0435\u0437 broadcast follower-\u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f, \u0438 \u043e\u0448\u0438\u0431\u043a\u0438.Generation counters \u043f\u0440\u043e\u0442\u0438\u0432 race conditionsSSE-\u043a\u043b\u0438\u0435\u043d\u0442 \u2014 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043f\u043e \u043f\u0440\u0438\u0440\u043e\u0434\u0435. \u0422\u0438\u043f\u0438\u0447\u043d\u0430\u044f race condition: \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0447\u0430\u0442\u044b, \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 connect() \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 fetch. \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043e\u0442\u0432\u0435\u0442\u0438\u043b \u043f\u043e\u0437\u0436\u0435 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u2014 \u0435\u0433\u043e callback \u043f\u043e\u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0443\u0436\u0435 &#171;\u0447\u0443\u0436\u043e\u0433\u043e&#187; \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 generation counter:let generation = 0function connect() {  const currentGeneration = ++generation  openStream().then(stream =&gt; {    \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0447\u0442\u043e \u043c\u044b \u0432\u0441\u0451 \u0435\u0449\u0451 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b    if (currentGeneration !== generation) return    readStream(stream, (event) =&gt; {      if (currentGeneration !== generation) return      dispatch(event)    })  })}function disconnect() {  generation++ \/\/ \u0418\u043d\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u0432\u0441\u0435 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 async \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438  abortController.abort()}\u041a\u0430\u0436\u0434\u044b\u0439 connect() \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u0441\u0447\u0451\u0442\u0447\u0438\u043a \u0438 \u0437\u0430\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0435\u0433\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435. \u0412\u0441\u0435 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435 \u043a\u043e\u043b\u043b\u0431\u044d\u043a\u0438 \u2014 \u0447\u0442\u0435\u043d\u0438\u0435 \u0441\u0442\u0440\u0438\u043c\u0430, \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0438 reconnect, \u0432\u044b\u0437\u043e\u0432 auth refresh \u2014 \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u044e\u0442 \u0441\u0432\u043e\u0439 generation \u0441 \u0442\u0435\u043a\u0443\u0449\u0438\u043c \u043f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c \u043a\u0430\u043a \u043c\u0443\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435. disconnect() \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u0441\u0447\u0451\u0442\u0447\u0438\u043a, \u0438\u043d\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u044f \u0432\u0441\u0435 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438.Generation counters \u0437\u0430\u0449\u0438\u0449\u0430\u044e\u0442 \u043e\u0442 stale callbacks \u2014 \u044d\u0442\u043e \u0442\u043e \u0447\u0435\u0433\u043e AbortController \u043d\u0435 \u043f\u043e\u043a\u0440\u044b\u0432\u0430\u0435\u0442. \u0412 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043e\u0431\u0430: AbortController \u043f\u0440\u0435\u0440\u044b\u0432\u0430\u0435\u0442 fetch, generation counters \u0437\u0430\u0449\u0438\u0449\u0430\u044e\u0442 \u0432\u0441\u044e \u0446\u0435\u043f\u043e\u0447\u043a\u0443 async \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0433\u043e.Stale-stream watchdog\u0411\u0440\u0430\u0443\u0437\u0435\u0440 \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u0442 \u043e \u0441\u043c\u0435\u0440\u0442\u0438 SSE-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f. \u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0433\u0434\u0435 \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442:\u0421\u043e\u043d \u043d\u043e\u0443\u0442\u0431\u0443\u043a\u0430 \u2014 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0430\u0435\u0442, \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043d\u0435 \u044d\u043c\u0438\u0442\u0438\u0442 error\u041f\u043e\u0442\u0435\u0440\u044f Wi-Fi \u0431\u0435\u0437 \u0440\u0430\u0437\u0440\u044b\u0432\u0430 TCP \u2014 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0436\u0438\u0432\u044b\u043c, \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u043d\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442Wake drift \u2014 \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0445\u043e\u0434\u0430 \u0438\u0437 \u0441\u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0435 \u0442\u0430\u0439\u043c\u0435\u0440\u044b \u043c\u043e\u0433\u0443\u0442 \u0441\u0434\u0432\u0438\u043d\u0443\u0442\u044c\u0441\u044f, setTimeout \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u0437\u0436\u0435 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0433\u043eWatchdog \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u043e \u0447\u0435\u0440\u0435\u0437 heartbeat timeout:function readStreamWithWatchdog(  stream: ReadableStream&lt;Uint8Array&gt;,  onEvent,  timeout = 45_000) {  let watchdogTimer: ReturnType&lt;typeof setTimeout&gt;  const reader = stream.getReader()  const decoder = new TextDecoder()  function resetWatchdog() {    clearTimeout(watchdogTimer)    watchdogTimer = setTimeout(() =&gt; {      \/\/ reader.cancel() \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442 pump() \u2014 \u0431\u0435\u0437 \u043d\u0435\u0433\u043e read() \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0441\u0435\u0442\u044c \u0432\u0435\u0447\u043d\u043e      reader.cancel()      onStaleStream()    }, timeout)  }  resetWatchdog()  async function pump() {    while (true) {      const { done, value } = await reader.read()      if (done) break      resetWatchdog() \/\/ \u0421\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0447\u0430\u043d\u043a\u0435      processChunk(decoder.decode(value, { stream: true }))    }  }  return pump()}\u041f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u043c \u0447\u0430\u043d\u043a\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0430\u0439\u043c\u0435\u0440 \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f. \u0415\u0441\u043b\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u043b\u0447\u0438\u0442 \u0434\u043e\u043b\u044c\u0448\u0435 timeout \u2014 \u0441\u0447\u0438\u0442\u0430\u0435\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0448\u0438\u043c \u0438 \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u0435\u043c reconnect. \u0421\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0436\u0435\u0442 \u0441\u043b\u0430\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u0435 keep-alive \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 :\\n\\n, \u0447\u0442\u043e\u0431\u044b \u0434\u0435\u0440\u0436\u0430\u0442\u044c watchdog \u0436\u0438\u0432\u044b\u043c \u043c\u0435\u0436\u0434\u0443 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u043c\u0438.Wake drift \u0438 \u043f\u043e\u0442\u0435\u0440\u044f \u0441\u0435\u0442\u0438 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u2014 \u0447\u0435\u0440\u0435\u0437 \u0442\u0440\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u044f: visibilitychange \u2014 \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0438\u0437 \u0444\u043e\u043d\u043e\u0432\u043e\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430, online \u2014 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0435\u0442\u0438, focus \u2014 \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0444\u043e\u043a\u0443\u0441\u0430 \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0443. \u041f\u0440\u0438 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u043d\u0438\u0438 \u043b\u044e\u0431\u043e\u0433\u043e \u0438\u0437 \u043d\u0438\u0445 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043f\u0440\u043e\u0448\u043b\u043e \u0441 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438 \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043d\u0435 \u0434\u043e\u0436\u0438\u0434\u0430\u044f\u0441\u044c \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u0430.React-\u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0445 \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0432\u041d\u0430\u0438\u0432\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f useSSE \u0445\u0443\u043a\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:function useSSE({ url, headers, onMessage }) {  useEffect(() =&gt; {    const client = createSSEClient({      url,      headers,      events: {        message: onMessage      }    })    client.connect()    return () =&gt; client.disconnect()  }, [url, headers, onMessage]) \/\/ \u2190 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0437\u0434\u0435\u0441\u044c}\u0415\u0441\u043b\u0438 headers \u0438\u043b\u0438 onMessage \u2014 \u044d\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0440\u0435\u043d\u0434\u0435\u0440\u0435, \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0435\u043d\u0434\u0435\u0440. useEffect \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043a\u043b\u0438\u0435\u043d\u0442, \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f. \u0411\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0446\u0438\u043a\u043b \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0432.\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0441\u043e\u0432\u0435\u0442 \u2014 \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u0432 useCallback. \u041d\u043e \u044d\u0442\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u043d\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0438 \u043e\u0434\u043d\u0430 \u0437\u0430\u0431\u044b\u0442\u0430\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u043b\u043e\u043c\u0430\u0435\u0442 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435.Transport identity vs handler identity\u041a\u043b\u044e\u0447\u0435\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0445\u0443\u043a\u0430: \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043e\u043f\u0446\u0438\u0438 \u043d\u0430 \u0434\u0432\u0430 \u043a\u043b\u0430\u0441\u0441\u0430.Transport identity \u2014 \u043e\u043f\u0446\u0438\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438:key \u2014 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0442\u0440\u0438\u043c\u0430url \u2014 endpointenabled \u2014 \u0432\u043a\u043b\u044e\u0447\u0451\u043d \u043b\u0438 \u0441\u0442\u0440\u0438\u043ccredentials \u2014 \u0440\u0435\u0436\u0438\u043c credentials \u0434\u043b\u044f fetchcoordination.modeHandler identity \u2014 \u043e\u043f\u0446\u0438\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0431\u0435\u0437 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f:\u0444\u0443\u043d\u043a\u0446\u0438\u0438-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439headers \u2014 async \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430auth.onUnauthorized\u043e\u043f\u0446\u0438\u0438 reconnect\u043a\u043e\u043b\u043b\u0431\u044d\u043a\u0438 diagnostics\u0418\u043c\u0435\u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u043e \u0443\u0431\u0440\u0430\u043d\u044b \u0438\u0437 transport identity. \u0412 SSE \u0441\u0435\u0440\u0432\u0435\u0440 \u0448\u043b\u0451\u0442 \u0447\u0442\u043e \u0448\u043b\u0451\u0442 \u2014 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u0442 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u0442\u0438\u043f\u044b. \u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.\u0414\u043b\u044f handler identity \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f useLatestRef:function useLatestRef&lt;T&gt;(value: T): React.RefObject&lt;T&gt; {  const ref = useRef(value)  \/\/ \u0421\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0432 \u0442\u0435\u043b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u2014 \u0434\u043e \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0437\u0430\u043f\u0443\u0441\u0442\u044f\u0442\u0441\u044f.  \/\/ \u0415\u0441\u043b\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c ref \u0432 useEffect, \u043c\u0435\u0436\u0434\u0443 \u0440\u0435\u043d\u0434\u0435\u0440\u043e\u043c \u0438 \u044d\u0444\u0444\u0435\u043a\u0442\u043e\u043c \u0435\u0441\u0442\u044c \u043c\u043e\u043c\u0435\u043d\u0442  \/\/ \u043a\u043e\u0433\u0434\u0430 ref \u0443\u0441\u0442\u0430\u0440\u0435\u043b \u0438 \u0441\u0442\u0430\u0440\u044b\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-482780","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/482780","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=482780"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/482780\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=482780"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=482780"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=482780"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}