[{"data":1,"prerenderedAt":3280},["ShallowReactive",2],{"docs:/docs/concepts/presence":3},{"id":4,"title":5,"body":6,"description":3273,"extension":3274,"meta":3275,"navigation":165,"path":3276,"seo":3277,"stem":3278,"__hash__":3279},"docs/docs/concepts/presence.md","Presence Tracking",{"type":7,"value":8,"toc":3250},"minimark",[9,13,17,22,25,31,47,52,76,120,124,127,855,859,862,882,895,900,909,913,916,1259,1263,1311,1314,1318,1325,1533,1537,1540,2121,2125,2128,2226,2274,2278,2281,2296,2299,2459,2462,2571,2575,2578,2619,2622,2625,2628,2631,2643,2647,2656,2847,2851,2857,2902,2910,2913,3018,3027,3031,3041,3127,3130,3136,3172,3178,3182,3217,3221,3246],[10,11,5],"h1",{"id":12},"presence-tracking",[14,15,16],"p",{},"Know who's online in real-time. Track presence per room and handle join/leave events.",[18,19,21],"h2",{"id":20},"what-is-presence","What is Presence?",[14,23,24],{},"Presence tracking allows you to see which actors (users, devices, services, or AI\nagents) are currently connected to a room. It covers two distinct jobs.",[14,26,27],{},[28,29,30],"strong",{},"For people:",[32,33,34,38,41,44],"ul",{},[35,36,37],"li",{},"Online/offline indicators in chat apps",[35,39,40],{},"Showing who's viewing a document",[35,42,43],{},"Live user counts",[35,45,46],{},"Typing indicators",[14,48,49],{},[28,50,51],{},"For backend workers, devices, and AI agents:",[32,53,54,57,60,68],{},[35,55,56],{},"Service discovery: which workers are up, and what each can do",[35,58,59],{},"Routing work to an actor that advertises the right capability",[35,61,62,63,67],{},"Detecting a worker that dropped, without polling or a ",[64,65,66],"code",{},"last_seen"," table",[35,69,70,71],{},"Keeping a scaled-to-zero service or sleeping device addressable, and waking it\non demand, with ",[72,73,75],"a",{"href":74},"#persistent-presence","Persistent Presence",[77,78,80],"callout",{"type":79},"info",[14,81,82,85,86,89,90,93,94,97,98,102,103,106,107,110,111,114,115,119],{},[28,83,84],{},"Room-scoped."," Presence is set on a room: ",[64,87,88],{},"room.setPresence(data)",". The broker only tracks and broadcasts presence that names a room. The older client-level ",[64,91,92],{},"client.setPresence()"," without a room is deprecated: the broker accepts the frame but never broadcasts it, and ",[64,95,96],{},"client.fetchPresence()"," without a room always returns an empty list. Presence ",[99,100,101],"em",{},"events"," are delivered on the client, ",[64,104,105],{},"client.on('presence:join', ...)",", because ",[64,108,109],{},"room.on()"," is for topic messages and ",[64,112,113],{},"room.on('presence:join', ...)"," never fires. To observe presence across many rooms at once, use ",[72,116,118],{"href":117},"/docs/concepts/lobbies","Lobbies",".",[18,121,123],{"id":122},"setting-presence","Setting Presence",[14,125,126],{},"Set your presence on a room after connecting. Presence data can include any custom fields you need:",[128,129,130,512,648],"code-tabs",{},[131,132,138],"pre",{"className":133,"code":134,"filename":135,"language":136,"meta":137,"style":137},"language-typescript shiki shiki-themes github-light github-dark","import { NoLag } from '@nolag/js-sdk'\n\nconst client = NoLag('your_access_token')\nawait client.connect()\n\nconst room = client.setApp(APP_SLUG).setRoom('general')\n\n// Set presence in the room\nroom.setPresence({\n  username: 'Alice',\n  status: 'online',\n  avatar: 'https://example.com/alice.jpg'\n})\n\n// Presence events are delivered on the client\nclient.on('presence:join', (actor) => {\n  console.log(`${actor.presence.username} joined`)\n})\n\nclient.on('presence:leave', (actor) => {\n  console.log(`${actor.actorTokenId} left`)\n})\n\nclient.on('presence:update', (actor) => {\n  console.log(`${actor.presence.username} updated status to ${actor.presence.status}`)\n})\n","TypeScript","typescript","",[64,139,140,160,167,193,208,213,246,251,258,270,282,293,302,308,313,319,349,380,385,390,412,435,440,445,467,507],{"__ignoreMap":137},[141,142,145,149,153,156],"span",{"class":143,"line":144},"line",1,[141,146,148],{"class":147},"szBVR","import",[141,150,152],{"class":151},"sVt8B"," { NoLag } ",[141,154,155],{"class":147},"from",[141,157,159],{"class":158},"sZZnC"," '@nolag/js-sdk'\n",[141,161,163],{"class":143,"line":162},2,[141,164,166],{"emptyLinePlaceholder":165},true,"\n",[141,168,170,173,177,180,184,187,190],{"class":143,"line":169},3,[141,171,172],{"class":147},"const",[141,174,176],{"class":175},"sj4cs"," client",[141,178,179],{"class":147}," =",[141,181,183],{"class":182},"sScJk"," NoLag",[141,185,186],{"class":151},"(",[141,188,189],{"class":158},"'your_access_token'",[141,191,192],{"class":151},")\n",[141,194,196,199,202,205],{"class":143,"line":195},4,[141,197,198],{"class":147},"await",[141,200,201],{"class":151}," client.",[141,203,204],{"class":182},"connect",[141,206,207],{"class":151},"()\n",[141,209,211],{"class":143,"line":210},5,[141,212,166],{"emptyLinePlaceholder":165},[141,214,216,218,221,223,225,228,230,233,236,239,241,244],{"class":143,"line":215},6,[141,217,172],{"class":147},[141,219,220],{"class":175}," room",[141,222,179],{"class":147},[141,224,201],{"class":151},[141,226,227],{"class":182},"setApp",[141,229,186],{"class":151},[141,231,232],{"class":175},"APP_SLUG",[141,234,235],{"class":151},").",[141,237,238],{"class":182},"setRoom",[141,240,186],{"class":151},[141,242,243],{"class":158},"'general'",[141,245,192],{"class":151},[141,247,249],{"class":143,"line":248},7,[141,250,166],{"emptyLinePlaceholder":165},[141,252,254],{"class":143,"line":253},8,[141,255,257],{"class":256},"sJ8bj","// Set presence in the room\n",[141,259,261,264,267],{"class":143,"line":260},9,[141,262,263],{"class":151},"room.",[141,265,266],{"class":182},"setPresence",[141,268,269],{"class":151},"({\n",[141,271,273,276,279],{"class":143,"line":272},10,[141,274,275],{"class":151},"  username: ",[141,277,278],{"class":158},"'Alice'",[141,280,281],{"class":151},",\n",[141,283,285,288,291],{"class":143,"line":284},11,[141,286,287],{"class":151},"  status: ",[141,289,290],{"class":158},"'online'",[141,292,281],{"class":151},[141,294,296,299],{"class":143,"line":295},12,[141,297,298],{"class":151},"  avatar: ",[141,300,301],{"class":158},"'https://example.com/alice.jpg'\n",[141,303,305],{"class":143,"line":304},13,[141,306,307],{"class":151},"})\n",[141,309,311],{"class":143,"line":310},14,[141,312,166],{"emptyLinePlaceholder":165},[141,314,316],{"class":143,"line":315},15,[141,317,318],{"class":256},"// Presence events are delivered on the client\n",[141,320,322,325,328,330,333,336,340,343,346],{"class":143,"line":321},16,[141,323,324],{"class":151},"client.",[141,326,327],{"class":182},"on",[141,329,186],{"class":151},[141,331,332],{"class":158},"'presence:join'",[141,334,335],{"class":151},", (",[141,337,339],{"class":338},"s4XuR","actor",[141,341,342],{"class":151},") ",[141,344,345],{"class":147},"=>",[141,347,348],{"class":151}," {\n",[141,350,352,355,358,360,363,365,367,370,372,375,378],{"class":143,"line":351},17,[141,353,354],{"class":151},"  console.",[141,356,357],{"class":182},"log",[141,359,186],{"class":151},[141,361,362],{"class":158},"`${",[141,364,339],{"class":151},[141,366,119],{"class":158},[141,368,369],{"class":151},"presence",[141,371,119],{"class":158},[141,373,374],{"class":151},"username",[141,376,377],{"class":158},"} joined`",[141,379,192],{"class":151},[141,381,383],{"class":143,"line":382},18,[141,384,307],{"class":151},[141,386,388],{"class":143,"line":387},19,[141,389,166],{"emptyLinePlaceholder":165},[141,391,393,395,397,399,402,404,406,408,410],{"class":143,"line":392},20,[141,394,324],{"class":151},[141,396,327],{"class":182},[141,398,186],{"class":151},[141,400,401],{"class":158},"'presence:leave'",[141,403,335],{"class":151},[141,405,339],{"class":338},[141,407,342],{"class":151},[141,409,345],{"class":147},[141,411,348],{"class":151},[141,413,415,417,419,421,423,425,427,430,433],{"class":143,"line":414},21,[141,416,354],{"class":151},[141,418,357],{"class":182},[141,420,186],{"class":151},[141,422,362],{"class":158},[141,424,339],{"class":151},[141,426,119],{"class":158},[141,428,429],{"class":151},"actorTokenId",[141,431,432],{"class":158},"} left`",[141,434,192],{"class":151},[141,436,438],{"class":143,"line":437},22,[141,439,307],{"class":151},[141,441,443],{"class":143,"line":442},23,[141,444,166],{"emptyLinePlaceholder":165},[141,446,448,450,452,454,457,459,461,463,465],{"class":143,"line":447},24,[141,449,324],{"class":151},[141,451,327],{"class":182},[141,453,186],{"class":151},[141,455,456],{"class":158},"'presence:update'",[141,458,335],{"class":151},[141,460,339],{"class":338},[141,462,342],{"class":151},[141,464,345],{"class":147},[141,466,348],{"class":151},[141,468,470,472,474,476,478,480,482,484,486,488,491,493,495,497,499,502,505],{"class":143,"line":469},25,[141,471,354],{"class":151},[141,473,357],{"class":182},[141,475,186],{"class":151},[141,477,362],{"class":158},[141,479,339],{"class":151},[141,481,119],{"class":158},[141,483,369],{"class":151},[141,485,119],{"class":158},[141,487,374],{"class":151},[141,489,490],{"class":158},"} updated status to ${",[141,492,339],{"class":151},[141,494,119],{"class":158},[141,496,369],{"class":151},[141,498,119],{"class":158},[141,500,501],{"class":151},"status",[141,503,504],{"class":158},"}`",[141,506,192],{"class":151},[141,508,510],{"class":143,"line":509},26,[141,511,307],{"class":151},[131,513,518],{"className":514,"code":515,"filename":516,"language":517,"meta":137,"style":137},"language-python shiki shiki-themes github-light github-dark","from nolag import NoLag\n\nclient = NoLag('your_access_token')\nawait client.connect()\n\nroom = client.set_app(APP_SLUG).set_room('general')\n\n# Set presence in the room\nawait room.set_presence({\n    'username': 'Alice',\n    'status': 'online',\n    'avatar': 'https://example.com/alice.jpg'\n})\n\n# Presence events are delivered on the client\ndef on_join(actor):\n    print(f\"{actor.presence['username']} joined\")\n\ndef on_leave(actor):\n    print(f\"{actor.actor_token_id} left\")\n\ndef on_update(actor):\n    print(f\"{actor.presence['username']} updated status\")\n\nclient.on('presence:join', on_join)\nclient.on('presence:leave', on_leave)\nclient.on('presence:update', on_update)\n","Python","python",[64,519,520,525,529,534,539,543,548,552,557,562,567,572,577,581,585,590,595,600,604,609,614,618,623,628,632,637,642],{"__ignoreMap":137},[141,521,522],{"class":143,"line":144},[141,523,524],{},"from nolag import NoLag\n",[141,526,527],{"class":143,"line":162},[141,528,166],{"emptyLinePlaceholder":165},[141,530,531],{"class":143,"line":169},[141,532,533],{},"client = NoLag('your_access_token')\n",[141,535,536],{"class":143,"line":195},[141,537,538],{},"await client.connect()\n",[141,540,541],{"class":143,"line":210},[141,542,166],{"emptyLinePlaceholder":165},[141,544,545],{"class":143,"line":215},[141,546,547],{},"room = client.set_app(APP_SLUG).set_room('general')\n",[141,549,550],{"class":143,"line":248},[141,551,166],{"emptyLinePlaceholder":165},[141,553,554],{"class":143,"line":253},[141,555,556],{},"# Set presence in the room\n",[141,558,559],{"class":143,"line":260},[141,560,561],{},"await room.set_presence({\n",[141,563,564],{"class":143,"line":272},[141,565,566],{},"    'username': 'Alice',\n",[141,568,569],{"class":143,"line":284},[141,570,571],{},"    'status': 'online',\n",[141,573,574],{"class":143,"line":295},[141,575,576],{},"    'avatar': 'https://example.com/alice.jpg'\n",[141,578,579],{"class":143,"line":304},[141,580,307],{},[141,582,583],{"class":143,"line":310},[141,584,166],{"emptyLinePlaceholder":165},[141,586,587],{"class":143,"line":315},[141,588,589],{},"# Presence events are delivered on the client\n",[141,591,592],{"class":143,"line":321},[141,593,594],{},"def on_join(actor):\n",[141,596,597],{"class":143,"line":351},[141,598,599],{},"    print(f\"{actor.presence['username']} joined\")\n",[141,601,602],{"class":143,"line":382},[141,603,166],{"emptyLinePlaceholder":165},[141,605,606],{"class":143,"line":387},[141,607,608],{},"def on_leave(actor):\n",[141,610,611],{"class":143,"line":392},[141,612,613],{},"    print(f\"{actor.actor_token_id} left\")\n",[141,615,616],{"class":143,"line":414},[141,617,166],{"emptyLinePlaceholder":165},[141,619,620],{"class":143,"line":437},[141,621,622],{},"def on_update(actor):\n",[141,624,625],{"class":143,"line":442},[141,626,627],{},"    print(f\"{actor.presence['username']} updated status\")\n",[141,629,630],{"class":143,"line":447},[141,631,166],{"emptyLinePlaceholder":165},[141,633,634],{"class":143,"line":469},[141,635,636],{},"client.on('presence:join', on_join)\n",[141,638,639],{"class":143,"line":509},[141,640,641],{},"client.on('presence:leave', on_leave)\n",[141,643,645],{"class":143,"line":644},27,[141,646,647],{},"client.on('presence:update', on_update)\n",[131,649,654],{"className":650,"code":651,"filename":652,"language":653,"meta":137,"style":137},"language-go shiki shiki-themes github-light github-dark","package main\n\nimport (\n    \"fmt\"\n    \"log\"\n\n    nolag \"github.com/NoLagApp/go-sdk\"\n)\n\nfunc main() {\n    client := nolag.New(\"your_access_token\")\n    if err := client.Connect(); err != nil {\n        log.Fatal(err)\n    }\n\n    room := client.SetApp(appSlug).SetRoom(\"general\")\n\n    // Set presence in the room\n    room.SetPresence(map[string]any{\n        \"username\": \"Alice\",\n        \"status\":   \"online\",\n        \"avatar\":   \"https://example.com/alice.jpg\",\n    })\n\n    // Presence events are delivered on the client, one nolag.ActorPresence per event\n    client.On(\"presence:join\", func(args ...any) {\n        actor := args[0].(nolag.ActorPresence)\n        fmt.Printf(\"%v joined\\n\", actor.Presence[\"username\"])\n    })\n\n    client.On(\"presence:leave\", func(args ...any) {\n        actor := args[0].(nolag.ActorPresence)\n        fmt.Printf(\"%s left\\n\", actor.ActorTokenID)\n    })\n\n    client.On(\"presence:update\", func(args ...any) {\n        actor := args[0].(nolag.ActorPresence)\n        fmt.Printf(\"%v updated status to %v\\n\", actor.Presence[\"username\"], actor.Presence[\"status\"])\n    })\n}\n","Go","go",[64,655,656,661,665,670,675,680,684,689,693,697,702,707,712,717,722,726,731,735,740,745,750,755,760,765,769,774,779,784,790,795,800,806,811,817,822,827,833,838,844,849],{"__ignoreMap":137},[141,657,658],{"class":143,"line":144},[141,659,660],{},"package main\n",[141,662,663],{"class":143,"line":162},[141,664,166],{"emptyLinePlaceholder":165},[141,666,667],{"class":143,"line":169},[141,668,669],{},"import (\n",[141,671,672],{"class":143,"line":195},[141,673,674],{},"    \"fmt\"\n",[141,676,677],{"class":143,"line":210},[141,678,679],{},"    \"log\"\n",[141,681,682],{"class":143,"line":215},[141,683,166],{"emptyLinePlaceholder":165},[141,685,686],{"class":143,"line":248},[141,687,688],{},"    nolag \"github.com/NoLagApp/go-sdk\"\n",[141,690,691],{"class":143,"line":253},[141,692,192],{},[141,694,695],{"class":143,"line":260},[141,696,166],{"emptyLinePlaceholder":165},[141,698,699],{"class":143,"line":272},[141,700,701],{},"func main() {\n",[141,703,704],{"class":143,"line":284},[141,705,706],{},"    client := nolag.New(\"your_access_token\")\n",[141,708,709],{"class":143,"line":295},[141,710,711],{},"    if err := client.Connect(); err != nil {\n",[141,713,714],{"class":143,"line":304},[141,715,716],{},"        log.Fatal(err)\n",[141,718,719],{"class":143,"line":310},[141,720,721],{},"    }\n",[141,723,724],{"class":143,"line":315},[141,725,166],{"emptyLinePlaceholder":165},[141,727,728],{"class":143,"line":321},[141,729,730],{},"    room := client.SetApp(appSlug).SetRoom(\"general\")\n",[141,732,733],{"class":143,"line":351},[141,734,166],{"emptyLinePlaceholder":165},[141,736,737],{"class":143,"line":382},[141,738,739],{},"    // Set presence in the room\n",[141,741,742],{"class":143,"line":387},[141,743,744],{},"    room.SetPresence(map[string]any{\n",[141,746,747],{"class":143,"line":392},[141,748,749],{},"        \"username\": \"Alice\",\n",[141,751,752],{"class":143,"line":414},[141,753,754],{},"        \"status\":   \"online\",\n",[141,756,757],{"class":143,"line":437},[141,758,759],{},"        \"avatar\":   \"https://example.com/alice.jpg\",\n",[141,761,762],{"class":143,"line":442},[141,763,764],{},"    })\n",[141,766,767],{"class":143,"line":447},[141,768,166],{"emptyLinePlaceholder":165},[141,770,771],{"class":143,"line":469},[141,772,773],{},"    // Presence events are delivered on the client, one nolag.ActorPresence per event\n",[141,775,776],{"class":143,"line":509},[141,777,778],{},"    client.On(\"presence:join\", func(args ...any) {\n",[141,780,781],{"class":143,"line":644},[141,782,783],{},"        actor := args[0].(nolag.ActorPresence)\n",[141,785,787],{"class":143,"line":786},28,[141,788,789],{},"        fmt.Printf(\"%v joined\\n\", actor.Presence[\"username\"])\n",[141,791,793],{"class":143,"line":792},29,[141,794,764],{},[141,796,798],{"class":143,"line":797},30,[141,799,166],{"emptyLinePlaceholder":165},[141,801,803],{"class":143,"line":802},31,[141,804,805],{},"    client.On(\"presence:leave\", func(args ...any) {\n",[141,807,809],{"class":143,"line":808},32,[141,810,783],{},[141,812,814],{"class":143,"line":813},33,[141,815,816],{},"        fmt.Printf(\"%s left\\n\", actor.ActorTokenID)\n",[141,818,820],{"class":143,"line":819},34,[141,821,764],{},[141,823,825],{"class":143,"line":824},35,[141,826,166],{"emptyLinePlaceholder":165},[141,828,830],{"class":143,"line":829},36,[141,831,832],{},"    client.On(\"presence:update\", func(args ...any) {\n",[141,834,836],{"class":143,"line":835},37,[141,837,783],{},[141,839,841],{"class":143,"line":840},38,[141,842,843],{},"        fmt.Printf(\"%v updated status to %v\\n\", actor.Presence[\"username\"], actor.Presence[\"status\"])\n",[141,845,847],{"class":143,"line":846},39,[141,848,764],{},[141,850,852],{"class":143,"line":851},40,[141,853,854],{},"}\n",[18,856,858],{"id":857},"presence-events","Presence Events",[14,860,861],{},"Listen for these events to track when actors come online, go offline, or update their status:",[32,863,864,870,876],{},[35,865,866,869],{},[64,867,868],{},"presence:join"," - An actor set presence in the room for the first time",[35,871,872,875],{},[64,873,874],{},"presence:leave"," - An actor's socket dropped, or it set presence in a different room",[35,877,878,881],{},[64,879,880],{},"presence:update"," - An actor updated its presence data",[14,883,884,885,887,888,890,891,894],{},"Each event carries the actor's ",[64,886,429],{}," and its ",[64,889,369],{}," data. The ",[64,892,893],{},"leave"," event carries the id only.",[896,897,899],"h3",{"id":898},"who-receives-them","Who receives them",[14,901,902,903,906,907,119],{},"A room's presence events are broadcast to the actors that have set presence in that room. Subscribing to the room's topics does not enrol you: a client that only wants to watch must either set its own presence in the room, or observe the room through a ",[72,904,905],{"href":117},"lobby",". One connection holds presence in one room at a time; setting presence in a second room leaves the first, and the first room's members receive ",[64,908,874],{},[18,910,912],{"id":911},"getting-current-presence","Getting Current Presence",[14,914,915],{},"Read the local cache, or ask the server for the room's current list:",[128,917,918,1130,1212],{},[131,919,921],{"className":133,"code":920,"filename":135,"language":136,"meta":137,"style":137},"import type { ActorPresence } from '@nolag/js-sdk'\n\n// Local cache, kept up to date by presence events.\n// room.getPresence() returns a Record keyed by actorTokenId, not an array.\nconst cached: Record\u003Cstring, ActorPresence> = room.getPresence()\nconsole.log('Users online:', Object.keys(cached).length)\n\nfor (const actor of Object.values(cached)) {\n  console.log(`- ${actor.presence.username} (${actor.actorTokenId})`)\n}\n\n// Fresh list from the server (also refreshes the cache)\nconst fresh = await room.fetchPresence()\nconsole.log('Server says online:', fresh.length)\n",[64,922,923,937,941,946,951,990,1016,1020,1045,1080,1084,1088,1093,1112],{"__ignoreMap":137},[141,924,925,927,930,933,935],{"class":143,"line":144},[141,926,148],{"class":147},[141,928,929],{"class":147}," type",[141,931,932],{"class":151}," { ActorPresence } ",[141,934,155],{"class":147},[141,936,159],{"class":158},[141,938,939],{"class":143,"line":162},[141,940,166],{"emptyLinePlaceholder":165},[141,942,943],{"class":143,"line":169},[141,944,945],{"class":256},"// Local cache, kept up to date by presence events.\n",[141,947,948],{"class":143,"line":195},[141,949,950],{"class":256},"// room.getPresence() returns a Record keyed by actorTokenId, not an array.\n",[141,952,953,955,958,961,964,967,970,973,976,979,982,985,988],{"class":143,"line":210},[141,954,172],{"class":147},[141,956,957],{"class":175}," cached",[141,959,960],{"class":147},":",[141,962,963],{"class":182}," Record",[141,965,966],{"class":151},"\u003C",[141,968,969],{"class":175},"string",[141,971,972],{"class":151},", ",[141,974,975],{"class":182},"ActorPresence",[141,977,978],{"class":151},"> ",[141,980,981],{"class":147},"=",[141,983,984],{"class":151}," room.",[141,986,987],{"class":182},"getPresence",[141,989,207],{"class":151},[141,991,992,995,997,999,1002,1005,1008,1011,1014],{"class":143,"line":215},[141,993,994],{"class":151},"console.",[141,996,357],{"class":182},[141,998,186],{"class":151},[141,1000,1001],{"class":158},"'Users online:'",[141,1003,1004],{"class":151},", Object.",[141,1006,1007],{"class":182},"keys",[141,1009,1010],{"class":151},"(cached).",[141,1012,1013],{"class":175},"length",[141,1015,192],{"class":151},[141,1017,1018],{"class":143,"line":248},[141,1019,166],{"emptyLinePlaceholder":165},[141,1021,1022,1025,1028,1030,1033,1036,1039,1042],{"class":143,"line":253},[141,1023,1024],{"class":147},"for",[141,1026,1027],{"class":151}," (",[141,1029,172],{"class":147},[141,1031,1032],{"class":175}," actor",[141,1034,1035],{"class":147}," of",[141,1037,1038],{"class":151}," Object.",[141,1040,1041],{"class":182},"values",[141,1043,1044],{"class":151},"(cached)) {\n",[141,1046,1047,1049,1051,1053,1056,1058,1060,1062,1064,1066,1069,1071,1073,1075,1078],{"class":143,"line":260},[141,1048,354],{"class":151},[141,1050,357],{"class":182},[141,1052,186],{"class":151},[141,1054,1055],{"class":158},"`- ${",[141,1057,339],{"class":151},[141,1059,119],{"class":158},[141,1061,369],{"class":151},[141,1063,119],{"class":158},[141,1065,374],{"class":151},[141,1067,1068],{"class":158},"} (${",[141,1070,339],{"class":151},[141,1072,119],{"class":158},[141,1074,429],{"class":151},[141,1076,1077],{"class":158},"})`",[141,1079,192],{"class":151},[141,1081,1082],{"class":143,"line":272},[141,1083,854],{"class":151},[141,1085,1086],{"class":143,"line":284},[141,1087,166],{"emptyLinePlaceholder":165},[141,1089,1090],{"class":143,"line":295},[141,1091,1092],{"class":256},"// Fresh list from the server (also refreshes the cache)\n",[141,1094,1095,1097,1100,1102,1105,1107,1110],{"class":143,"line":304},[141,1096,172],{"class":147},[141,1098,1099],{"class":175}," fresh",[141,1101,179],{"class":147},[141,1103,1104],{"class":147}," await",[141,1106,984],{"class":151},[141,1108,1109],{"class":182},"fetchPresence",[141,1111,207],{"class":151},[141,1113,1114,1116,1118,1120,1123,1126,1128],{"class":143,"line":310},[141,1115,994],{"class":151},[141,1117,357],{"class":182},[141,1119,186],{"class":151},[141,1121,1122],{"class":158},"'Server says online:'",[141,1124,1125],{"class":151},", fresh.",[141,1127,1013],{"class":175},[141,1129,192],{"class":151},[131,1131,1133],{"className":514,"code":1132,"filename":516,"language":517,"meta":137,"style":137},"# Local cache, kept up to date by presence events\nactors = client.get_all_presence()\nprint('Users online:', len(actors))\n\nfor actor in actors:\n    print(f\"- {actor.presence['username']} ({actor.actor_token_id})\")\n\n# One actor from the cache\nalice = client.get_presence('actor_token_id_here')\nif alice:\n    print(f\"Alice is {alice.presence['status']}\")\n\n# Fresh list from the server: raw entries with actorTokenId, presence and joinedAt\nfresh = await room.fetch_presence()\nfor entry in fresh:\n    print(f\"- {entry['presence'].get('username')} ({entry['actorTokenId']})\")\n",[64,1134,1135,1140,1145,1150,1154,1159,1164,1168,1173,1178,1183,1188,1192,1197,1202,1207],{"__ignoreMap":137},[141,1136,1137],{"class":143,"line":144},[141,1138,1139],{},"# Local cache, kept up to date by presence events\n",[141,1141,1142],{"class":143,"line":162},[141,1143,1144],{},"actors = client.get_all_presence()\n",[141,1146,1147],{"class":143,"line":169},[141,1148,1149],{},"print('Users online:', len(actors))\n",[141,1151,1152],{"class":143,"line":195},[141,1153,166],{"emptyLinePlaceholder":165},[141,1155,1156],{"class":143,"line":210},[141,1157,1158],{},"for actor in actors:\n",[141,1160,1161],{"class":143,"line":215},[141,1162,1163],{},"    print(f\"- {actor.presence['username']} ({actor.actor_token_id})\")\n",[141,1165,1166],{"class":143,"line":248},[141,1167,166],{"emptyLinePlaceholder":165},[141,1169,1170],{"class":143,"line":253},[141,1171,1172],{},"# One actor from the cache\n",[141,1174,1175],{"class":143,"line":260},[141,1176,1177],{},"alice = client.get_presence('actor_token_id_here')\n",[141,1179,1180],{"class":143,"line":272},[141,1181,1182],{},"if alice:\n",[141,1184,1185],{"class":143,"line":284},[141,1186,1187],{},"    print(f\"Alice is {alice.presence['status']}\")\n",[141,1189,1190],{"class":143,"line":295},[141,1191,166],{"emptyLinePlaceholder":165},[141,1193,1194],{"class":143,"line":304},[141,1195,1196],{},"# Fresh list from the server: raw entries with actorTokenId, presence and joinedAt\n",[141,1198,1199],{"class":143,"line":310},[141,1200,1201],{},"fresh = await room.fetch_presence()\n",[141,1203,1204],{"class":143,"line":315},[141,1205,1206],{},"for entry in fresh:\n",[141,1208,1209],{"class":143,"line":321},[141,1210,1211],{},"    print(f\"- {entry['presence'].get('username')} ({entry['actorTokenId']})\")\n",[131,1213,1215],{"className":650,"code":1214,"filename":652,"language":653,"meta":137,"style":137},"// Fresh list from the server for a room, by room slug\nactors, err := client.GetPresence(\"general\")\nif err == nil {\n    fmt.Println(\"Users online:\", len(actors))\n\n    for _, actor := range actors {\n        fmt.Printf(\"- %v (%s)\\n\", actor.Presence[\"username\"], actor.ActorTokenID)\n    }\n}\n",[64,1216,1217,1222,1227,1232,1237,1241,1246,1251,1255],{"__ignoreMap":137},[141,1218,1219],{"class":143,"line":144},[141,1220,1221],{},"// Fresh list from the server for a room, by room slug\n",[141,1223,1224],{"class":143,"line":162},[141,1225,1226],{},"actors, err := client.GetPresence(\"general\")\n",[141,1228,1229],{"class":143,"line":169},[141,1230,1231],{},"if err == nil {\n",[141,1233,1234],{"class":143,"line":195},[141,1235,1236],{},"    fmt.Println(\"Users online:\", len(actors))\n",[141,1238,1239],{"class":143,"line":210},[141,1240,166],{"emptyLinePlaceholder":165},[141,1242,1243],{"class":143,"line":215},[141,1244,1245],{},"    for _, actor := range actors {\n",[141,1247,1248],{"class":143,"line":248},[141,1249,1250],{},"        fmt.Printf(\"- %v (%s)\\n\", actor.Presence[\"username\"], actor.ActorTokenID)\n",[141,1252,1253],{"class":143,"line":253},[141,1254,721],{},[141,1256,1257],{"class":143,"line":260},[141,1258,854],{},[896,1260,1262],{"id":1261},"local-cache-vs-server-fetch","Local Cache vs Server Fetch",[32,1264,1265,1286,1302],{},[35,1266,1267,1270,1271,1274,1275,972,1278,1281,1282,1285],{},[28,1268,1269],{},"JavaScript:"," ",[64,1272,1273],{},"room.getPresence()"," returns the cache as a ",[64,1276,1277],{},"Record\u003Cstring, ActorPresence>",[64,1279,1280],{},"client.getPresence(actorId)"," returns one cached actor, ",[64,1283,1284],{},"room.fetchPresence()"," fetches the room's list from the server",[35,1287,1288,1270,1291,1294,1295,1281,1298,1301],{},[28,1289,1290],{},"Python:",[64,1292,1293],{},"client.get_all_presence()"," returns the cache, ",[64,1296,1297],{},"client.get_presence(actor_token_id)",[64,1299,1300],{},"room.fetch_presence()"," fetches the room's list from the server as plain dicts",[35,1303,1304,1270,1307,1310],{},[28,1305,1306],{},"Go:",[64,1308,1309],{},"client.GetPresence(\"room-slug\")"," fetches the room's list from the server; there is no local cache",[14,1312,1313],{},"The local cache is updated by presence events, so it only reflects rooms whose events you receive.",[18,1315,1317],{"id":1316},"updating-presence","Updating Presence",[14,1319,1320,1321,1324],{},"Update your presence data at any time by calling ",[64,1322,1323],{},"setPresence()"," on the room again:",[128,1326,1327,1432,1473],{},[131,1328,1330],{"className":133,"code":1329,"filename":135,"language":136,"meta":137,"style":137},"// Update your presence data (e.g., status change)\nroom.setPresence({\n  username: 'Alice',\n  status: 'away',\n  lastActive: Date.now()\n})\n\n// Room presence is not re-sent automatically after a reconnect.\n// 'connect' fires on every successful connection, so re-set it there.\nclient.on('connect', () => {\n  room.setPresence({ username: 'Alice', status: 'online' })\n})\n",[64,1331,1332,1337,1345,1353,1362,1372,1376,1380,1385,1390,1408,1428],{"__ignoreMap":137},[141,1333,1334],{"class":143,"line":144},[141,1335,1336],{"class":256},"// Update your presence data (e.g., status change)\n",[141,1338,1339,1341,1343],{"class":143,"line":162},[141,1340,263],{"class":151},[141,1342,266],{"class":182},[141,1344,269],{"class":151},[141,1346,1347,1349,1351],{"class":143,"line":169},[141,1348,275],{"class":151},[141,1350,278],{"class":158},[141,1352,281],{"class":151},[141,1354,1355,1357,1360],{"class":143,"line":195},[141,1356,287],{"class":151},[141,1358,1359],{"class":158},"'away'",[141,1361,281],{"class":151},[141,1363,1364,1367,1370],{"class":143,"line":210},[141,1365,1366],{"class":151},"  lastActive: Date.",[141,1368,1369],{"class":182},"now",[141,1371,207],{"class":151},[141,1373,1374],{"class":143,"line":215},[141,1375,307],{"class":151},[141,1377,1378],{"class":143,"line":248},[141,1379,166],{"emptyLinePlaceholder":165},[141,1381,1382],{"class":143,"line":253},[141,1383,1384],{"class":256},"// Room presence is not re-sent automatically after a reconnect.\n",[141,1386,1387],{"class":143,"line":260},[141,1388,1389],{"class":256},"// 'connect' fires on every successful connection, so re-set it there.\n",[141,1391,1392,1394,1396,1398,1401,1404,1406],{"class":143,"line":272},[141,1393,324],{"class":151},[141,1395,327],{"class":182},[141,1397,186],{"class":151},[141,1399,1400],{"class":158},"'connect'",[141,1402,1403],{"class":151},", () ",[141,1405,345],{"class":147},[141,1407,348],{"class":151},[141,1409,1410,1413,1415,1418,1420,1423,1425],{"class":143,"line":284},[141,1411,1412],{"class":151},"  room.",[141,1414,266],{"class":182},[141,1416,1417],{"class":151},"({ username: ",[141,1419,278],{"class":158},[141,1421,1422],{"class":151},", status: ",[141,1424,290],{"class":158},[141,1426,1427],{"class":151}," })\n",[141,1429,1430],{"class":143,"line":295},[141,1431,307],{"class":151},[131,1433,1435],{"className":514,"code":1434,"filename":516,"language":517,"meta":137,"style":137},"# Update your presence data (e.g., status change)\nawait room.set_presence({\n    'username': 'Alice',\n    'status': 'away',\n    'lastActive': time.time()\n})\n\n# The Python SDK re-sends room presence after a reconnect\n",[64,1436,1437,1442,1446,1450,1455,1460,1464,1468],{"__ignoreMap":137},[141,1438,1439],{"class":143,"line":144},[141,1440,1441],{},"# Update your presence data (e.g., status change)\n",[141,1443,1444],{"class":143,"line":162},[141,1445,561],{},[141,1447,1448],{"class":143,"line":169},[141,1449,566],{},[141,1451,1452],{"class":143,"line":195},[141,1453,1454],{},"    'status': 'away',\n",[141,1456,1457],{"class":143,"line":210},[141,1458,1459],{},"    'lastActive': time.time()\n",[141,1461,1462],{"class":143,"line":215},[141,1463,307],{},[141,1465,1466],{"class":143,"line":248},[141,1467,166],{"emptyLinePlaceholder":165},[141,1469,1470],{"class":143,"line":253},[141,1471,1472],{},"# The Python SDK re-sends room presence after a reconnect\n",[131,1474,1476],{"className":650,"code":1475,"filename":652,"language":653,"meta":137,"style":137},"// Update your presence data (e.g., status change)\nroom.SetPresence(map[string]any{\n    \"username\":   \"Alice\",\n    \"status\":     \"away\",\n    \"lastActive\": time.Now().Unix(),\n})\n\n// Room presence is not re-sent automatically after a reconnect.\n// \"connected\" fires on every successful connection, so re-set it there.\nclient.On(\"connected\", func(args ...any) {\n    room.SetPresence(map[string]any{\"username\": \"Alice\", \"status\": \"online\"})\n})\n",[64,1477,1478,1482,1487,1492,1497,1502,1506,1510,1514,1519,1524,1529],{"__ignoreMap":137},[141,1479,1480],{"class":143,"line":144},[141,1481,1336],{},[141,1483,1484],{"class":143,"line":162},[141,1485,1486],{},"room.SetPresence(map[string]any{\n",[141,1488,1489],{"class":143,"line":169},[141,1490,1491],{},"    \"username\":   \"Alice\",\n",[141,1493,1494],{"class":143,"line":195},[141,1495,1496],{},"    \"status\":     \"away\",\n",[141,1498,1499],{"class":143,"line":210},[141,1500,1501],{},"    \"lastActive\": time.Now().Unix(),\n",[141,1503,1504],{"class":143,"line":215},[141,1505,307],{},[141,1507,1508],{"class":143,"line":248},[141,1509,166],{"emptyLinePlaceholder":165},[141,1511,1512],{"class":143,"line":253},[141,1513,1384],{},[141,1515,1516],{"class":143,"line":260},[141,1517,1518],{},"// \"connected\" fires on every successful connection, so re-set it there.\n",[141,1520,1521],{"class":143,"line":272},[141,1522,1523],{},"client.On(\"connected\", func(args ...any) {\n",[141,1525,1526],{"class":143,"line":284},[141,1527,1528],{},"    room.SetPresence(map[string]any{\"username\": \"Alice\", \"status\": \"online\"})\n",[141,1530,1531],{"class":143,"line":295},[141,1532,307],{},[18,1534,1536],{"id":1535},"typing-indicators","Typing Indicators",[14,1538,1539],{},"A common use case is showing typing indicators. Use presence updates with a debounce:",[128,1541,1542,1791,1958],{},[131,1543,1545],{"className":133,"code":1544,"filename":135,"language":136,"meta":137,"style":137},"// For typing indicators, update presence with typing status\nlet typingTimeout: ReturnType\u003Ctypeof setTimeout> | undefined\n\nfunction sendTyping() {\n  room.setPresence({\n    username: 'Alice',\n    status: 'online',\n    isTyping: true\n  })\n\n  // Clear typing after 3 seconds of inactivity\n  clearTimeout(typingTimeout)\n  typingTimeout = setTimeout(() => {\n    room.setPresence({\n      username: 'Alice',\n      status: 'online',\n      isTyping: false\n    })\n  }, 3000)\n}\n\n// Listen for typing from others (client-level event)\nclient.on('presence:update', (actor) => {\n  if (actor.presence.isTyping) {\n    showTypingIndicator(actor.presence.username)\n  } else {\n    hideTypingIndicator(actor.presence.username)\n  }\n})\n",[64,1546,1547,1552,1579,1583,1594,1602,1611,1620,1628,1633,1637,1642,1650,1667,1676,1685,1694,1702,1706,1716,1720,1724,1729,1749,1757,1765,1775,1782,1787],{"__ignoreMap":137},[141,1548,1549],{"class":143,"line":144},[141,1550,1551],{"class":256},"// For typing indicators, update presence with typing status\n",[141,1553,1554,1557,1560,1562,1565,1567,1570,1573,1576],{"class":143,"line":162},[141,1555,1556],{"class":147},"let",[141,1558,1559],{"class":151}," typingTimeout",[141,1561,960],{"class":147},[141,1563,1564],{"class":182}," ReturnType",[141,1566,966],{"class":151},[141,1568,1569],{"class":147},"typeof",[141,1571,1572],{"class":151}," setTimeout> ",[141,1574,1575],{"class":147},"|",[141,1577,1578],{"class":175}," undefined\n",[141,1580,1581],{"class":143,"line":169},[141,1582,166],{"emptyLinePlaceholder":165},[141,1584,1585,1588,1591],{"class":143,"line":195},[141,1586,1587],{"class":147},"function",[141,1589,1590],{"class":182}," sendTyping",[141,1592,1593],{"class":151},"() {\n",[141,1595,1596,1598,1600],{"class":143,"line":210},[141,1597,1412],{"class":151},[141,1599,266],{"class":182},[141,1601,269],{"class":151},[141,1603,1604,1607,1609],{"class":143,"line":215},[141,1605,1606],{"class":151},"    username: ",[141,1608,278],{"class":158},[141,1610,281],{"class":151},[141,1612,1613,1616,1618],{"class":143,"line":248},[141,1614,1615],{"class":151},"    status: ",[141,1617,290],{"class":158},[141,1619,281],{"class":151},[141,1621,1622,1625],{"class":143,"line":253},[141,1623,1624],{"class":151},"    isTyping: ",[141,1626,1627],{"class":175},"true\n",[141,1629,1630],{"class":143,"line":260},[141,1631,1632],{"class":151},"  })\n",[141,1634,1635],{"class":143,"line":272},[141,1636,166],{"emptyLinePlaceholder":165},[141,1638,1639],{"class":143,"line":284},[141,1640,1641],{"class":256},"  // Clear typing after 3 seconds of inactivity\n",[141,1643,1644,1647],{"class":143,"line":295},[141,1645,1646],{"class":182},"  clearTimeout",[141,1648,1649],{"class":151},"(typingTimeout)\n",[141,1651,1652,1655,1657,1660,1663,1665],{"class":143,"line":304},[141,1653,1654],{"class":151},"  typingTimeout ",[141,1656,981],{"class":147},[141,1658,1659],{"class":182}," setTimeout",[141,1661,1662],{"class":151},"(() ",[141,1664,345],{"class":147},[141,1666,348],{"class":151},[141,1668,1669,1672,1674],{"class":143,"line":310},[141,1670,1671],{"class":151},"    room.",[141,1673,266],{"class":182},[141,1675,269],{"class":151},[141,1677,1678,1681,1683],{"class":143,"line":315},[141,1679,1680],{"class":151},"      username: ",[141,1682,278],{"class":158},[141,1684,281],{"class":151},[141,1686,1687,1690,1692],{"class":143,"line":321},[141,1688,1689],{"class":151},"      status: ",[141,1691,290],{"class":158},[141,1693,281],{"class":151},[141,1695,1696,1699],{"class":143,"line":351},[141,1697,1698],{"class":151},"      isTyping: ",[141,1700,1701],{"class":175},"false\n",[141,1703,1704],{"class":143,"line":382},[141,1705,764],{"class":151},[141,1707,1708,1711,1714],{"class":143,"line":387},[141,1709,1710],{"class":151},"  }, ",[141,1712,1713],{"class":175},"3000",[141,1715,192],{"class":151},[141,1717,1718],{"class":143,"line":392},[141,1719,854],{"class":151},[141,1721,1722],{"class":143,"line":414},[141,1723,166],{"emptyLinePlaceholder":165},[141,1725,1726],{"class":143,"line":437},[141,1727,1728],{"class":256},"// Listen for typing from others (client-level event)\n",[141,1730,1731,1733,1735,1737,1739,1741,1743,1745,1747],{"class":143,"line":442},[141,1732,324],{"class":151},[141,1734,327],{"class":182},[141,1736,186],{"class":151},[141,1738,456],{"class":158},[141,1740,335],{"class":151},[141,1742,339],{"class":338},[141,1744,342],{"class":151},[141,1746,345],{"class":147},[141,1748,348],{"class":151},[141,1750,1751,1754],{"class":143,"line":447},[141,1752,1753],{"class":147},"  if",[141,1755,1756],{"class":151}," (actor.presence.isTyping) {\n",[141,1758,1759,1762],{"class":143,"line":469},[141,1760,1761],{"class":182},"    showTypingIndicator",[141,1763,1764],{"class":151},"(actor.presence.username)\n",[141,1766,1767,1770,1773],{"class":143,"line":509},[141,1768,1769],{"class":151},"  } ",[141,1771,1772],{"class":147},"else",[141,1774,348],{"class":151},[141,1776,1777,1780],{"class":143,"line":644},[141,1778,1779],{"class":182},"    hideTypingIndicator",[141,1781,1764],{"class":151},[141,1783,1784],{"class":143,"line":786},[141,1785,1786],{"class":151},"  }\n",[141,1788,1789],{"class":143,"line":792},[141,1790,307],{"class":151},[131,1792,1794],{"className":514,"code":1793,"filename":516,"language":517,"meta":137,"style":137},"# For typing indicators, update presence with typing status\ntyping_task = None\n\nasync def send_typing():\n    global typing_task\n\n    await room.set_presence({\n        'username': 'Alice',\n        'status': 'online',\n        'isTyping': True\n    })\n\n    # Clear typing after 3 seconds of inactivity\n    if typing_task:\n        typing_task.cancel()\n\n    async def clear_typing():\n        await asyncio.sleep(3)\n        await room.set_presence({\n            'username': 'Alice',\n            'status': 'online',\n            'isTyping': False\n        })\n\n    typing_task = asyncio.create_task(clear_typing())\n\n# Listen for typing from others (client-level event)\ndef on_presence_update(actor):\n    if actor.presence.get('isTyping'):\n        show_typing_indicator(actor.presence['username'])\n    else:\n        hide_typing_indicator(actor.presence['username'])\n\nclient.on('presence:update', on_presence_update)\n",[64,1795,1796,1801,1806,1810,1815,1820,1824,1829,1834,1839,1844,1848,1852,1857,1862,1867,1871,1876,1881,1886,1891,1896,1901,1906,1910,1915,1919,1924,1929,1934,1939,1944,1949,1953],{"__ignoreMap":137},[141,1797,1798],{"class":143,"line":144},[141,1799,1800],{},"# For typing indicators, update presence with typing status\n",[141,1802,1803],{"class":143,"line":162},[141,1804,1805],{},"typing_task = None\n",[141,1807,1808],{"class":143,"line":169},[141,1809,166],{"emptyLinePlaceholder":165},[141,1811,1812],{"class":143,"line":195},[141,1813,1814],{},"async def send_typing():\n",[141,1816,1817],{"class":143,"line":210},[141,1818,1819],{},"    global typing_task\n",[141,1821,1822],{"class":143,"line":215},[141,1823,166],{"emptyLinePlaceholder":165},[141,1825,1826],{"class":143,"line":248},[141,1827,1828],{},"    await room.set_presence({\n",[141,1830,1831],{"class":143,"line":253},[141,1832,1833],{},"        'username': 'Alice',\n",[141,1835,1836],{"class":143,"line":260},[141,1837,1838],{},"        'status': 'online',\n",[141,1840,1841],{"class":143,"line":272},[141,1842,1843],{},"        'isTyping': True\n",[141,1845,1846],{"class":143,"line":284},[141,1847,764],{},[141,1849,1850],{"class":143,"line":295},[141,1851,166],{"emptyLinePlaceholder":165},[141,1853,1854],{"class":143,"line":304},[141,1855,1856],{},"    # Clear typing after 3 seconds of inactivity\n",[141,1858,1859],{"class":143,"line":310},[141,1860,1861],{},"    if typing_task:\n",[141,1863,1864],{"class":143,"line":315},[141,1865,1866],{},"        typing_task.cancel()\n",[141,1868,1869],{"class":143,"line":321},[141,1870,166],{"emptyLinePlaceholder":165},[141,1872,1873],{"class":143,"line":351},[141,1874,1875],{},"    async def clear_typing():\n",[141,1877,1878],{"class":143,"line":382},[141,1879,1880],{},"        await asyncio.sleep(3)\n",[141,1882,1883],{"class":143,"line":387},[141,1884,1885],{},"        await room.set_presence({\n",[141,1887,1888],{"class":143,"line":392},[141,1889,1890],{},"            'username': 'Alice',\n",[141,1892,1893],{"class":143,"line":414},[141,1894,1895],{},"            'status': 'online',\n",[141,1897,1898],{"class":143,"line":437},[141,1899,1900],{},"            'isTyping': False\n",[141,1902,1903],{"class":143,"line":442},[141,1904,1905],{},"        })\n",[141,1907,1908],{"class":143,"line":447},[141,1909,166],{"emptyLinePlaceholder":165},[141,1911,1912],{"class":143,"line":469},[141,1913,1914],{},"    typing_task = asyncio.create_task(clear_typing())\n",[141,1916,1917],{"class":143,"line":509},[141,1918,166],{"emptyLinePlaceholder":165},[141,1920,1921],{"class":143,"line":644},[141,1922,1923],{},"# Listen for typing from others (client-level event)\n",[141,1925,1926],{"class":143,"line":786},[141,1927,1928],{},"def on_presence_update(actor):\n",[141,1930,1931],{"class":143,"line":792},[141,1932,1933],{},"    if actor.presence.get('isTyping'):\n",[141,1935,1936],{"class":143,"line":797},[141,1937,1938],{},"        show_typing_indicator(actor.presence['username'])\n",[141,1940,1941],{"class":143,"line":802},[141,1942,1943],{},"    else:\n",[141,1945,1946],{"class":143,"line":808},[141,1947,1948],{},"        hide_typing_indicator(actor.presence['username'])\n",[141,1950,1951],{"class":143,"line":813},[141,1952,166],{"emptyLinePlaceholder":165},[141,1954,1955],{"class":143,"line":819},[141,1956,1957],{},"client.on('presence:update', on_presence_update)\n",[131,1959,1961],{"className":650,"code":1960,"filename":652,"language":653,"meta":137,"style":137},"// For typing indicators, update presence with typing status\nvar typingTimer *time.Timer\n\nfunc sendTyping() {\n    room.SetPresence(map[string]any{\n        \"username\": \"Alice\",\n        \"status\":   \"online\",\n        \"isTyping\": true,\n    })\n\n    // Clear typing after 3 seconds of inactivity\n    if typingTimer != nil {\n        typingTimer.Stop()\n    }\n    typingTimer = time.AfterFunc(3*time.Second, func() {\n        room.SetPresence(map[string]any{\n            \"username\": \"Alice\",\n            \"status\":   \"online\",\n            \"isTyping\": false,\n        })\n    })\n}\n\n// Listen for typing from others (client-level event)\nfunc watchTyping() {\n    client.On(\"presence:update\", func(args ...any) {\n        actor := args[0].(nolag.ActorPresence)\n        username, _ := actor.Presence[\"username\"].(string)\n        if isTyping, ok := actor.Presence[\"isTyping\"].(bool); ok && isTyping {\n            showTypingIndicator(username)\n        } else {\n            hideTypingIndicator(username)\n        }\n    })\n}\n",[64,1962,1963,1967,1972,1976,1981,1985,1989,1993,1998,2002,2006,2011,2016,2021,2025,2030,2035,2040,2045,2050,2054,2058,2062,2066,2070,2075,2079,2083,2088,2093,2098,2103,2108,2113,2117],{"__ignoreMap":137},[141,1964,1965],{"class":143,"line":144},[141,1966,1551],{},[141,1968,1969],{"class":143,"line":162},[141,1970,1971],{},"var typingTimer *time.Timer\n",[141,1973,1974],{"class":143,"line":169},[141,1975,166],{"emptyLinePlaceholder":165},[141,1977,1978],{"class":143,"line":195},[141,1979,1980],{},"func sendTyping() {\n",[141,1982,1983],{"class":143,"line":210},[141,1984,744],{},[141,1986,1987],{"class":143,"line":215},[141,1988,749],{},[141,1990,1991],{"class":143,"line":248},[141,1992,754],{},[141,1994,1995],{"class":143,"line":253},[141,1996,1997],{},"        \"isTyping\": true,\n",[141,1999,2000],{"class":143,"line":260},[141,2001,764],{},[141,2003,2004],{"class":143,"line":272},[141,2005,166],{"emptyLinePlaceholder":165},[141,2007,2008],{"class":143,"line":284},[141,2009,2010],{},"    // Clear typing after 3 seconds of inactivity\n",[141,2012,2013],{"class":143,"line":295},[141,2014,2015],{},"    if typingTimer != nil {\n",[141,2017,2018],{"class":143,"line":304},[141,2019,2020],{},"        typingTimer.Stop()\n",[141,2022,2023],{"class":143,"line":310},[141,2024,721],{},[141,2026,2027],{"class":143,"line":315},[141,2028,2029],{},"    typingTimer = time.AfterFunc(3*time.Second, func() {\n",[141,2031,2032],{"class":143,"line":321},[141,2033,2034],{},"        room.SetPresence(map[string]any{\n",[141,2036,2037],{"class":143,"line":351},[141,2038,2039],{},"            \"username\": \"Alice\",\n",[141,2041,2042],{"class":143,"line":382},[141,2043,2044],{},"            \"status\":   \"online\",\n",[141,2046,2047],{"class":143,"line":387},[141,2048,2049],{},"            \"isTyping\": false,\n",[141,2051,2052],{"class":143,"line":392},[141,2053,1905],{},[141,2055,2056],{"class":143,"line":414},[141,2057,764],{},[141,2059,2060],{"class":143,"line":437},[141,2061,854],{},[141,2063,2064],{"class":143,"line":442},[141,2065,166],{"emptyLinePlaceholder":165},[141,2067,2068],{"class":143,"line":447},[141,2069,1728],{},[141,2071,2072],{"class":143,"line":469},[141,2073,2074],{},"func watchTyping() {\n",[141,2076,2077],{"class":143,"line":509},[141,2078,832],{},[141,2080,2081],{"class":143,"line":644},[141,2082,783],{},[141,2084,2085],{"class":143,"line":786},[141,2086,2087],{},"        username, _ := actor.Presence[\"username\"].(string)\n",[141,2089,2090],{"class":143,"line":792},[141,2091,2092],{},"        if isTyping, ok := actor.Presence[\"isTyping\"].(bool); ok && isTyping {\n",[141,2094,2095],{"class":143,"line":797},[141,2096,2097],{},"            showTypingIndicator(username)\n",[141,2099,2100],{"class":143,"line":802},[141,2101,2102],{},"        } else {\n",[141,2104,2105],{"class":143,"line":808},[141,2106,2107],{},"            hideTypingIndicator(username)\n",[141,2109,2110],{"class":143,"line":813},[141,2111,2112],{},"        }\n",[141,2114,2115],{"class":143,"line":819},[141,2116,764],{},[141,2118,2119],{"class":143,"line":824},[141,2120,854],{},[18,2122,2124],{"id":2123},"actor-presence-structure","Actor Presence Structure",[14,2126,2127],{},"Each actor presence object contains:",[128,2129,2130],{},[131,2131,2133],{"className":133,"code":2132,"filename":135,"language":136,"meta":137,"style":137},"interface ActorPresence {\n  actorTokenId: string      // Unique actor identifier\n  presence: PresenceData    // Your custom fields (plus persistent and wake, below)\n  joinedAt?: number         // Only present in fetchPresence() results\n  status?: 'online' | 'offline' | 'waking'  // Persistent Presence only, absent otherwise\n  actorType?: ActorType     // Optional; presence events and lists do not carry it\n}\n",[64,2134,2135,2145,2158,2171,2185,2209,2222],{"__ignoreMap":137},[141,2136,2137,2140,2143],{"class":143,"line":144},[141,2138,2139],{"class":147},"interface",[141,2141,2142],{"class":182}," ActorPresence",[141,2144,348],{"class":151},[141,2146,2147,2150,2152,2155],{"class":143,"line":162},[141,2148,2149],{"class":338},"  actorTokenId",[141,2151,960],{"class":147},[141,2153,2154],{"class":175}," string",[141,2156,2157],{"class":256},"      // Unique actor identifier\n",[141,2159,2160,2163,2165,2168],{"class":143,"line":169},[141,2161,2162],{"class":338},"  presence",[141,2164,960],{"class":147},[141,2166,2167],{"class":182}," PresenceData",[141,2169,2170],{"class":256},"    // Your custom fields (plus persistent and wake, below)\n",[141,2172,2173,2176,2179,2182],{"class":143,"line":195},[141,2174,2175],{"class":338},"  joinedAt",[141,2177,2178],{"class":147},"?:",[141,2180,2181],{"class":175}," number",[141,2183,2184],{"class":256},"         // Only present in fetchPresence() results\n",[141,2186,2187,2190,2192,2195,2198,2201,2203,2206],{"class":143,"line":210},[141,2188,2189],{"class":338},"  status",[141,2191,2178],{"class":147},[141,2193,2194],{"class":158}," 'online'",[141,2196,2197],{"class":147}," |",[141,2199,2200],{"class":158}," 'offline'",[141,2202,2197],{"class":147},[141,2204,2205],{"class":158}," 'waking'",[141,2207,2208],{"class":256},"  // Persistent Presence only, absent otherwise\n",[141,2210,2211,2214,2216,2219],{"class":143,"line":215},[141,2212,2213],{"class":338},"  actorType",[141,2215,2178],{"class":147},[141,2217,2218],{"class":182}," ActorType",[141,2220,2221],{"class":256},"     // Optional; presence events and lists do not carry it\n",[141,2223,2224],{"class":143,"line":248},[141,2225,854],{"class":151},[14,2227,2228,2229,2231,2232,2234,2235,2238,2239,2241,2242,972,2245,972,2247,2231,2250,2252,2253,2256,2257,2260,2261,2264,2265,972,2268,2231,2271,119],{},"Presence events and fetch results carry ",[64,2230,429],{}," and ",[64,2233,369],{},"; fetch results add ",[64,2236,2237],{},"joinedAt",". Python's ",[64,2240,975],{}," has the same fields as ",[64,2243,2244],{},"actor_token_id",[64,2246,369],{},[64,2248,2249],{},"joined_at",[64,2251,501],{}," (its ",[64,2254,2255],{},"actor_type"," defaults to ",[64,2258,2259],{},"device",", the broker does not send one). Go's ",[64,2262,2263],{},"nolag.ActorPresence"," populates ",[64,2266,2267],{},"ActorTokenID",[64,2269,2270],{},"Presence",[64,2272,2273],{},"Status",[18,2275,2277],{"id":2276},"backend-workers-and-agents","Backend Workers and Agents",[14,2279,2280],{},"Presence is not only a chat feature. It is also how a backend service, worker\npool, or AI agent announces that it is running and what it can do, and how an\norchestrator discovers it.",[77,2282,2283],{"type":79},[14,2284,2285,2288,2289,2292,2293,2295],{},[28,2286,2287],{},"Do not mirror presence into your own database."," A ",[64,2290,2291],{},"last_seen_at"," column plus a\nperiodic \"anything older than N minutes is dead\" sweep is the usual way this gets\nrebuilt, and it is strictly worse: the broker already knows the moment a socket\ndrops and emits ",[64,2294,874],{}," in realtime, where a staleness sweep is only as\nfresh as its interval. Store durable records if you need history or audit, but\nread liveness from presence.",[14,2297,2298],{},"A worker advertises itself the same way a user does, with the payload describing\ncapability rather than identity:",[128,2300,2301,2385,2422],{},[131,2302,2304],{"className":133,"code":2303,"filename":135,"language":136,"meta":137,"style":137},"const room = client.setApp(APP_SLUG).setRoom('workers')\n\nroom.setPresence({\n  name: 'echo-runtime-a91f',\n  role: 'agent',\n  capabilities: ['chat_response', 'soil_analysis'],\n})\n",[64,2305,2306,2333,2337,2345,2355,2365,2381],{"__ignoreMap":137},[141,2307,2308,2310,2312,2314,2316,2318,2320,2322,2324,2326,2328,2331],{"class":143,"line":144},[141,2309,172],{"class":147},[141,2311,220],{"class":175},[141,2313,179],{"class":147},[141,2315,201],{"class":151},[141,2317,227],{"class":182},[141,2319,186],{"class":151},[141,2321,232],{"class":175},[141,2323,235],{"class":151},[141,2325,238],{"class":182},[141,2327,186],{"class":151},[141,2329,2330],{"class":158},"'workers'",[141,2332,192],{"class":151},[141,2334,2335],{"class":143,"line":162},[141,2336,166],{"emptyLinePlaceholder":165},[141,2338,2339,2341,2343],{"class":143,"line":169},[141,2340,263],{"class":151},[141,2342,266],{"class":182},[141,2344,269],{"class":151},[141,2346,2347,2350,2353],{"class":143,"line":195},[141,2348,2349],{"class":151},"  name: ",[141,2351,2352],{"class":158},"'echo-runtime-a91f'",[141,2354,281],{"class":151},[141,2356,2357,2360,2363],{"class":143,"line":210},[141,2358,2359],{"class":151},"  role: ",[141,2361,2362],{"class":158},"'agent'",[141,2364,281],{"class":151},[141,2366,2367,2370,2373,2375,2378],{"class":143,"line":215},[141,2368,2369],{"class":151},"  capabilities: [",[141,2371,2372],{"class":158},"'chat_response'",[141,2374,972],{"class":151},[141,2376,2377],{"class":158},"'soil_analysis'",[141,2379,2380],{"class":151},"],\n",[141,2382,2383],{"class":143,"line":248},[141,2384,307],{"class":151},[131,2386,2388],{"className":514,"code":2387,"filename":516,"language":517,"meta":137,"style":137},"room = client.set_app(APP_SLUG).set_room('workers')\n\nawait room.set_presence({\n    'name': 'echo-runtime-a91f',\n    'role': 'agent',\n    'capabilities': ['chat_response', 'soil_analysis'],\n})\n",[64,2389,2390,2395,2399,2403,2408,2413,2418],{"__ignoreMap":137},[141,2391,2392],{"class":143,"line":144},[141,2393,2394],{},"room = client.set_app(APP_SLUG).set_room('workers')\n",[141,2396,2397],{"class":143,"line":162},[141,2398,166],{"emptyLinePlaceholder":165},[141,2400,2401],{"class":143,"line":169},[141,2402,561],{},[141,2404,2405],{"class":143,"line":195},[141,2406,2407],{},"    'name': 'echo-runtime-a91f',\n",[141,2409,2410],{"class":143,"line":210},[141,2411,2412],{},"    'role': 'agent',\n",[141,2414,2415],{"class":143,"line":215},[141,2416,2417],{},"    'capabilities': ['chat_response', 'soil_analysis'],\n",[141,2419,2420],{"class":143,"line":248},[141,2421,307],{},[131,2423,2425],{"className":650,"code":2424,"filename":652,"language":653,"meta":137,"style":137},"room := client.SetApp(appSlug).SetRoom(\"workers\")\n\nroom.SetPresence(map[string]any{\n    \"name\":         \"echo-runtime-a91f\",\n    \"role\":         \"agent\",\n    \"capabilities\": []string{\"chat_response\", \"soil_analysis\"},\n})\n",[64,2426,2427,2432,2436,2440,2445,2450,2455],{"__ignoreMap":137},[141,2428,2429],{"class":143,"line":144},[141,2430,2431],{},"room := client.SetApp(appSlug).SetRoom(\"workers\")\n",[141,2433,2434],{"class":143,"line":162},[141,2435,166],{"emptyLinePlaceholder":165},[141,2437,2438],{"class":143,"line":169},[141,2439,1486],{},[141,2441,2442],{"class":143,"line":195},[141,2443,2444],{},"    \"name\":         \"echo-runtime-a91f\",\n",[141,2446,2447],{"class":143,"line":210},[141,2448,2449],{},"    \"role\":         \"agent\",\n",[141,2451,2452],{"class":143,"line":215},[141,2453,2454],{},"    \"capabilities\": []string{\"chat_response\", \"soil_analysis\"},\n",[141,2456,2457],{"class":143,"line":248},[141,2458,307],{},[14,2460,2461],{},"An orchestrator then reads the live set to route work, with no registry of its own. It has set its own presence in the room, so it receives the room's events and its cache stays current:",[131,2463,2465],{"className":133,"code":2464,"filename":135,"language":136,"meta":137,"style":137},"import type { ActorPresence } from '@nolag/js-sdk'\n\n// room.getPresence() returns a Record keyed by actorTokenId, not an array\nconst workers: Record\u003Cstring, ActorPresence> = room.getPresence()\nconst available = Object.values(workers)\n  .filter((a) => (a.presence.capabilities as string[])?.includes('chat_response'))\n",[64,2466,2467,2479,2483,2488,2517,2533],{"__ignoreMap":137},[141,2468,2469,2471,2473,2475,2477],{"class":143,"line":144},[141,2470,148],{"class":147},[141,2472,929],{"class":147},[141,2474,932],{"class":151},[141,2476,155],{"class":147},[141,2478,159],{"class":158},[141,2480,2481],{"class":143,"line":162},[141,2482,166],{"emptyLinePlaceholder":165},[141,2484,2485],{"class":143,"line":169},[141,2486,2487],{"class":256},"// room.getPresence() returns a Record keyed by actorTokenId, not an array\n",[141,2489,2490,2492,2495,2497,2499,2501,2503,2505,2507,2509,2511,2513,2515],{"class":143,"line":195},[141,2491,172],{"class":147},[141,2493,2494],{"class":175}," workers",[141,2496,960],{"class":147},[141,2498,963],{"class":182},[141,2500,966],{"class":151},[141,2502,969],{"class":175},[141,2504,972],{"class":151},[141,2506,975],{"class":182},[141,2508,978],{"class":151},[141,2510,981],{"class":147},[141,2512,984],{"class":151},[141,2514,987],{"class":182},[141,2516,207],{"class":151},[141,2518,2519,2521,2524,2526,2528,2530],{"class":143,"line":210},[141,2520,172],{"class":147},[141,2522,2523],{"class":175}," available",[141,2525,179],{"class":147},[141,2527,1038],{"class":151},[141,2529,1041],{"class":182},[141,2531,2532],{"class":151},"(workers)\n",[141,2534,2535,2538,2541,2544,2546,2548,2550,2553,2556,2558,2561,2564,2566,2568],{"class":143,"line":215},[141,2536,2537],{"class":151},"  .",[141,2539,2540],{"class":182},"filter",[141,2542,2543],{"class":151},"((",[141,2545,72],{"class":338},[141,2547,342],{"class":151},[141,2549,345],{"class":147},[141,2551,2552],{"class":151}," (a.presence.capabilities ",[141,2554,2555],{"class":147},"as",[141,2557,2154],{"class":175},[141,2559,2560],{"class":151},"[])?.",[141,2562,2563],{"class":182},"includes",[141,2565,186],{"class":151},[141,2567,2372],{"class":158},[141,2569,2570],{"class":151},"))\n",[896,2572,2574],{"id":2573},"connection-liveness-is-not-task-liveness","Connection liveness is not task liveness",[14,2576,2577],{},"These are different questions and presence only answers the first:",[2579,2580,2581,2594],"table",{},[2582,2583,2584],"thead",{},[2585,2586,2587,2591],"tr",{},[2588,2589,2590],"th",{},"Question",[2588,2592,2593],{},"Use",[2595,2596,2597,2611],"tbody",{},[2585,2598,2599,2603],{},[2600,2601,2602],"td",{},"Is this worker connected?",[2600,2604,2605,2606,2608,2609],{},"Presence: ",[64,2607,868],{}," / ",[64,2610,874],{},[2585,2612,2613,2616],{},[2600,2614,2615],{},"Is this worker still making progress on task X?",[2600,2617,2618],{},"An application-level progress signal",[14,2620,2621],{},"A worker can hold a healthy socket while a task is wedged, so presence will keep\nreporting it online. If you need to fail a stalled task, publish progress events\nfrom the worker and run a watchdog that resets on each one. That is a legitimate\napplication concern, not a gap in presence, and the two mechanisms belong side by\nside.",[18,2623,75],{"id":2624},"persistent-presence",[14,2626,2627],{},"By default a presence record is ephemeral: it exists while the socket is\nconnected and disappears on disconnect. That does not suit a service that scales\nto zero, a device that sleeps, or an agent you want to remain discoverable and\naddressable while it is not running.",[14,2629,2630],{},"Persistent Presence keeps the record after the socket drops, so the actor stays\ndiscoverable, and optionally lets NoLag wake it when work arrives.",[77,2632,2633],{"type":79},[14,2634,2635,2636,2231,2639,2642],{},"Persistent Presence is available on NoLag cloud. A self-hosted or standalone\nbroker runs with the durable presence store and wake dispatcher disabled, where\n",[64,2637,2638],{},"persistent",[64,2640,2641],{},"wake"," are accepted and ignored, so the same code stays portable\nand simply behaves as ephemeral presence.",[896,2644,2646],{"id":2645},"advertising-a-persistent-actor","Advertising a persistent actor",[14,2648,2649,2650,2652,2653,2655],{},"Add ",[64,2651,2638],{}," and, if the actor can be woken, a ",[64,2654,2641],{}," block:",[128,2657,2658,2742,2795],{},[131,2659,2661],{"className":133,"code":2660,"filename":135,"language":136,"meta":137,"style":137},"room.setPresence({\n  name: 'soil-sensor-14',\n  role: 'device',\n  capabilities: ['telemetry'],\n  persistent: true,\n  wake: {\n    url: 'https://example.com/hooks/nolag-wake',\n    timeoutMs: 10000,\n  },\n})\n",[64,2662,2663,2671,2680,2689,2698,2708,2713,2723,2733,2738],{"__ignoreMap":137},[141,2664,2665,2667,2669],{"class":143,"line":144},[141,2666,263],{"class":151},[141,2668,266],{"class":182},[141,2670,269],{"class":151},[141,2672,2673,2675,2678],{"class":143,"line":162},[141,2674,2349],{"class":151},[141,2676,2677],{"class":158},"'soil-sensor-14'",[141,2679,281],{"class":151},[141,2681,2682,2684,2687],{"class":143,"line":169},[141,2683,2359],{"class":151},[141,2685,2686],{"class":158},"'device'",[141,2688,281],{"class":151},[141,2690,2691,2693,2696],{"class":143,"line":195},[141,2692,2369],{"class":151},[141,2694,2695],{"class":158},"'telemetry'",[141,2697,2380],{"class":151},[141,2699,2700,2703,2706],{"class":143,"line":210},[141,2701,2702],{"class":151},"  persistent: ",[141,2704,2705],{"class":175},"true",[141,2707,281],{"class":151},[141,2709,2710],{"class":143,"line":215},[141,2711,2712],{"class":151},"  wake: {\n",[141,2714,2715,2718,2721],{"class":143,"line":248},[141,2716,2717],{"class":151},"    url: ",[141,2719,2720],{"class":158},"'https://example.com/hooks/nolag-wake'",[141,2722,281],{"class":151},[141,2724,2725,2728,2731],{"class":143,"line":253},[141,2726,2727],{"class":151},"    timeoutMs: ",[141,2729,2730],{"class":175},"10000",[141,2732,281],{"class":151},[141,2734,2735],{"class":143,"line":260},[141,2736,2737],{"class":151},"  },\n",[141,2739,2740],{"class":143,"line":272},[141,2741,307],{"class":151},[131,2743,2745],{"className":514,"code":2744,"filename":516,"language":517,"meta":137,"style":137},"await room.set_presence({\n    'name': 'soil-sensor-14',\n    'role': 'device',\n    'capabilities': ['telemetry'],\n    'persistent': True,\n    'wake': {\n        'url': 'https://example.com/hooks/nolag-wake',\n        'timeoutMs': 10000,\n    },\n})\n",[64,2746,2747,2751,2756,2761,2766,2771,2776,2781,2786,2791],{"__ignoreMap":137},[141,2748,2749],{"class":143,"line":144},[141,2750,561],{},[141,2752,2753],{"class":143,"line":162},[141,2754,2755],{},"    'name': 'soil-sensor-14',\n",[141,2757,2758],{"class":143,"line":169},[141,2759,2760],{},"    'role': 'device',\n",[141,2762,2763],{"class":143,"line":195},[141,2764,2765],{},"    'capabilities': ['telemetry'],\n",[141,2767,2768],{"class":143,"line":210},[141,2769,2770],{},"    'persistent': True,\n",[141,2772,2773],{"class":143,"line":215},[141,2774,2775],{},"    'wake': {\n",[141,2777,2778],{"class":143,"line":248},[141,2779,2780],{},"        'url': 'https://example.com/hooks/nolag-wake',\n",[141,2782,2783],{"class":143,"line":253},[141,2784,2785],{},"        'timeoutMs': 10000,\n",[141,2787,2788],{"class":143,"line":260},[141,2789,2790],{},"    },\n",[141,2792,2793],{"class":143,"line":272},[141,2794,307],{},[131,2796,2798],{"className":650,"code":2797,"filename":652,"language":653,"meta":137,"style":137},"room.SetPresence(map[string]any{\n    \"name\":         \"soil-sensor-14\",\n    \"role\":         \"device\",\n    \"capabilities\": []string{\"telemetry\"},\n    \"persistent\":   true,\n    \"wake\": map[string]any{\n        \"url\":       \"https://example.com/hooks/nolag-wake\",\n        \"timeoutMs\": 10000,\n    },\n})\n",[64,2799,2800,2804,2809,2814,2819,2824,2829,2834,2839,2843],{"__ignoreMap":137},[141,2801,2802],{"class":143,"line":144},[141,2803,1486],{},[141,2805,2806],{"class":143,"line":162},[141,2807,2808],{},"    \"name\":         \"soil-sensor-14\",\n",[141,2810,2811],{"class":143,"line":169},[141,2812,2813],{},"    \"role\":         \"device\",\n",[141,2815,2816],{"class":143,"line":195},[141,2817,2818],{},"    \"capabilities\": []string{\"telemetry\"},\n",[141,2820,2821],{"class":143,"line":210},[141,2822,2823],{},"    \"persistent\":   true,\n",[141,2825,2826],{"class":143,"line":215},[141,2827,2828],{},"    \"wake\": map[string]any{\n",[141,2830,2831],{"class":143,"line":248},[141,2832,2833],{},"        \"url\":       \"https://example.com/hooks/nolag-wake\",\n",[141,2835,2836],{"class":143,"line":253},[141,2837,2838],{},"        \"timeoutMs\": 10000,\n",[141,2840,2841],{"class":143,"line":260},[141,2842,2790],{},[141,2844,2845],{"class":143,"line":272},[141,2846,307],{},[896,2848,2850],{"id":2849},"lifecycle","Lifecycle",[14,2852,2853,2854,2856],{},"A persistent record carries a ",[64,2855,501],{}," that an ephemeral one does not:",[2579,2858,2859,2870],{},[2582,2860,2861],{},[2585,2862,2863,2867],{},[2588,2864,2865],{},[64,2866,501],{},[2588,2868,2869],{},"Meaning",[2595,2871,2872,2882,2892],{},[2585,2873,2874,2879],{},[2600,2875,2876],{},[64,2877,2878],{},"online",[2600,2880,2881],{},"Socket connected right now",[2585,2883,2884,2889],{},[2600,2885,2886],{},[64,2887,2888],{},"offline",[2600,2890,2891],{},"Registered but disconnected. Still discoverable, still addressable",[2585,2893,2894,2899],{},[2600,2895,2896],{},[64,2897,2898],{},"waking",[2600,2900,2901],{},"A wake webhook has been fired and NoLag is waiting for the reconnect",[131,2903,2908],{"className":2904,"code":2906,"language":2907},[2905],"language-text","   advertise(persistent)          socket drops\n ─────────────────────▶ online ─────────────────▶ offline\n                          ▲                          │\n                          │                          │ message routed to it\n       reconnects, queued │                          ▼\n       messages flush     └───────────────────── waking\n","text",[64,2909,2906],{"__ignoreMap":137},[14,2911,2912],{},"Discovery returns offline actors too, so filter on status when you only want\nwhat is live right now:",[131,2914,2916],{"className":133,"code":2915,"filename":135,"language":136,"meta":137,"style":137},"import type { ActorPresence } from '@nolag/js-sdk'\n\nconst workers: Record\u003Cstring, ActorPresence> = room.getPresence()\nconst live = Object.values(workers).filter(\n  (a) => a.status === undefined || a.status === 'online',\n)\n",[64,2917,2918,2930,2934,2962,2983,3014],{"__ignoreMap":137},[141,2919,2920,2922,2924,2926,2928],{"class":143,"line":144},[141,2921,148],{"class":147},[141,2923,929],{"class":147},[141,2925,932],{"class":151},[141,2927,155],{"class":147},[141,2929,159],{"class":158},[141,2931,2932],{"class":143,"line":162},[141,2933,166],{"emptyLinePlaceholder":165},[141,2935,2936,2938,2940,2942,2944,2946,2948,2950,2952,2954,2956,2958,2960],{"class":143,"line":169},[141,2937,172],{"class":147},[141,2939,2494],{"class":175},[141,2941,960],{"class":147},[141,2943,963],{"class":182},[141,2945,966],{"class":151},[141,2947,969],{"class":175},[141,2949,972],{"class":151},[141,2951,975],{"class":182},[141,2953,978],{"class":151},[141,2955,981],{"class":147},[141,2957,984],{"class":151},[141,2959,987],{"class":182},[141,2961,207],{"class":151},[141,2963,2964,2966,2969,2971,2973,2975,2978,2980],{"class":143,"line":195},[141,2965,172],{"class":147},[141,2967,2968],{"class":175}," live",[141,2970,179],{"class":147},[141,2972,1038],{"class":151},[141,2974,1041],{"class":182},[141,2976,2977],{"class":151},"(workers).",[141,2979,2540],{"class":182},[141,2981,2982],{"class":151},"(\n",[141,2984,2985,2988,2990,2992,2994,2997,3000,3003,3006,3008,3010,3012],{"class":143,"line":210},[141,2986,2987],{"class":151},"  (",[141,2989,72],{"class":338},[141,2991,342],{"class":151},[141,2993,345],{"class":147},[141,2995,2996],{"class":151}," a.status ",[141,2998,2999],{"class":147},"===",[141,3001,3002],{"class":175}," undefined",[141,3004,3005],{"class":147}," ||",[141,3007,2996],{"class":151},[141,3009,2999],{"class":147},[141,3011,2194],{"class":158},[141,3013,281],{"class":151},[141,3015,3016],{"class":143,"line":215},[141,3017,192],{"class":151},[14,3019,3020,3022,3023,3026],{},[64,3021,501],{}," is absent for ordinary ephemeral actors, which is why the check above\ntreats ",[64,3024,3025],{},"undefined"," as live.",[896,3028,3030],{"id":3029},"wake-webhook","Wake webhook",[14,3032,3033,3034,3036,3037,3040],{},"When a message is routed to an ",[64,3035,2888],{}," persistent actor, NoLag queues the\nmessage on the actor's session and POSTs to the registered ",[64,3038,3039],{},"wake.url"," so the\nactor can cold-start and drain it. The webhook carries no message payload.",[131,3042,3047],{"className":3043,"code":3044,"filename":3045,"language":3046,"meta":137,"style":137},"language-json shiki shiki-themes github-light github-dark","{\n  \"appId\": \"019f...\",\n  \"roomId\": \"019f...\",\n  \"actorTokenId\": \"019f...\",\n  \"reason\": \"dispatch\",\n  \"wakeId\": \"a91f2c...\",\n  \"ts\": 1765200000000\n}\n","POST body","json",[64,3048,3049,3054,3067,3078,3089,3101,3113,3123],{"__ignoreMap":137},[141,3050,3051],{"class":143,"line":144},[141,3052,3053],{"class":151},"{\n",[141,3055,3056,3059,3062,3065],{"class":143,"line":162},[141,3057,3058],{"class":175},"  \"appId\"",[141,3060,3061],{"class":151},": ",[141,3063,3064],{"class":158},"\"019f...\"",[141,3066,281],{"class":151},[141,3068,3069,3072,3074,3076],{"class":143,"line":169},[141,3070,3071],{"class":175},"  \"roomId\"",[141,3073,3061],{"class":151},[141,3075,3064],{"class":158},[141,3077,281],{"class":151},[141,3079,3080,3083,3085,3087],{"class":143,"line":195},[141,3081,3082],{"class":175},"  \"actorTokenId\"",[141,3084,3061],{"class":151},[141,3086,3064],{"class":158},[141,3088,281],{"class":151},[141,3090,3091,3094,3096,3099],{"class":143,"line":210},[141,3092,3093],{"class":175},"  \"reason\"",[141,3095,3061],{"class":151},[141,3097,3098],{"class":158},"\"dispatch\"",[141,3100,281],{"class":151},[141,3102,3103,3106,3108,3111],{"class":143,"line":215},[141,3104,3105],{"class":175},"  \"wakeId\"",[141,3107,3061],{"class":151},[141,3109,3110],{"class":158},"\"a91f2c...\"",[141,3112,281],{"class":151},[141,3114,3115,3118,3120],{"class":143,"line":248},[141,3116,3117],{"class":175},"  \"ts\"",[141,3119,3061],{"class":151},[141,3121,3122],{"class":175},"1765200000000\n",[141,3124,3125],{"class":143,"line":253},[141,3126,854],{"class":151},[14,3128,3129],{},"Verify the signature before acting on it:",[131,3131,3134],{"className":3132,"code":3133,"language":2907},[2905],"x-nolag-signature: sha256=\u003Chex HMAC-SHA256 of the raw body>\n",[64,3135,3133],{"__ignoreMap":137},[32,3137,3138,3148,3151,3158,3165],{},[35,3139,3140,3141,3144,3145,3147],{},"Wakes are ",[28,3142,3143],{},"debounced",": one per actor per ",[64,3146,2898],{}," window, not one per message.",[35,3149,3150],{},"The call is fire-and-forget and never blocks the publisher.",[35,3152,3153,2256,3156,119],{},[64,3154,3155],{},"timeoutMs",[64,3157,2730],{},[35,3159,3160,3161,3164],{},"Treat repeated ",[64,3162,3163],{},"wakeId"," values as no-ops; a cold start may already be in flight.",[35,3166,3167,3168,3171],{},"Respond ",[64,3169,3170],{},"2xx"," to acknowledge. NoLag then waits for the actor to reconnect, not\nfor the work to finish.",[77,3173,3175],{"type":3174},"warning",[14,3176,3177],{},"A failed wake is logged and dropped, not retried. If your endpoint is down when\nthe wake fires, the queued message waits for the actor to reconnect on its own.\nTreat the wake as a best-effort nudge rather than a delivery guarantee.",[18,3179,3181],{"id":3180},"best-practices","Best Practices",[32,3183,3184,3190,3196,3205,3211],{},[35,3185,3186,3189],{},[28,3187,3188],{},"Keep presence data small"," - Only include necessary information (username, status, avatar URL)",[35,3191,3192,3195],{},[28,3193,3194],{},"Use debouncing"," - For typing indicators, debounce updates to avoid flooding",[35,3197,3198,3201,3202,3204],{},[28,3199,3200],{},"Handle reconnections"," - Re-set room presence from your ",[64,3203,204],{}," handler in JavaScript and Go; the Python SDK re-sends it for you",[35,3206,3207,3210],{},[28,3208,3209],{},"Consider privacy"," - Let users opt out of presence tracking if needed",[35,3212,3213,3216],{},[28,3214,3215],{},"Use fetchPresence() sparingly"," - The local cache is usually sufficient once you have set presence in the room",[18,3218,3220],{"id":3219},"next-steps","Next Steps",[32,3222,3223,3228,3234,3240],{},[35,3224,3225],{},[72,3226,3227],{"href":117},"Observe presence across rooms with Lobbies",[35,3229,3230],{},[72,3231,3233],{"href":3232},"/docs/concepts/topics","Learn about Topics",[35,3235,3236],{},[72,3237,3239],{"href":3238},"/docs/concepts/rooms","Organize with Rooms",[35,3241,3242],{},[72,3243,3245],{"href":3244},"/docs/guides/chat-app","Build a Chat App",[3247,3248,3249],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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":137,"searchDepth":162,"depth":162,"links":3251},[3252,3253,3254,3257,3260,3261,3262,3263,3266,3271,3272],{"id":20,"depth":162,"text":21},{"id":122,"depth":162,"text":123},{"id":857,"depth":162,"text":858,"children":3255},[3256],{"id":898,"depth":169,"text":899},{"id":911,"depth":162,"text":912,"children":3258},[3259],{"id":1261,"depth":169,"text":1262},{"id":1316,"depth":162,"text":1317},{"id":1535,"depth":162,"text":1536},{"id":2123,"depth":162,"text":2124},{"id":2276,"depth":162,"text":2277,"children":3264},[3265],{"id":2573,"depth":169,"text":2574},{"id":2624,"depth":162,"text":75,"children":3267},[3268,3269,3270],{"id":2645,"depth":169,"text":2646},{"id":2849,"depth":169,"text":2850},{"id":3029,"depth":169,"text":3030},{"id":3180,"depth":162,"text":3181},{"id":3219,"depth":162,"text":3220},"Track presence in real-time with NoLag. Know who is online, handle join/leave events, discover backend workers and AI agents by capability, and keep scaled-to-zero actors addressable with Persistent Presence.","md",{},"/docs/concepts/presence",{"title":5,"description":3273},"docs/concepts/presence","k_ikYogxsKcoO9EXDZBBhYXUvXu18nsQvAcmOT_hQys",1789869419417]