1"use strict";(self.webpackChunksample_website=self.webpackChunksample_website||[]).push([[772],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>h});var o=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function i(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?a(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):a(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function s(e,t){if(null==e)return{};var n,o,r=function(e,t){if(null==e)return{};var n,o,r={},a=Object.keys(e);for(o=0;o<a.length;o++)n=a[o],t.indexOf(n)>=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(o=0;o<a.length;o++)n=a[o],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var c=o.createContext({}),l=function(e){var t=o.useContext(c),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},d=function(e){var t=l(e.components);return o.createElement(c.Provider,{value:t},e.children)},p="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},u=o.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,c=e.parentName,d=s(e,["components","mdxType","originalType","parentName"]),p=l(n),u=r,h=p["".concat(c,".").concat(u)]||p[u]||m[u]||a;return n?o.createElement(h,i(i({ref:t},d),{},{components:n})):o.createElement(h,i({ref:t},d))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,i=new Array(a);i[0]=u;var s={};for(var c in t)hasOwnProperty.call(t,c)&&(s[c]=t[c]);s.originalType=e,s[p]="string"==typeof e?e:r,i[1]=s;for(var l=2;l<a;l++)i[l]=n[l];return o.createElement.apply(null,i)}return o.createElement.apply(null,n)}u.displayName="MDXCreateElement"},9745:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var o=n(3117),r=(n(7294),n(3905));const a={title:"Connection state recovery",sidebar_position:4,slug:"/connection-state-recovery"},i=void 0,s={unversionedId:"categories/Documentation/connection-state-recovery",id:"categories/Documentation/connection-state-recovery",title:"Connection state recovery",description:"Connection state recovery is a feature which allows restoring a client's state after a temporary disconnection, including any missed packets.",source:"@site/docs/categories/01-Documentation/connection-state-recovery.md",sourceDirName:"categories/01-Documentation",slug:"/connection-state-recovery",permalink:"/docs/v4/connection-state-recovery",draft:!1,editUrl:"https://github.com/socketio/socket.io-website/edit/main/docs/categories/01-Documentation/connection-state-recovery.md",tags:[],version:"current",lastUpdatedAt:1784117104,formattedLastUpdatedAt:"Jul 15, 2026",sidebarPosition:4,frontMatter:{title:"Connection state recovery",sidebar_position:4,slug:"/connection-state-recovery"},sidebar:"sidebar",previous:{title:"Delivery guarantees",permalink:"/docs/v4/delivery-guarantees"},next:{title:"Logging and debugging",permalink:"/docs/v4/logging-and-debugging/"}},c={},l=[{value:"Disclaimer",id:"disclaimer",level:2},{value:"Usage",id:"usage",level:2},{value:"<code>skipMiddlewares</code> option",id:"skipmiddlewares-option",level:2},{value:"Compatibility with existing adapters",id:"compatibility-with-existing-adapters",level:2},{value:"How it works under the hood",id:"how-it-works-under-the-hood",level:2}],d={toc:l},p="wrapper";
1function m(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,o.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Connection state recovery is a feature which allows restoring a client's state after a temporary disconnection, including any missed packets."),(0,r.kt)("h2",{id:"disclaimer"},"Disclaimer"),(0,r.kt)("p",null,"Under real conditions, a Socket.IO client will inevitably experience temporary disconnections, regardless of the quality of the connection."),(0,r.kt)("p",null,"This feature will help you cope with such disconnections, but please be aware that the recovery ",(0,r.kt)("strong",{parentName:"p"},"will not always be successful"),". That's why you will still need to handle the case where the states of the client and the server must be synchronized."),(0,r.kt)("h2",{id:"usage"},"Usage"),(0,r.kt)("p",null,"The server must explicitly enable connection state recovery:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-js"},"const io = new Server(httpServer, {\n connectionStateRecovery: {\n // the backup duration of the sessions and the packets\n maxDisconnectionDuration: 2 * 60 * 1000,\n // whether to skip middlewares upon successful recovery\n skipMiddlewares: false,\n }\n});\n")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"The connection state recovery feature is designed for dealing with intermittent disconnections, so please use a sensible value for ",(0,r.kt)("inlineCode",{parentName:"p"},"maxDisconnectionDuration")," (that is, not ",(0,r.kt)("inlineCode",{parentName:"p"},"Infinity"),").")),(0,r.kt)("p",null,"Upon an unexpected disconnection (i.e. no manual disconnection with ",(0,r.kt)("inlineCode",{parentName:"p"},"socket.disconnect()"),"), the server will store the ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),", the rooms and the ",(0,r.kt)("inlineCode",{parentName:"p"},"data")," attribute of the socket."),(0,r.kt)("p",null,"Then upon reconnection, the server will try to restore the state of the client. The ",(0,r.kt)("inlineCode",{parentName:"p"},"recovered")," attribute indicates whether this recovery was successful:"),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"Server")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-js"},'io.on("connection", (socket) => {\n if (socket.recovered) {\n // recovery was successful: socket.id, socket.rooms and socket.data were restored\n } else {\n // new or unrecoverable session\n }\n});\n')),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"Client")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-js"},'socket.on("connect", () => {\n if (socket.recovered) {\n // any event missed during the disconnection period will be received now\n } else {\n // new or unrecoverable session\n }\n});\n')),(0,r.kt)("p",null,"You can check that the recovery is working by forcefully closing the underlying engine:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-js"},'import { io } from "socket.io-client";\n\nconst socket = io({\n reconnectionDelay: 10000, // defaults to 1000\n reconnectionDelayMax: 10000 // defaults to 5000\n});\n\nsocket.on("connect", () => {\n console.log("recovered?", socket.recovered);\n\n setTimeout(() => {\n if (socket.io.engine) {\n // close the low-level connection and trigger a reconnection\n socket.io.engine.close();\n }\n }, 10000);\n});\n')),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"You can also run this example directly in your browser on:"),(0,r.kt)("ul",{parentName:"admonition"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://codesandbox.io/p/sandbox/github/socketio/socket.io/tree/main/examples/connection-state-recovery-example/esm?file=index.js"},"CodeSandbox")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://stackblitz.com/github/socketio/socket.io/tree/main/examples/connection-state-recovery-example/esm?file=index.js"},"StackBlitz")))),(0,r.kt)("h2",{id:"skipmiddlewares-option"},(0,r.kt)("inlineCode",{parentName:"h2"},"skipMiddlewares")," option"),(0,r.kt)("p",null,"If the ",(0,r.kt)("inlineCode",{parentName:"p"},"skipMiddlewares")," option is set to ",(0,r.kt)("inlineCode",{parentName:"p"},"true"),", then the middlewares will be skipped when the connection is successfully recovered:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-js"},'function computeUserIdFromHeaders(headers) {\n // to be implemented\n}\n\n// this middleware will be skipped if the connection is successfully recovered\nio.use(async (socket, next) => {\n socket.data.userId = await computeUserIdFromHeaders(socket.handshake.headers);\n\n next();\n});\n\nio.on("connection", (socket) => {\n // the userId attribute will either come:\n // - from the middleware above (first connection or failed recovery)\n // - from the recovery mechanism\n console.log("userId", socket.data.userId);\n});\n')),(0,r.kt)("admonition",{type:"caution"},(0,r.kt)("p",{parentName:"admonition"},"This might, for example, allow users blocked during the disconnection period to reconnect without going through the middleware validations. So please use this option with caution.")),(0,r.kt)("h2",{id:"compatibility-with-existing-adapter
1s"},"Compatibility with existing adapters"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Adapter"),(0,r.kt)("th",{parentName:"tr",align:"center"},"Support?"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Built-in adapter (in memory)"),(0,r.kt)("td",{parentName:"tr",align:"center"},"YES \u2705")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/docs/v4/redis-adapter/"},"Redis adapter")),(0,r.kt)("td",{parentName:"tr",align:"center"},"NO",(0,r.kt)("sup",null,"1"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/docs/v4/redis-streams-adapter/"},"Redis Streams adapter")),(0,r.kt)("td",{parentName:"tr",align:"center"},"YES \u2705")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/docs/v4/mongo-adapter/"},"MongoDB adapter")),(0,r.kt)("td",{parentName:"tr",align:"center"},"YES \u2705 (since version ",(0,r.kt)("a",{parentName:"td",href:"https://github.com/socketio/socket.io-mongo-adapter/releases/tag/0.3.0"},(0,r.kt)("inlineCode",{parentName:"a"},"0.3.0")),")")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/docs/v4/postgres-adapter/"},"Postgres adapter")),(0,r.kt)("td",{parentName:"tr",align:"center"},"WIP")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"/docs/v4/cluster-adapter/"},"Cluster adapter")),(0,r.kt)("td",{parentName:"tr",align:"center"},"WIP")))),(0,r.kt)("p",null,"[1]"," Persisting the packets is not compatible with the Redis PUB/SUB mechanism."),(0,r.kt)("h2",{id:"how-it-works-under-the-hood"},"How it works under the hood"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"the server sends a session ID ",(0,r.kt)("a",{parentName:"li",href:"/docs/v4/socket-io-protocol/#connection-to-a-namespace-1"},"during the handshake")," (which is different from the existing ",(0,r.kt)("inlineCode",{parentName:"li"},"id")," attribute, which is public and can be freely shared)")),(0,r.kt)("p",null,"Example:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},'40{"sid":"GNpWD7LbGCBNCr8GAAAB","pid":"YHcX2sdAF1z452-HAAAW"}\n\nwhere\n\n4 => the Engine.IO message type\n0 => the Socket.IO CONNECT type\nGN...AB => the public id of the session\nYH...AW => the private id of the session\n')),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"the server also includes an offset in ",(0,r.kt)("a",{parentName:"li",href:"/docs/v4/socket-io-protocol/#sending-and-receiving-data-1"},"each packet")," (added at the end of the data array, for backward compatibility)")),(0,r.kt)("p",null,"Example:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},'42["foo","MzUPkW0"]\n\nwhere\n\n4 => the Engine.IO message type\n2 => the Socket.IO EVENT type\nfoo => the event name (socket.emit("foo"))\nMzUPkW0 => the offset\n')),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"For the recovery to succeed, the server must send at least one event, in order to initialize the offset on the client side.")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("p",{parentName:"li"},"upon temporary disconnection, the server stores the client state for a given delay (implemented at the adapter level)")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("p",{parentName:"li"},"upon reconnection, the client sends both the session ID and the last offset it has processed, and the server tries to restore the state"))),(0,r.kt)("p",null,"Example:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},'40{"pid":"YHcX2sdAF1z452-HAAAW","offset":"MzUPkW0"}\n\nwhere\n\n4 => the Engine.IO message type\n0 => the Socket.IO CONNECT type\nYH...AW => the private id of the session\nMzUPkW0 => the last processed offset\n')))}m.isMDXComponent=!0}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.