[{"data":1,"prerenderedAt":972},["ShallowReactive",2],{"navigation":3,"-guide-resolver":88,"-guide-resolver-surround":968},[4,51],{"title":5,"path":6,"stem":7,"children":8,"icon":10},"Guide","\u002Fguide","1.guide\u002F1.index",[9,11,16,21,26,31,36,41,46],{"title":5,"path":6,"stem":7,"icon":10},"ph:book-open-duotone",{"title":12,"path":13,"stem":14,"icon":15},"Hooks","\u002Fguide\u002Fhooks","1.guide\u002F2.hooks","material-symbols-light:data-object",{"title":17,"path":18,"stem":19,"icon":20},"Peer","\u002Fguide\u002Fpeer","1.guide\u002F3.peer","mynaui:api",{"title":22,"path":23,"stem":24,"icon":25},"Message","\u002Fguide\u002Fmessage","1.guide\u002F4.message","solar:letter-line-duotone",{"title":27,"path":28,"stem":29,"icon":30},"Pub \u002F Sub","\u002Fguide\u002Fpubsub","1.guide\u002F5.pubsub","simple-icons:googlepubsub",{"title":32,"path":33,"stem":34,"icon":35},"Sync Backplane","\u002Fguide\u002Fsync","1.guide\u002F6.sync","tabler:refresh",{"title":37,"path":38,"stem":39,"icon":40},"Resolver API","\u002Fguide\u002Fresolver","1.guide\u002F7.resolver","tabler:route",{"title":42,"path":43,"stem":44,"icon":45},"WebSocket Proxy","\u002Fguide\u002Fproxy","1.guide\u002F8.proxy","tabler:arrows-exchange",{"title":47,"path":48,"stem":49,"icon":50},"WebSocket Client","\u002Fguide\u002Fclient","1.guide\u002F9.client","tabler:plug-connected",{"title":52,"path":53,"stem":54,"children":55,"icon":57},"Adapters","\u002Fadapters","2.adapters\u002F1.index",[56,58,63,68,73,78,83],{"title":52,"path":53,"stem":54,"icon":57},"emojione-monotone:electric-plug",{"title":59,"path":60,"stem":61,"icon":62},"Bun","\u002Fadapters\u002Fbun","2.adapters\u002Fbun","simple-icons:bun",{"title":64,"path":65,"stem":66,"icon":67},"Bunny","\u002Fadapters\u002Fbunny","2.adapters\u002Fbunny","mdi:rabbit",{"title":69,"path":70,"stem":71,"icon":72},"Cloudflare","\u002Fadapters\u002Fcloudflare","2.adapters\u002Fcloudflare","devicon-plain:cloudflareworkers",{"title":74,"path":75,"stem":76,"icon":77},"Deno","\u002Fadapters\u002Fdeno","2.adapters\u002Fdeno","teenyicons:deno-solid",{"title":79,"path":80,"stem":81,"icon":82},"Node.js","\u002Fadapters\u002Fnode","2.adapters\u002Fnode","akar-icons:node-fill",{"title":84,"path":85,"stem":86,"icon":87},"SSE","\u002Fadapters\u002Fsse","2.adapters\u002Fsse","clarity:two-way-arrows-line",{"id":89,"title":37,"body":90,"description":109,"extension":963,"meta":964,"navigation":965,"path":38,"seo":966,"stem":39,"__hash__":967},"content\u002F1.guide\u002F7.resolver.md",{"type":91,"value":92,"toc":957,"icon":40},"minimark",[93,97,103,254,257,342,347,378,426,432,474,486,496,504,514,953],[94,95,96],"p",{},"When integrating WebSockets with larger projects, it is often needed to dynamically route an incoming event to websocket hooks. crossws provides a very simple mechanism to do this using resolver API.",[98,99,100],"tip",{},[94,101,102],{},"\nResolver supports async results. This allows implementing lazy loading.",[104,105,110],"pre",{"className":106,"code":107,"language":108,"meta":109,"style":109},"language-js shiki shiki-themes github-light github-dark github-dark","\u002F\u002F https:\u002F\u002Fcrossws.h3.dev\u002Fadapters\nimport crossws from \"crossws\u002Fadapters\u002F\u003Cadapter>\";\n\nimport { defineHooks } from \"crossws\";\n\nconst websocket = crossws({\n  async resolve(req) {\n    \u002F\u002F TODO: Resolve hooks based on req.url, req.headers\n    \u002F\u002F You can return undefined in case there is no match\n    return {\n      \u002F* resolved hooks *\u002F\n    };\n  },\n});\n","js","",[111,112,113,122,143,150,165,170,190,209,215,221,230,236,242,248],"code",{"__ignoreMap":109},[114,115,118],"span",{"class":116,"line":117},"line",1,[114,119,121],{"class":120},"sCsY4","\u002F\u002F https:\u002F\u002Fcrossws.h3.dev\u002Fadapters\n",[114,123,125,129,133,136,140],{"class":116,"line":124},2,[114,126,128],{"class":127},"so5gQ","import",[114,130,132],{"class":131},"slsVL"," crossws ",[114,134,135],{"class":127},"from",[114,137,139],{"class":138},"sfrk1"," \"crossws\u002Fadapters\u002F\u003Cadapter>\"",[114,141,142],{"class":131},";\n",[114,144,146],{"class":116,"line":145},3,[114,147,149],{"emptyLinePlaceholder":148},true,"\n",[114,151,153,155,158,160,163],{"class":116,"line":152},4,[114,154,128],{"class":127},[114,156,157],{"class":131}," { defineHooks } ",[114,159,135],{"class":127},[114,161,162],{"class":138}," \"crossws\"",[114,164,142],{"class":131},[114,166,168],{"class":116,"line":167},5,[114,169,149],{"emptyLinePlaceholder":148},[114,171,173,176,180,183,187],{"class":116,"line":172},6,[114,174,175],{"class":127},"const",[114,177,179],{"class":178},"suiK_"," websocket",[114,181,182],{"class":127}," =",[114,184,186],{"class":185},"shcOC"," crossws",[114,188,189],{"class":131},"({\n",[114,191,193,196,199,202,206],{"class":116,"line":192},7,[114,194,195],{"class":127},"  async",[114,197,198],{"class":185}," resolve",[114,200,201],{"class":131},"(",[114,203,205],{"class":204},"sQHwn","req",[114,207,208],{"class":131},") {\n",[114,210,212],{"class":116,"line":211},8,[114,213,214],{"class":120},"    \u002F\u002F TODO: Resolve hooks based on req.url, req.headers\n",[114,216,218],{"class":116,"line":217},9,[114,219,220],{"class":120},"    \u002F\u002F You can return undefined in case there is no match\n",[114,222,224,227],{"class":116,"line":223},10,[114,225,226],{"class":127},"    return",[114,228,229],{"class":131}," {\n",[114,231,233],{"class":116,"line":232},11,[114,234,235],{"class":120},"      \u002F* resolved hooks *\u002F\n",[114,237,239],{"class":116,"line":238},12,[114,240,241],{"class":131},"    };\n",[114,243,245],{"class":116,"line":244},13,[114,246,247],{"class":131},"  },\n",[114,249,251],{"class":116,"line":250},14,[114,252,253],{"class":131},"});\n",[94,255,256],{},"If you need to change resolve function (for cases like handling HMR):",[104,258,262],{"className":259,"code":260,"language":261,"meta":109,"style":109},"language-ts shiki shiki-themes github-light github-dark github-dark","let resolveWebSocketHooks = (req) => \u002F* ... *\u002F\n\nconst websocket = crossws({\n  async resolve(req) {\n    return resolveWebSocketHooks(req)\n  },\n});\n\n\u002F\u002F Update reference to `resolveWebSocketHooks` later.\n","ts",[111,263,264,288,292,304,316,325,329,333,337],{"__ignoreMap":109},[114,265,266,269,272,274,277,279,282,285],{"class":116,"line":117},[114,267,268],{"class":127},"let",[114,270,271],{"class":185}," resolveWebSocketHooks",[114,273,182],{"class":127},[114,275,276],{"class":131}," (",[114,278,205],{"class":204},[114,280,281],{"class":131},") ",[114,283,284],{"class":127},"=>",[114,286,287],{"class":120}," \u002F* ... *\u002F\n",[114,289,290],{"class":116,"line":124},[114,291,149],{"emptyLinePlaceholder":148},[114,293,294,296,298,300,302],{"class":116,"line":145},[114,295,175],{"class":127},[114,297,179],{"class":178},[114,299,182],{"class":127},[114,301,186],{"class":185},[114,303,189],{"class":131},[114,305,306,308,310,312,314],{"class":116,"line":152},[114,307,195],{"class":127},[114,309,198],{"class":185},[114,311,201],{"class":131},[114,313,205],{"class":204},[114,315,208],{"class":131},[114,317,318,320,322],{"class":116,"line":167},[114,319,226],{"class":127},[114,321,271],{"class":185},[114,323,324],{"class":131},"(req)\n",[114,326,327],{"class":116,"line":172},[114,328,247],{"class":131},[114,330,331],{"class":116,"line":192},[114,332,253],{"class":131},[114,334,335],{"class":116,"line":211},[114,336,149],{"emptyLinePlaceholder":148},[114,338,339],{"class":116,"line":217},[114,340,341],{"class":120},"\u002F\u002F Update reference to `resolveWebSocketHooks` later.\n",[343,344,346],"h2",{"id":345},"server-plugin","Server plugin",[94,348,349,350,356,357,360,361,365,366,369,370,373,374,377],{},"When using the ",[351,352,353],"a",{"href":6},[111,354,355],{},"crossws\u002Fserver"," plugin (srvx integration), ",[111,358,359],{},"resolve"," is ",[362,363,364],"strong",{},"optional",". If you omit it, hooks are resolved by calling the server's own ",[111,367,368],{},"fetch"," handler and reading the ",[111,371,372],{},"crossws"," property off the returned ",[111,375,376],{},"Response"," (the srvx convention for attaching WebSocket hooks):",[104,379,381],{"className":106,"code":380,"language":108,"meta":109,"style":109},"import { plugin as ws } from \"crossws\u002Fserver\";\n\n\u002F\u002F No `resolve` needed — hooks come from the app's fetch response `.crossws`.\nserve(app, { plugins: [ws()] });\n",[111,382,383,403,407,412],{"__ignoreMap":109},[114,384,385,387,390,393,396,398,401],{"class":116,"line":117},[114,386,128],{"class":127},[114,388,389],{"class":131}," { plugin ",[114,391,392],{"class":127},"as",[114,394,395],{"class":131}," ws } ",[114,397,135],{"class":127},[114,399,400],{"class":138}," \"crossws\u002Fserver\"",[114,402,142],{"class":131},[114,404,405],{"class":116,"line":124},[114,406,149],{"emptyLinePlaceholder":148},[114,408,409],{"class":116,"line":145},[114,410,411],{"class":120},"\u002F\u002F No `resolve` needed — hooks come from the app's fetch response `.crossws`.\n",[114,413,414,417,420,423],{"class":116,"line":152},[114,415,416],{"class":185},"serve",[114,418,419],{"class":131},"(app, { plugins: [",[114,421,422],{"class":185},"ws",[114,424,425],{"class":131},"()] });\n",[94,427,428,429,431],{},"Provide ",[111,430,359],{}," only to customize routing, e.g. to resolve hooks without invoking the app:",[104,433,435],{"className":106,"code":434,"language":108,"meta":109,"style":109},"serve(app, {\n  plugins: [ws({ resolve: (req) => resolveWebSocketHooks(req) })],\n});\n",[111,436,437,444,470],{"__ignoreMap":109},[114,438,439,441],{"class":116,"line":117},[114,440,416],{"class":185},[114,442,443],{"class":131},"(app, {\n",[114,445,446,449,451,454,456,459,461,463,465,467],{"class":116,"line":124},[114,447,448],{"class":131},"  plugins: [",[114,450,422],{"class":185},[114,452,453],{"class":131},"({ ",[114,455,359],{"class":185},[114,457,458],{"class":131},": (",[114,460,205],{"class":204},[114,462,281],{"class":131},[114,464,284],{"class":127},[114,466,271],{"class":185},[114,468,469],{"class":131},"(req) })],\n",[114,471,472],{"class":116,"line":145},[114,473,253],{"class":131},[94,475,476,478,479,482,483,485],{},[111,477,359],{}," is invoked ",[362,480,481],{},"once per connection"," — its result is cached against the connection for the lifetime of the peer — so the app's ",[111,484,368],{}," runs on connect, not on every message.",[487,488,489],"note",{},[94,490,491,492,495],{},"\nPassing inline hooks directly (",[111,493,494],{},"ws({ message })",") opts out of the fetch-based default: those hooks run with zero per-event overhead instead.",[497,498,500,501,503],"h3",{"id":499},"making-your-fetch-handler-upgrade-aware","Making your ",[111,502,368],{}," handler upgrade-aware",[94,505,506,507,509,510,513],{},"With the default resolver, your app's ",[111,508,368],{}," handler is called with the raw WebSocket ",[362,511,512],{},"upgrade request"," on connect. Keep two things in mind:",[515,516,517,913,927],"ul",{},[518,519,520,527,528,530,531,533,534,537,538,541,542,545,546,549,550,553,554,558,559,561,562,564,565,568,569,571,572,771,774,775,778,779,781,782,785,786,883],"li",{},[362,521,522,523,526],{},"Attach hooks via ",[111,524,525],{},".crossws","."," Hooks are read from the ",[111,529,372],{}," property of the value returned for the upgrade request. A non-",[111,532,372],{}," ",[362,535,536],{},"error or redirect"," response (any status that is not ",[111,539,540],{},"2xx"," or ",[111,543,544],{},"101",", e.g. ",[111,547,548],{},"new Response(\"Unauthorized\", { status: 401 })"," or a ",[111,551,552],{},"302",") is sent back to the client and the handshake is rejected — handy for auth. A ",[362,555,556],{},[111,557,540],{}," (or ",[111,560,544],{},") response without ",[111,563,525],{}," still completes the handshake but with ",[362,566,567],{},"no hooks attached",", so every message is silently dropped. Make sure the upgrade path returns hooks, either on a ",[111,570,376],{},":",[104,573,575],{"className":106,"code":574,"language":108,"meta":109,"style":109},"import { defineHooks } from \"crossws\";\n\nconst hooks = defineHooks({\n  message(peer, message) {\n    peer.send(message.text());\n  },\n});\n\nfunction fetch(req) {\n  if (req.headers.get(\"upgrade\") === \"websocket\") {\n    const res = new Response(null);\n    res.crossws = hooks; \u002F\u002F opt this connection into WebSocket handling\n    return res;\n  }\n  return new Response(\"Hello!\"); \u002F\u002F normal HTTP response\n}\n",[111,576,577,589,593,607,625,642,646,650,654,668,694,718,732,739,744,765],{"__ignoreMap":109},[114,578,579,581,583,585,587],{"class":116,"line":117},[114,580,128],{"class":127},[114,582,157],{"class":131},[114,584,135],{"class":127},[114,586,162],{"class":138},[114,588,142],{"class":131},[114,590,591],{"class":116,"line":124},[114,592,149],{"emptyLinePlaceholder":148},[114,594,595,597,600,602,605],{"class":116,"line":145},[114,596,175],{"class":127},[114,598,599],{"class":178}," hooks",[114,601,182],{"class":127},[114,603,604],{"class":185}," defineHooks",[114,606,189],{"class":131},[114,608,609,612,614,617,620,623],{"class":116,"line":152},[114,610,611],{"class":185},"  message",[114,613,201],{"class":131},[114,615,616],{"class":204},"peer",[114,618,619],{"class":131},", ",[114,621,622],{"class":204},"message",[114,624,208],{"class":131},[114,626,627,630,633,636,639],{"class":116,"line":167},[114,628,629],{"class":131},"    peer.",[114,631,632],{"class":185},"send",[114,634,635],{"class":131},"(message.",[114,637,638],{"class":185},"text",[114,640,641],{"class":131},"());\n",[114,643,644],{"class":116,"line":172},[114,645,247],{"class":131},[114,647,648],{"class":116,"line":192},[114,649,253],{"class":131},[114,651,652],{"class":116,"line":211},[114,653,149],{"emptyLinePlaceholder":148},[114,655,656,659,662,664,666],{"class":116,"line":217},[114,657,658],{"class":127},"function",[114,660,661],{"class":185}," fetch",[114,663,201],{"class":131},[114,665,205],{"class":204},[114,667,208],{"class":131},[114,669,670,673,676,679,681,684,686,689,692],{"class":116,"line":223},[114,671,672],{"class":127},"  if",[114,674,675],{"class":131}," (req.headers.",[114,677,678],{"class":185},"get",[114,680,201],{"class":131},[114,682,683],{"class":138},"\"upgrade\"",[114,685,281],{"class":131},[114,687,688],{"class":127},"===",[114,690,691],{"class":138}," \"websocket\"",[114,693,208],{"class":131},[114,695,696,699,702,704,707,710,712,715],{"class":116,"line":232},[114,697,698],{"class":127},"    const",[114,700,701],{"class":178}," res",[114,703,182],{"class":127},[114,705,706],{"class":127}," new",[114,708,709],{"class":185}," Response",[114,711,201],{"class":131},[114,713,714],{"class":178},"null",[114,716,717],{"class":131},");\n",[114,719,720,723,726,729],{"class":116,"line":238},[114,721,722],{"class":131},"    res.crossws ",[114,724,725],{"class":127},"=",[114,727,728],{"class":131}," hooks; ",[114,730,731],{"class":120},"\u002F\u002F opt this connection into WebSocket handling\n",[114,733,734,736],{"class":116,"line":244},[114,735,226],{"class":127},[114,737,738],{"class":131}," res;\n",[114,740,741],{"class":116,"line":250},[114,742,743],{"class":131},"  }\n",[114,745,747,750,752,754,756,759,762],{"class":116,"line":746},15,[114,748,749],{"class":127},"  return",[114,751,706],{"class":127},[114,753,709],{"class":185},[114,755,201],{"class":131},[114,757,758],{"class":138},"\"Hello!\"",[114,760,761],{"class":131},"); ",[114,763,764],{"class":120},"\u002F\u002F normal HTTP response\n",[114,766,768],{"class":116,"line":767},16,[114,769,770],{"class":131},"}\n",[772,773],"br",{},"…or, as a shortcut for the upgrade path, a plain object ",[111,776,777],{},"{ crossws, headers }"," (no ",[111,780,376],{}," needed). Any ",[111,783,784],{},"headers"," are sent on the WebSocket handshake response:",[104,787,789],{"className":106,"code":788,"language":108,"meta":109,"style":109},"function fetch(req) {\n  if (req.headers.get(\"upgrade\") === \"websocket\") {\n    return {\n      crossws: hooks,\n      headers: { \"x-powered-by\": \"crossws\" }, \u002F\u002F optional handshake headers\n    };\n  }\n  return new Response(\"Hello!\"); \u002F\u002F normal HTTP response — must be a Response\n}\n",[111,790,791,803,823,829,834,854,858,862,879],{"__ignoreMap":109},[114,792,793,795,797,799,801],{"class":116,"line":117},[114,794,658],{"class":127},[114,796,661],{"class":185},[114,798,201],{"class":131},[114,800,205],{"class":204},[114,802,208],{"class":131},[114,804,805,807,809,811,813,815,817,819,821],{"class":116,"line":124},[114,806,672],{"class":127},[114,808,675],{"class":131},[114,810,678],{"class":185},[114,812,201],{"class":131},[114,814,683],{"class":138},[114,816,281],{"class":131},[114,818,688],{"class":127},[114,820,691],{"class":138},[114,822,208],{"class":131},[114,824,825,827],{"class":116,"line":145},[114,826,226],{"class":127},[114,828,229],{"class":131},[114,830,831],{"class":116,"line":152},[114,832,833],{"class":131},"      crossws: hooks,\n",[114,835,836,839,842,845,848,851],{"class":116,"line":167},[114,837,838],{"class":131},"      headers: { ",[114,840,841],{"class":138},"\"x-powered-by\"",[114,843,844],{"class":131},": ",[114,846,847],{"class":138},"\"crossws\"",[114,849,850],{"class":131}," }, ",[114,852,853],{"class":120},"\u002F\u002F optional handshake headers\n",[114,855,856],{"class":116,"line":172},[114,857,241],{"class":131},[114,859,860],{"class":116,"line":192},[114,861,743],{"class":131},[114,863,864,866,868,870,872,874,876],{"class":116,"line":211},[114,865,749],{"class":127},[114,867,706],{"class":127},[114,869,709],{"class":185},[114,871,201],{"class":131},[114,873,758],{"class":138},[114,875,761],{"class":131},[114,877,878],{"class":120},"\u002F\u002F normal HTTP response — must be a Response\n",[114,880,881],{"class":116,"line":217},[114,882,770],{"class":131},[884,885,886],"blockquote",{},[94,887,888,891,892,894,895,897,898,901,902,904,905,908,909,912],{},[114,889,890],{},"!NOTE","\nThe plain-object form is only for upgrade requests. Non-upgrade paths must still return a real ",[111,893,376],{},". An HTTP ",[111,896,376],{},"'s own headers are ",[362,899,900],{},"not"," applied to the handshake — use the ",[111,903,777],{}," shortcut (or an ",[111,906,907],{},"upgrade"," hook returning ",[111,910,911],{},"{ headers }",") to set handshake headers.",[518,914,915,920,921,923,924,926],{},[362,916,917,919],{},[111,918,368],{}," runs per connection, so keep it side-effect-safe."," Any auth, logging, metrics, or database work in ",[111,922,368],{}," now executes once for every WebSocket upgrade. If a code path must run only for regular HTTP requests, branch on the ",[111,925,907],{}," header first.",[518,928,929,932,933,935,936,938,939,942,943,947,948,533,950,952],{},[362,930,931],{},"Throwing\u002Frejecting fails the handshake."," If ",[111,934,368],{}," throws (or rejects) on the upgrade request, the WebSocket handshake is rejected rather than left hanging. Throw a ",[111,937,376],{}," to control the rejection; otherwise the client sees a ",[111,940,941],{},"500",". To reject an upgrade ",[944,945,946],"em",{},"gracefully",", prefer returning a non-",[111,949,540],{},[111,951,376],{}," (which is rendered to the client) over throwing.",[954,955,956],"style",{},"html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":109,"searchDepth":124,"depth":124,"links":958},[959],{"id":345,"depth":124,"text":346,"children":960},[961],{"id":499,"depth":145,"text":962},"Making your fetch handler upgrade-aware","md",{"icon":40},{"icon":40},{"title":37,"description":109},"-SvjLr_AAgMJhZx5Q_F8otlqHJTSkRWpy7AGhZeaE9I",[969,970],{"title":32,"path":33,"stem":34,"description":109,"icon":35,"children":-1},{"title":42,"path":43,"stem":44,"description":971,"icon":45,"children":-1},"Forward incoming WebSocket connections to an upstream ws:\u002F\u002F or wss:\u002F\u002F target.",1783073589554]