[{"data":1,"prerenderedAt":2177},["ShallowReactive",2],{"navigation":3,"index":46,"index-blogs":86},[4],{"title":5,"path":6,"stem":7,"children":8,"page":45},"Blog","\u002Fblog","blog",[9,13,17,21,25,29,33,37,41],{"title":10,"path":11,"stem":12},"Audio Augmentation Techniques for Deep Learning","\u002Fblog\u002Faudio-augmentation","blog\u002Faudio-augmentation",{"title":14,"path":15,"stem":16},"Speed Up Whisper with CTranslate2","\u002Fblog\u002Fc-translate","blog\u002Fc-translate",{"title":18,"path":19,"stem":20},"Calling C++ and Go from Dart using FFI","\u002Fblog\u002Fdart-ffi-native-bridge","blog\u002Fdart-ffi-native-bridge",{"title":22,"path":23,"stem":24},"Russey Keo High School Memories","\u002Fblog\u002Fmemory","blog\u002Fmemory",{"title":26,"path":27,"stem":28},"My Dev Setup: Same Keys Everywhere","\u002Fblog\u002Fmy-dev-setup","blog\u002Fmy-dev-setup",{"title":30,"path":31,"stem":32},"Data Extraction with LangChain and Ollama Locally","\u002Fblog\u002Fpydantic-llm","blog\u002Fpydantic-llm",{"title":34,"path":35,"stem":36},"7-Segment LED Display with Proteus Simulation","\u002Fblog\u002Fseven-segment","blog\u002Fseven-segment",{"title":38,"path":39,"stem":40},"Understanding JWT Refresh Token Rotation & Revocation","\u002Fblog\u002Funderstand-refresh-token","blog\u002Funderstand-refresh-token",{"title":42,"path":43,"stem":44},"Khmer Speech Recognition","\u002Fblog\u002Fwhisper","blog\u002Fwhisper",false,{"id":47,"title":48,"about":49,"blog":52,"body":55,"description":56,"extension":57,"hero":58,"meta":79,"navigation":80,"path":81,"seo":82,"stem":84,"__hash__":85},"index\u002Findex.yml","Hey, I'm Phanith LIM",{"title":50,"description":51},"About","I'm a developer in Phnom Penh. I like building things and learning how they work.\nOutside of code: photos, running, family. I use Arch btw.\n",{"title":53,"description":54},"Writing","",null,"Software developer based in Phnom Penh, Cambodia.","yml",{"links":59,"images":63},[60],{"label":61,"to":6,"color":62},"Read the blog","neutral",[64,67,70,73,76],{"src":65,"alt":66},"\u002Fhero\u002Fphoto-1.webp","Bayon, Angkor Thom",{"src":68,"alt":69},"\u002Fhero\u002Fphoto-2.webp","Angkor Wat, upper terrace",{"src":71,"alt":72},"\u002Fhero\u002Fphoto-3.webp","Angkor Wat, Siem Reap",{"src":74,"alt":75},"\u002Fhero\u002Fphoto-4.webp","Morning light over Angkor",{"src":77,"alt":78},"\u002Fhero\u002Fphoto-5.webp","Temple ruins at dusk",{},true,"\u002F",{"title":83,"description":56},"Home","index","fyx_UpLA8hiMb151VtU2nJv4h4avtB2oxfcHqo1IFGw",[87,310,1969],{"id":88,"title":26,"author":89,"body":93,"date":300,"description":301,"extension":302,"image":303,"meta":304,"minRead":294,"navigation":80,"path":27,"seo":308,"stem":28,"__hash__":309},"blog\u002Fblog\u002Fmy-dev-setup.md",{"name":90,"avatar":91},"Phanith LIM",{"src":92,"alt":90},"\u002Fmyprofile.jpg",{"type":94,"value":95,"toc":290},"minimark",[96,104,107,112,119,130,134,140,143,171,178,182,188,195,199,210,214,221,265,268,272,281,284],[97,98,99,100],"p",{},"I switch between a lot of tools in a day: a terminal, Neovim, IntelliJ for Java, a browser. For a long time each one had its own shortcuts, and my hands kept pressing the wrong ones. So I rebuilt my setup around one rule: ",[101,102,103],"strong",{},"the same keys should do the same thing everywhere.",[97,105,106],{},"Here is what that looks like.",[108,109,111],"h3",{"id":110},"the-desktop-omarchy-and-hyprland","The desktop: Omarchy and Hyprland",[97,113,114],{},[115,116],"img",{"alt":117,"src":118},"Hyprland","\u002Fblog\u002Fmy-dev-setup\u002Fhyprland.svg",[97,120,121,122,129],{},"I run ",[123,124,128],"a",{"href":125,"rel":126},"https:\u002F\u002Fomarchy.org",[127],"nofollow","Omarchy",", an Arch Linux setup built on the Hyprland window manager. Windows tile themselves, and everything is a keyboard shortcut. Each monitor gets its own workspaces 1–10, and one key throws a window between my laptop and my external screen.",[108,131,133],{"id":132},"the-editor-neovim","The editor: Neovim",[97,135,136],{},[115,137],{"alt":138,"src":139},"Neovim","\u002Fblog\u002Fmy-dev-setup\u002Fneovim.png",[97,141,142],{},"Most of my writing and coding happens in Neovim with LazyVim. A few things I added on top:",[144,145,146,153,165],"ul",{},[147,148,149,152],"li",{},[101,150,151],{},"Harpoon"," for the four files I am working on right now, one key each.",[147,154,155,156,159,160,164],{},"A ",[101,157,158],{},"floating terminal"," on ",[161,162,163],"code",{},"Ctrl + \u002F",".",[147,166,167,170],{},[101,168,169],{},"Spring Boot support"," for Java, including a key that checks my Spring Modulith module boundaries.",[97,172,173,174,177],{},"When I want smooth animations, I open the same config in ",[101,175,176],{},"Neovide",", a GUI for Neovim.",[108,179,181],{"id":180},"sessions-tmux","Sessions: tmux",[97,183,184],{},[115,185],{"alt":186,"src":187},"tmux","\u002Fblog\u002Fmy-dev-setup\u002Ftmux.png",[97,189,190,191,194],{},"tmux keeps my terminals alive between sessions. I gave its bar rounded \"pills\" that recolour themselves when I change my desktop theme. In Neovide I copied tmux's ",[161,192,193],{},"Ctrl + b"," keys, so opening a tab or a split is the same in both.",[108,196,198],{"id":197},"java-intellij-with-vim-keys","Java: IntelliJ with Vim keys",[97,200,201,202,205,206,209],{},"For big Java projects I still use IntelliJ, with the IdeaVim plugin. I mapped it to match Neovim: the same leader key, the same ",[161,203,204],{},"gd",", the same ",[161,207,208],{},"Alt + 1–4"," to jump between my main files.",[108,211,213],{"id":212},"the-keyboard-cornix-lp","The keyboard: Cornix LP",[97,215,216,217,220],{},"The last piece is a split keyboard, the Cornix LP. Its layers turn the home row into shortcuts. My favourite part is that ",[101,218,219],{},"H \u002F J \u002F K \u002F L"," moves focus at every level:",[222,223,224,237],"table",{},[225,226,227],"thead",{},[228,229,230,234],"tr",{},[231,232,233],"th",{},"Hold",[231,235,236],{},"+ H \u002F J \u002F K \u002F L",[238,239,240,249,257],"tbody",{},[228,241,242,246],{},[243,244,245],"td",{},"Layer 1",[243,247,248],{},"Arrow keys",[228,250,251,254],{},[243,252,253],{},"Ctrl",[243,255,256],{},"Neovim splits and tmux panes",[228,258,259,262],{},[243,260,261],{},"Layer 2",[243,263,264],{},"Hyprland windows",[97,266,267],{},"One motion, three tools, no thinking.",[108,269,271],{"id":270},"try-it","Try it",[97,273,274,275,280],{},"Everything is in my ",[123,276,279],{"href":277,"rel":278},"https:\u002F\u002Fgithub.com\u002Fphanithlim\u002Fdotfiles",[127],"dotfiles on GitHub",", with a page of keybindings for each tool.",[97,282,283],{},"The best setup is not the one with the most features. It is the one where you stop noticing the tools.",[97,285,286],{},[287,288,289],"em",{},"Images: Neovim and tmux logos by Jason Long, Hyprland logo from the Hyprland project.",{"title":54,"searchDepth":291,"depth":291,"links":292},2,[293,295,296,297,298,299],{"id":110,"depth":294,"text":111},3,{"id":132,"depth":294,"text":133},{"id":180,"depth":294,"text":181},{"id":197,"depth":294,"text":198},{"id":212,"depth":294,"text":213},{"id":270,"depth":294,"text":271},"2026-10-05","Omarchy, Neovim, tmux, IntelliJ and a split keyboard, set up so one habit works in every tool.","md","\u002Fblog\u002Fmy-dev-setup\u002Fthumbnail.webp",{"tags":305},[306,138,307],"Linux","Productivity",{"title":26,"description":301},"I1CFIWCT88teEiYNfSjZB04U-TdAuJxa2AnLGdbHIPg",{"id":311,"title":18,"author":312,"body":314,"date":1960,"description":1961,"extension":302,"image":1962,"meta":1963,"minRead":404,"navigation":80,"path":19,"seo":1967,"stem":20,"__hash__":1968},"blog\u002Fblog\u002Fdart-ffi-native-bridge.md",{"name":90,"avatar":313},{"src":92,"alt":90},{"type":94,"value":315,"toc":1942},[316,330,333,336,341,348,352,363,484,487,525,529,544,818,821,862,866,872,1099,1102,1150,1153,1168,1171,1175,1178,1182,1308,1312,1388,1394,1396,1400,1404,1535,1539,1645,1648,1737,1740,1828,1831,1917,1919,1923,1931,1938],[97,317,318,321,322,325,326,329],{},[161,319,320],{},"dart:ffi"," lets Dart call into a shared library (",[161,323,324],{},".so"," or ",[161,327,328],{},".dylib",") directly. No platform channels, no plugin, no Flutter needed.",[97,331,332],{},"That is worth having when the code you want already exists in C or C++ and you would rather bind to it than port it, or when a hot loop is faster in a compiled language than in Dart. The cost is that you now have a build step per platform and a pointer or two to look after.",[97,334,335],{},"I wired up C++ and Go this way and worked out which other languages can join in. Both are below.",[337,338,340],"h2",{"id":339},"the-one-rule","The one rule",[97,342,343,344,347],{},"Dart can only call functions that expose a ",[101,345,346],{},"C ABI",": a plain C-compatible symbol with no name mangling. Every language reaches that differently, but the Dart side never changes: open the library, declare the types, look up the function.",[337,349,351],{"id":350},"c","C++",[97,353,354,355,358,359,362],{},"C++ mangles function names, so ",[161,356,357],{},"add"," becomes something unrecognisable. ",[161,360,361],{},"extern \"C\""," turns that off.",[364,365,369],"pre",{"className":366,"code":367,"language":368,"meta":54,"style":54},"language-cpp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F native\u002Fcpp\u002Fmylib.cpp\n#include \u003Ccstdint>\n\nextern \"C\" {\n  int32_t add(int32_t a, int32_t b) {\n    return a + b;\n  }\n}\n","cpp",[161,370,371,380,397,402,421,455,472,478],{"__ignoreMap":54},[372,373,376],"span",{"class":374,"line":375},"line",1,[372,377,379],{"class":378},"sHwdD","\u002F\u002F native\u002Fcpp\u002Fmylib.cpp\n",[372,381,382,386,390,394],{"class":374,"line":291},[372,383,385],{"class":384},"s7zQu","#include",[372,387,389],{"class":388},"sMK4o"," \u003C",[372,391,393],{"class":392},"sfazB","cstdint",[372,395,396],{"class":388},">\n",[372,398,399],{"class":374,"line":294},[372,400,401],{"emptyLinePlaceholder":80},"\n",[372,403,405,409,412,415,418],{"class":374,"line":404},4,[372,406,408],{"class":407},"spNyl","extern",[372,410,411],{"class":388}," \"",[372,413,414],{"class":392},"C",[372,416,417],{"class":388},"\"",[372,419,420],{"class":388}," {\n",[372,422,424,427,431,434,437,441,444,447,450,453],{"class":374,"line":423},5,[372,425,426],{"class":407},"  int32_t",[372,428,430],{"class":429},"s2Zo4"," add",[372,432,433],{"class":388},"(",[372,435,436],{"class":407},"int32_t",[372,438,440],{"class":439},"sHdIc"," a",[372,442,443],{"class":388},",",[372,445,446],{"class":407}," int32_t",[372,448,449],{"class":439}," b",[372,451,452],{"class":388},")",[372,454,420],{"class":388},[372,456,458,461,464,467,469],{"class":374,"line":457},6,[372,459,460],{"class":384},"    return",[372,462,440],{"class":463},"sTEyZ",[372,465,466],{"class":388}," +",[372,468,449],{"class":463},[372,470,471],{"class":388},";\n",[372,473,475],{"class":374,"line":474},7,[372,476,477],{"class":388},"  }\n",[372,479,481],{"class":374,"line":480},8,[372,482,483],{"class":388},"}\n",[97,485,486],{},"Build it with CMake:",[364,488,492],{"className":489,"code":490,"language":491,"meta":54,"style":54},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","cmake -S native\u002Fcpp -B native\u002Fcpp\u002Fbuild -DCMAKE_BUILD_TYPE=Release\ncmake --build native\u002Fcpp\u002Fbuild\n","bash",[161,493,494,515],{"__ignoreMap":54},[372,495,496,500,503,506,509,512],{"class":374,"line":375},[372,497,499],{"class":498},"sBMFI","cmake",[372,501,502],{"class":392}," -S",[372,504,505],{"class":392}," native\u002Fcpp",[372,507,508],{"class":392}," -B",[372,510,511],{"class":392}," native\u002Fcpp\u002Fbuild",[372,513,514],{"class":392}," -DCMAKE_BUILD_TYPE=Release\n",[372,516,517,519,522],{"class":374,"line":291},[372,518,499],{"class":498},[372,520,521],{"class":392}," --build",[372,523,524],{"class":392}," native\u002Fcpp\u002Fbuild\n",[337,526,528],{"id":527},"go","Go",[97,530,531,532,535,536,539,540,543],{},"Go exports through CGo. Two details will bite you if you miss them: the file needs ",[161,533,534],{},"package main"," with an empty ",[161,537,538],{},"func main()",", and ",[161,541,542],{},"\u002F\u002Fexport"," must sit on the line directly above the function, with no blank line between.",[364,545,548],{"className":546,"code":547,"language":527,"meta":54,"style":54},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F native\u002Fgo\u002Fmylib.go\npackage main\n\nimport \"C\"\n\n\u002F\u002Fexport multiply\nfunc multiply(a, b int32) int32 { return a * b }\n\n\u002F\u002Fexport fibonacci\nfunc fibonacci(n int32) int64 {\n    if n \u003C= 1 { return int64(n) }\n    a, b := int64(0), int64(1)\n    for i := int32(2); i \u003C= n; i++ { a, b = b, a+b }\n    return b\n}\n\nfunc main() {}\n",[161,549,550,555,563,567,579,583,588,628,632,638,660,691,724,786,794,799,804],{"__ignoreMap":54},[372,551,552],{"class":374,"line":375},[372,553,554],{"class":378},"\u002F\u002F native\u002Fgo\u002Fmylib.go\n",[372,556,557,560],{"class":374,"line":291},[372,558,559],{"class":388},"package",[372,561,562],{"class":498}," main\n",[372,564,565],{"class":374,"line":294},[372,566,401],{"emptyLinePlaceholder":80},[372,568,569,572,574,576],{"class":374,"line":404},[372,570,571],{"class":384},"import",[372,573,411],{"class":388},[372,575,414],{"class":498},[372,577,578],{"class":388},"\"\n",[372,580,581],{"class":374,"line":423},[372,582,401],{"emptyLinePlaceholder":80},[372,584,585],{"class":374,"line":457},[372,586,587],{"class":378},"\u002F\u002Fexport multiply\n",[372,589,590,593,596,598,600,602,604,607,609,611,614,617,620,623,626],{"class":374,"line":474},[372,591,592],{"class":388},"func",[372,594,595],{"class":429}," multiply",[372,597,433],{"class":388},[372,599,123],{"class":439},[372,601,443],{"class":388},[372,603,449],{"class":439},[372,605,606],{"class":407}," int32",[372,608,452],{"class":388},[372,610,606],{"class":407},[372,612,613],{"class":388}," {",[372,615,616],{"class":384}," return",[372,618,619],{"class":463}," a ",[372,621,622],{"class":388},"*",[372,624,625],{"class":463}," b ",[372,627,483],{"class":388},[372,629,630],{"class":374,"line":480},[372,631,401],{"emptyLinePlaceholder":80},[372,633,635],{"class":374,"line":634},9,[372,636,637],{"class":378},"\u002F\u002Fexport fibonacci\n",[372,639,641,643,646,648,651,653,655,658],{"class":374,"line":640},10,[372,642,592],{"class":388},[372,644,645],{"class":429}," fibonacci",[372,647,433],{"class":388},[372,649,650],{"class":439},"n",[372,652,606],{"class":407},[372,654,452],{"class":388},[372,656,657],{"class":407}," int64",[372,659,420],{"class":388},[372,661,663,666,669,672,676,678,680,682,684,686,688],{"class":374,"line":662},11,[372,664,665],{"class":384},"    if",[372,667,668],{"class":463}," n ",[372,670,671],{"class":388},"\u003C=",[372,673,675],{"class":674},"sbssI"," 1",[372,677,613],{"class":388},[372,679,616],{"class":384},[372,681,657],{"class":407},[372,683,433],{"class":388},[372,685,650],{"class":463},[372,687,452],{"class":388},[372,689,690],{"class":388}," }\n",[372,692,694,697,699,701,704,706,708,711,714,716,718,721],{"class":374,"line":693},12,[372,695,696],{"class":463},"    a",[372,698,443],{"class":388},[372,700,625],{"class":463},[372,702,703],{"class":388},":=",[372,705,657],{"class":407},[372,707,433],{"class":388},[372,709,710],{"class":674},"0",[372,712,713],{"class":388},"),",[372,715,657],{"class":407},[372,717,433],{"class":388},[372,719,720],{"class":674},"1",[372,722,723],{"class":388},")\n",[372,725,727,730,733,735,737,739,742,745,747,749,752,755,758,761,763,765,767,769,772,774,776,778,781,784],{"class":374,"line":726},13,[372,728,729],{"class":384},"    for",[372,731,732],{"class":463}," i ",[372,734,703],{"class":388},[372,736,606],{"class":407},[372,738,433],{"class":388},[372,740,741],{"class":674},"2",[372,743,744],{"class":388},");",[372,746,732],{"class":463},[372,748,671],{"class":388},[372,750,751],{"class":463}," n",[372,753,754],{"class":388},";",[372,756,757],{"class":463}," i",[372,759,760],{"class":388},"++",[372,762,613],{"class":388},[372,764,440],{"class":463},[372,766,443],{"class":388},[372,768,625],{"class":463},[372,770,771],{"class":388},"=",[372,773,449],{"class":463},[372,775,443],{"class":388},[372,777,440],{"class":463},[372,779,780],{"class":388},"+",[372,782,783],{"class":463},"b ",[372,785,483],{"class":388},[372,787,789,791],{"class":374,"line":788},14,[372,790,460],{"class":384},[372,792,793],{"class":463}," b\n",[372,795,797],{"class":374,"line":796},15,[372,798,483],{"class":388},[372,800,802],{"class":374,"line":801},16,[372,803,401],{"emptyLinePlaceholder":80},[372,805,807,809,812,815],{"class":374,"line":806},17,[372,808,592],{"class":388},[372,810,811],{"class":429}," main",[372,813,814],{"class":388},"()",[372,816,817],{"class":388}," {}\n",[97,819,820],{},"Build:",[364,822,824],{"className":489,"code":823,"language":491,"meta":54,"style":54},"cd native\u002Fgo && CGO_ENABLED=1 go build -o ..\u002Flibs\u002Flibgolib.so -buildmode=c-shared .\n",[161,825,826],{"__ignoreMap":54},[372,827,828,831,834,837,840,842,844,847,850,853,856,859],{"class":374,"line":375},[372,829,830],{"class":429},"cd",[372,832,833],{"class":392}," native\u002Fgo",[372,835,836],{"class":388}," &&",[372,838,839],{"class":463}," CGO_ENABLED",[372,841,771],{"class":388},[372,843,720],{"class":392},[372,845,846],{"class":498}," go",[372,848,849],{"class":392}," build",[372,851,852],{"class":392}," -o",[372,854,855],{"class":392}," ..\u002Flibs\u002Flibgolib.so",[372,857,858],{"class":392}," -buildmode=c-shared",[372,860,861],{"class":392}," .\n",[337,863,865],{"id":864},"the-dart-side","The Dart side",[97,867,868,869,164],{},"This part is the same no matter which language produced the library. Declare the native signature, declare the Dart signature, then ",[161,870,871],{},"lookupFunction",[364,873,877],{"className":874,"code":875,"language":876,"meta":54,"style":54},"language-dart shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F lib\u002Fsrc\u002Fgo_bindings.dart\nimport 'dart:ffi';\nimport 'package:path\u002Fpath.dart' as p;\n\ntypedef _FibonacciNative = Int64 Function(Int32 n);\ntypedef FibonacciDart = int Function(int n);\n\nclass GoLib {\n  final DynamicLibrary _lib;\n  late final FibonacciDart fibonacci;\n\n  GoLib(String libsDir)\n      : _lib = DynamicLibrary.open(p.join(libsDir, 'libgolib.so')) {\n    fibonacci = _lib.lookupFunction\u003C_FibonacciNative, FibonacciDart>('fibonacci');\n  }\n}\n","dart",[161,878,879,884,893,908,912,939,962,966,976,989,1003,1007,1020,1056,1091,1095],{"__ignoreMap":54},[372,880,881],{"class":374,"line":375},[372,882,883],{"class":378},"\u002F\u002F lib\u002Fsrc\u002Fgo_bindings.dart\n",[372,885,886,888,891],{"class":374,"line":291},[372,887,571],{"class":674},[372,889,890],{"class":392}," 'dart:ffi'",[372,892,471],{"class":388},[372,894,895,897,900,903,906],{"class":374,"line":294},[372,896,571],{"class":674},[372,898,899],{"class":392}," 'package:path\u002Fpath.dart'",[372,901,902],{"class":674}," as",[372,904,905],{"class":463}," p",[372,907,471],{"class":388},[372,909,910],{"class":374,"line":404},[372,911,401],{"emptyLinePlaceholder":80},[372,913,914,917,920,923,926,929,931,934,937],{"class":374,"line":423},[372,915,916],{"class":388},"typedef",[372,918,919],{"class":498}," _FibonacciNative",[372,921,922],{"class":388}," =",[372,924,925],{"class":498}," Int64",[372,927,928],{"class":498}," Function",[372,930,433],{"class":463},[372,932,933],{"class":498},"Int32",[372,935,936],{"class":463}," n)",[372,938,471],{"class":388},[372,940,941,943,946,948,951,953,955,958,960],{"class":374,"line":457},[372,942,916],{"class":388},[372,944,945],{"class":498}," FibonacciDart",[372,947,922],{"class":388},[372,949,950],{"class":498}," int",[372,952,928],{"class":498},[372,954,433],{"class":463},[372,956,957],{"class":498},"int",[372,959,936],{"class":463},[372,961,471],{"class":388},[372,963,964],{"class":374,"line":474},[372,965,401],{"emptyLinePlaceholder":80},[372,967,968,971,974],{"class":374,"line":480},[372,969,970],{"class":388},"class",[372,972,973],{"class":498}," GoLib",[372,975,420],{"class":463},[372,977,978,981,984,987],{"class":374,"line":634},[372,979,980],{"class":407},"  final",[372,982,983],{"class":498}," DynamicLibrary",[372,985,986],{"class":463}," _lib",[372,988,471],{"class":388},[372,990,991,994,997,999,1001],{"class":374,"line":640},[372,992,993],{"class":407},"  late",[372,995,996],{"class":407}," final",[372,998,945],{"class":498},[372,1000,645],{"class":463},[372,1002,471],{"class":388},[372,1004,1005],{"class":374,"line":662},[372,1006,401],{"emptyLinePlaceholder":80},[372,1008,1009,1012,1014,1017],{"class":374,"line":693},[372,1010,1011],{"class":498},"  GoLib",[372,1013,433],{"class":463},[372,1015,1016],{"class":498},"String",[372,1018,1019],{"class":463}," libsDir)\n",[372,1021,1022,1025,1028,1030,1032,1034,1037,1040,1042,1045,1048,1050,1053],{"class":374,"line":726},[372,1023,1024],{"class":388},"      :",[372,1026,1027],{"class":463}," _lib ",[372,1029,771],{"class":388},[372,1031,983],{"class":498},[372,1033,164],{"class":388},[372,1035,1036],{"class":429},"open",[372,1038,1039],{"class":463},"(p",[372,1041,164],{"class":388},[372,1043,1044],{"class":429},"join",[372,1046,1047],{"class":463},"(libsDir",[372,1049,443],{"class":388},[372,1051,1052],{"class":392}," 'libgolib.so'",[372,1054,1055],{"class":463},")) {\n",[372,1057,1058,1061,1063,1065,1067,1069,1072,1075,1078,1081,1084,1087,1089],{"class":374,"line":788},[372,1059,1060],{"class":463},"    fibonacci ",[372,1062,771],{"class":388},[372,1064,986],{"class":463},[372,1066,164],{"class":388},[372,1068,871],{"class":429},[372,1070,1071],{"class":463},"\u003C",[372,1073,1074],{"class":498},"_FibonacciNative",[372,1076,1077],{"class":463},", ",[372,1079,1080],{"class":498},"FibonacciDart",[372,1082,1083],{"class":463},">(",[372,1085,1086],{"class":392},"'fibonacci'",[372,1088,452],{"class":463},[372,1090,471],{"class":388},[372,1092,1093],{"class":374,"line":796},[372,1094,477],{"class":463},[372,1096,1097],{"class":374,"line":801},[372,1098,483],{"class":463},[97,1100,1101],{},"Then call it like ordinary Dart:",[364,1103,1105],{"className":874,"code":1104,"language":876,"meta":54,"style":54},"final go = GoLib(libsDir);\nprint(go.fibonacci(10)); \u002F\u002F 55\n",[161,1106,1107,1124],{"__ignoreMap":54},[372,1108,1109,1112,1115,1117,1119,1122],{"class":374,"line":375},[372,1110,1111],{"class":407},"final",[372,1113,1114],{"class":463}," go ",[372,1116,771],{"class":388},[372,1118,973],{"class":498},[372,1120,1121],{"class":463},"(libsDir)",[372,1123,471],{"class":388},[372,1125,1126,1129,1132,1134,1137,1139,1142,1145,1147],{"class":374,"line":291},[372,1127,1128],{"class":429},"print",[372,1130,1131],{"class":463},"(go",[372,1133,164],{"class":388},[372,1135,1136],{"class":429},"fibonacci",[372,1138,433],{"class":463},[372,1140,1141],{"class":674},"10",[372,1143,1144],{"class":463},"))",[372,1146,754],{"class":388},[372,1148,1149],{"class":378}," \u002F\u002F 55\n",[97,1151,1152],{},"Numbers are the easy case. They copy across on their own, and nothing needs cleaning up afterwards.",[97,1154,1155,1156,1159,1160,1163,1164,1167],{},"Strings do not. A ",[161,1157,1158],{},"Pointer\u003CUtf8>"," is memory you asked for, so you convert with ",[161,1161,1162],{},"toNativeUtf8()",", pass the pointer, read the reply back with ",[161,1165,1166],{},"toDartString()",", and free what you allocated. Forget the free and you have a leak that no garbage collector will find for you.",[1169,1170],"hr",{},[337,1172,1174],{"id":1173},"which-languages-work","Which languages work",[97,1176,1177],{},"Anything that compiles to a shared library with C-compatible symbols.",[108,1179,1181],{"id":1180},"works","Works",[222,1183,1184,1194],{},[225,1185,1186],{},[228,1187,1188,1191],{},[231,1189,1190],{},"Language",[231,1192,1193],{},"Export mechanism",[238,1195,1196,1203,1213,1226,1239,1250,1261,1272,1283,1294],{},[228,1197,1198,1200],{},[243,1199,414],{},[243,1201,1202],{},"Native, no wrapper needed",[228,1204,1205,1207],{},[243,1206,351],{},[243,1208,1209,1212],{},[161,1210,1211],{},"extern \"C\" { ... }"," block",[228,1214,1215,1217],{},[243,1216,528],{},[243,1218,1219,1220,1222,1223],{},"CGo ",[161,1221,542],{}," + ",[161,1224,1225],{},"-buildmode=c-shared",[228,1227,1228,1231],{},[243,1229,1230],{},"Rust",[243,1232,1233,1222,1236],{},[161,1234,1235],{},"#[no_mangle] extern \"C\" fn",[161,1237,1238],{},"crate-type = [\"cdylib\"]",[228,1240,1241,1244],{},[243,1242,1243],{},"Zig",[243,1245,1246,1249],{},[161,1247,1248],{},"export fn"," + compile as shared library",[228,1251,1252,1255],{},[243,1253,1254],{},"Nim",[243,1256,1257,1260],{},[161,1258,1259],{},"{.exportc, dynlib.}"," pragma",[228,1262,1263,1266],{},[243,1264,1265],{},"D",[243,1267,1268,1271],{},[161,1269,1270],{},"extern(C)"," linkage",[228,1273,1274,1277],{},[243,1275,1276],{},"Kotlin\u002FNative",[243,1278,1279,1282],{},[161,1280,1281],{},"@CName"," annotation (not Kotlin\u002FJVM)",[228,1284,1285,1288],{},[243,1286,1287],{},"Swift",[243,1289,1290,1293],{},[161,1291,1292],{},"@_cdecl(\"name\")"," on a free function",[228,1295,1296,1299],{},[243,1297,1298],{},"Fortran",[243,1300,1301,1222,1304,1307],{},[161,1302,1303],{},"ISO_C_BINDING",[161,1305,1306],{},"bind(C)"," attribute",[108,1309,1311],{"id":1310},"does-not-work","Does not work",[222,1313,1314,1323],{},[225,1315,1316],{},[228,1317,1318,1320],{},[231,1319,1190],{},[231,1321,1322],{},"Reason",[238,1324,1325,1333,1341,1349,1357,1365,1373,1381],{},[228,1326,1327,1330],{},[243,1328,1329],{},"Python",[243,1331,1332],{},"No C ABI export, would need embedding CPython",[228,1334,1335,1338],{},[243,1336,1337],{},"Java",[243,1339,1340],{},"JVM bytecode only; JNI goes the other direction",[228,1342,1343,1346],{},[243,1344,1345],{},"Kotlin (JVM)",[243,1347,1348],{},"Runs on the JVM, no C ABI",[228,1350,1351,1354],{},[243,1352,1353],{},"Scala",[243,1355,1356],{},"JVM, no C ABI",[228,1358,1359,1362],{},[243,1360,1361],{},"C# \u002F .NET",[243,1363,1364],{},"CLR managed runtime, no native C ABI export",[228,1366,1367,1370],{},[243,1368,1369],{},"JavaScript \u002F TypeScript",[243,1371,1372],{},"Runs in a JS engine, no C ABI",[228,1374,1375,1378],{},[243,1376,1377],{},"Ruby",[243,1379,1380],{},"Interpreter-based, no C ABI export",[228,1382,1383,1386],{},[243,1384,1385],{},"PHP",[243,1387,1380],{},[97,1389,1390,1391,1393],{},"The pattern behind both tables: if the language can produce a ",[161,1392,324],{}," with plain C symbol names, Dart FFI can call it. If it needs its own runtime to be alive first, it cannot.",[1169,1395],{},[337,1397,1399],{"id":1398},"type-mappings","Type mappings",[108,1401,1403],{"id":1402},"c-and-c","C and C++",[222,1405,1406,1416],{},[225,1407,1408],{},[228,1409,1410,1413],{},[231,1411,1412],{},"Dart FFI type",[231,1414,1415],{},"C \u002F C++ type",[238,1417,1418,1430,1442,1452,1464,1476,1488,1500,1512,1523],{},[228,1419,1420,1425],{},[243,1421,1422],{},[161,1423,1424],{},"Int8",[243,1426,1427],{},[161,1428,1429],{},"int8_t",[228,1431,1432,1437],{},[243,1433,1434],{},[161,1435,1436],{},"Int16",[243,1438,1439],{},[161,1440,1441],{},"int16_t",[228,1443,1444,1448],{},[243,1445,1446],{},[161,1447,933],{},[243,1449,1450],{},[161,1451,436],{},[228,1453,1454,1459],{},[243,1455,1456],{},[161,1457,1458],{},"Int64",[243,1460,1461],{},[161,1462,1463],{},"int64_t",[228,1465,1466,1471],{},[243,1467,1468],{},[161,1469,1470],{},"Float",[243,1472,1473],{},[161,1474,1475],{},"float",[228,1477,1478,1483],{},[243,1479,1480],{},[161,1481,1482],{},"Double",[243,1484,1485],{},[161,1486,1487],{},"double",[228,1489,1490,1495],{},[243,1491,1492],{},[161,1493,1494],{},"Bool",[243,1496,1497],{},[161,1498,1499],{},"bool",[228,1501,1502,1507],{},[243,1503,1504],{},[161,1505,1506],{},"Void",[243,1508,1509],{},[161,1510,1511],{},"void",[228,1513,1514,1518],{},[243,1515,1516],{},[161,1517,1158],{},[243,1519,1520],{},[161,1521,1522],{},"char*",[228,1524,1525,1530],{},[243,1526,1527],{},[161,1528,1529],{},"Pointer\u003CT>",[243,1531,1532],{},[161,1533,1534],{},"T*",[108,1536,1538],{"id":1537},"go-via-cgo","Go (via CGo)",[222,1540,1541,1550],{},[225,1542,1543],{},[228,1544,1545,1547],{},[231,1546,1412],{},[231,1548,1549],{},"Go \u002F CGo type",[238,1551,1552,1567,1581,1595,1609,1622,1633],{},[228,1553,1554,1558],{},[243,1555,1556],{},[161,1557,933],{},[243,1559,1560,1563,1564],{},[161,1561,1562],{},"int32"," \u002F ",[161,1565,1566],{},"C.int",[228,1568,1569,1573],{},[243,1570,1571],{},[161,1572,1458],{},[243,1574,1575,1563,1578],{},[161,1576,1577],{},"int64",[161,1579,1580],{},"C.longlong",[228,1582,1583,1587],{},[243,1584,1585],{},[161,1586,1470],{},[243,1588,1589,1563,1592],{},[161,1590,1591],{},"float32",[161,1593,1594],{},"C.float",[228,1596,1597,1601],{},[243,1598,1599],{},[161,1600,1482],{},[243,1602,1603,1563,1606],{},[161,1604,1605],{},"float64",[161,1607,1608],{},"C.double",[228,1610,1611,1615],{},[243,1612,1613],{},[161,1614,1494],{},[243,1616,1617,1563,1619,1621],{},[161,1618,1499],{},[161,1620,1566],{}," (0\u002F1)",[228,1623,1624,1628],{},[243,1625,1626],{},[161,1627,1158],{},[243,1629,1630],{},[161,1631,1632],{},"*C.char",[228,1634,1635,1640],{},[243,1636,1637],{},[161,1638,1639],{},"Pointer\u003CInt32>",[243,1641,1642],{},[161,1643,1644],{},"*C.int",[108,1646,1230],{"id":1647},"rust",[222,1649,1650,1659],{},[225,1651,1652],{},[228,1653,1654,1656],{},[231,1655,1412],{},[231,1657,1658],{},"Rust type",[238,1660,1661,1672,1683,1694,1705,1715,1726],{},[228,1662,1663,1667],{},[243,1664,1665],{},[161,1666,933],{},[243,1668,1669],{},[161,1670,1671],{},"i32",[228,1673,1674,1678],{},[243,1675,1676],{},[161,1677,1458],{},[243,1679,1680],{},[161,1681,1682],{},"i64",[228,1684,1685,1689],{},[243,1686,1687],{},[161,1688,1470],{},[243,1690,1691],{},[161,1692,1693],{},"f32",[228,1695,1696,1700],{},[243,1697,1698],{},[161,1699,1482],{},[243,1701,1702],{},[161,1703,1704],{},"f64",[228,1706,1707,1711],{},[243,1708,1709],{},[161,1710,1494],{},[243,1712,1713],{},[161,1714,1499],{},[228,1716,1717,1721],{},[243,1718,1719],{},[161,1720,1158],{},[243,1722,1723],{},[161,1724,1725],{},"*const c_char",[228,1727,1728,1732],{},[243,1729,1730],{},[161,1731,1529],{},[243,1733,1734],{},[161,1735,1736],{},"*mut T",[108,1738,1276],{"id":1739},"kotlinnative",[222,1741,1742,1751],{},[225,1743,1744],{},[228,1745,1746,1748],{},[231,1747,1412],{},[231,1749,1750],{},"Kotlin type",[238,1752,1753,1764,1775,1785,1795,1806,1817],{},[228,1754,1755,1759],{},[243,1756,1757],{},[161,1758,933],{},[243,1760,1761],{},[161,1762,1763],{},"Int",[228,1765,1766,1770],{},[243,1767,1768],{},[161,1769,1458],{},[243,1771,1772],{},[161,1773,1774],{},"Long",[228,1776,1777,1781],{},[243,1778,1779],{},[161,1780,1470],{},[243,1782,1783],{},[161,1784,1470],{},[228,1786,1787,1791],{},[243,1788,1789],{},[161,1790,1482],{},[243,1792,1793],{},[161,1794,1482],{},[228,1796,1797,1801],{},[243,1798,1799],{},[161,1800,1494],{},[243,1802,1803],{},[161,1804,1805],{},"Boolean",[228,1807,1808,1812],{},[243,1809,1810],{},[161,1811,1158],{},[243,1813,1814],{},[161,1815,1816],{},"CPointer\u003CByteVar>?",[228,1818,1819,1823],{},[243,1820,1821],{},[161,1822,1639],{},[243,1824,1825],{},[161,1826,1827],{},"CPointer\u003CIntVar>?",[108,1829,1287],{"id":1830},"swift",[222,1832,1833,1842],{},[225,1834,1835],{},[228,1836,1837,1839],{},[231,1838,1412],{},[231,1840,1841],{},"Swift type",[238,1843,1844,1854,1864,1874,1884,1894,1905],{},[228,1845,1846,1850],{},[243,1847,1848],{},[161,1849,933],{},[243,1851,1852],{},[161,1853,933],{},[228,1855,1856,1860],{},[243,1857,1858],{},[161,1859,1458],{},[243,1861,1862],{},[161,1863,1458],{},[228,1865,1866,1870],{},[243,1867,1868],{},[161,1869,1470],{},[243,1871,1872],{},[161,1873,1470],{},[228,1875,1876,1880],{},[243,1877,1878],{},[161,1879,1482],{},[243,1881,1882],{},[161,1883,1482],{},[228,1885,1886,1890],{},[243,1887,1888],{},[161,1889,1494],{},[243,1891,1892],{},[161,1893,1494],{},[228,1895,1896,1900],{},[243,1897,1898],{},[161,1899,1158],{},[243,1901,1902],{},[161,1903,1904],{},"UnsafePointer\u003CCChar>?",[228,1906,1907,1911],{},[243,1908,1909],{},[161,1910,1158],{},[243,1912,1913,1916],{},[161,1914,1915],{},"UnsafeMutablePointer\u003CCChar>?"," (out)",[1169,1918],{},[337,1920,1922],{"id":1921},"source","Source",[97,1924,1925,1926],{},"Source code: ",[123,1927,1930],{"href":1928,"rel":1929},"https:\u002F\u002Fgithub.com\u002Fphanithlim\u002Fdart-native",[127],"phanithlim\u002Fdart-native",[97,1932,1933,1934,1937],{},"Everything lives under ",[161,1935,1936],{},"native\u002F",", one directory per language, each with its own README and build steps. Clone it, build one of the libraries, and the Dart side will call straight into it.",[1939,1940,1941],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":54,"searchDepth":291,"depth":291,"links":1943},[1944,1945,1946,1947,1948,1952,1959],{"id":339,"depth":291,"text":340},{"id":350,"depth":291,"text":351},{"id":527,"depth":291,"text":528},{"id":864,"depth":291,"text":865},{"id":1173,"depth":291,"text":1174,"children":1949},[1950,1951],{"id":1180,"depth":294,"text":1181},{"id":1310,"depth":294,"text":1311},{"id":1398,"depth":291,"text":1399,"children":1953},[1954,1955,1956,1957,1958],{"id":1402,"depth":294,"text":1403},{"id":1537,"depth":294,"text":1538},{"id":1647,"depth":294,"text":1230},{"id":1739,"depth":294,"text":1276},{"id":1830,"depth":294,"text":1287},{"id":1921,"depth":291,"text":1922},"2026-06-02","Calling native libraries from Dart with dart:ffi, and which languages work.","\u002Fblog\u002Fdart-ffi-native-bridge\u002Fcover.jpg",{"tags":1964},[1965,1966],"Dart","FFI",{"title":18,"description":1961},"ZhFB4P4xj5eoA7PeGlInQmuG8DbLq9e34CcYib-16dc",{"id":1970,"title":38,"author":1971,"body":1973,"date":2168,"description":2169,"extension":302,"image":2170,"meta":2171,"minRead":291,"navigation":80,"path":39,"seo":2175,"stem":40,"__hash__":2176},"blog\u002Fblog\u002Funderstand-refresh-token.md",{"name":90,"avatar":1972},{"src":92,"alt":90},{"type":94,"value":1974,"toc":2161},[1975,1982,1985,1989,1992,2013,2016,2020,2023,2037,2040,2044,2047,2110,2116,2136,2140,2151,2154,2158],[97,1976,1977,1978,1981],{},"If you rotate refresh tokens, every refresh hands back a new access token ",[287,1979,1980],{},"and"," a new refresh token. So why set the refresh token to 7 days? If it gets replaced anyway, why not 30 minutes?",[97,1983,1984],{},"Because the expiry is not about how long the token lives. It is about how long a user can be away.",[108,1986,1988],{"id":1987},"a-30-minute-token-breaks-lunch","A 30-minute token breaks lunch",[97,1990,1991],{},"Say you set the refresh token to 30 minutes, for extra security.",[1993,1994,1995,2001,2007],"ol",{},[147,1996,1997,2000],{},[101,1998,1999],{},"12:00",": the user logs in. Access token: 15 minutes. Refresh token: 30 minutes.",[147,2002,2003,2006],{},[101,2004,2005],{},"12:15",": they finish what they were doing and close the laptop to get lunch.",[147,2008,2009,2012],{},[101,2010,2011],{},"12:50",": they come back and open the app.",[97,2014,2015],{},"Both tokens are dead. The access token expired at 12:15, the refresh token at 12:30. The user has to type their password again because they stepped away for 35 minutes, and they blame your app for it.",[108,2017,2019],{"id":2018},"_7-days-is-a-sliding-window","7 days is a sliding window",[97,2021,2022],{},"With a 7-day refresh token you get a window that keeps moving:",[144,2024,2025,2031],{},[147,2026,2027,2030],{},[101,2028,2029],{},"Active user:"," every refresh resets the 7-day timer. As long as they keep using the app, they stay logged in.",[147,2032,2033,2036],{},[101,2034,2035],{},"User on holiday:"," no app for 10 days, the timer runs out, and they log in again when they return.",[97,2038,2039],{},"That is the real setting you are choosing: how long inactivity is allowed before a password is required.",[108,2041,2043],{"id":2042},"expiry-is-the-clock-revocation-is-the-kill-switch","Expiry is the clock, revocation is the kill switch",[97,2045,2046],{},"Expiry is a token dying of old age. Revocation is you killing it on purpose.",[222,2048,2049,2063],{},[225,2050,2051],{},[228,2052,2053,2057,2060],{},[231,2054,2056],{"align":2055},"left","Feature",[231,2058,2059],{"align":2055},"Expiration",[231,2061,2062],{"align":2055},"Revocation",[238,2064,2065,2078,2091],{},[228,2066,2067,2072,2075],{},[243,2068,2069],{"align":2055},[101,2070,2071],{},"Controlled by",[243,2073,2074],{"align":2055},"The clock",[243,2076,2077],{"align":2055},"The admin \u002F server",[228,2079,2080,2085,2088],{},[243,2081,2082],{"align":2055},[101,2083,2084],{},"Question",[243,2086,2087],{"align":2055},"\"Is this token too old?\"",[243,2089,2090],{"align":2055},"\"Is this token still allowed?\"",[228,2092,2093,2098,2104],{},[243,2094,2095],{"align":2055},[101,2096,2097],{},"Database",[243,2099,2100,2101],{"align":2055},"Checked via ",[161,2102,2103],{},"expiry_date",[243,2105,2100,2106,2109],{"align":2055},[161,2107,2108],{},"is_revoked"," column",[97,2111,2112,2113,2115],{},"That ",[161,2114,2108],{}," column earns its place three times over:",[144,2117,2118,2124,2130],{},[147,2119,2120,2123],{},[101,2121,2122],{},"Logout."," The server marks the token revoked, so it can never be used again.",[147,2125,2126,2129],{},[101,2127,2128],{},"Password reset."," Revoke every token for that user and all their sessions drop at once.",[147,2131,2132,2135],{},[101,2133,2134],{},"Stolen token."," If someone replays an old rotated token, the server sees it is already revoked and can lock the account.",[108,2137,2139],{"id":2138},"rotation-step-by-step","Rotation, step by step",[1993,2141,2142,2145,2148],{},[147,2143,2144],{},"The client logs in with a username and password. The server stores refresh token 1 with a 7-day expiry and returns access token 1 and refresh token 1.",[147,2146,2147],{},"Access token 1 expires. The client asks for a refresh and sends refresh token 1.",[147,2149,2150],{},"The server checks that refresh token 1 is valid and not revoked, marks it revoked, stores a new refresh token 2 with a fresh 7-day window, and returns access token 2 and refresh token 2.",[97,2152,2153],{},"Step 3 is the whole trick. The old token is revoked the moment it is used, so any later attempt to reuse it is both rejected and a signal that something is wrong.",[108,2155,2157],{"id":2156},"in-short","In short",[97,2159,2160],{},"The 7-day expiry is your inactivity limit: it keeps active users logged in. Rotation and revocation are what make a long-lived session safe to hand out.",{"title":54,"searchDepth":291,"depth":291,"links":2162},[2163,2164,2165,2166,2167],{"id":1987,"depth":294,"text":1988},{"id":2018,"depth":294,"text":2019},{"id":2042,"depth":294,"text":2043},{"id":2138,"depth":294,"text":2139},{"id":2156,"depth":294,"text":2157},"2026-01-21","Why a refresh token lives for 7 days, and what the revoke column is for.","\u002Fblog\u002Funderstand-refresh-token\u002Fcover.jpg",{"tags":2172},[2173,2174],"Security","Authentication",{"title":38,"description":2169},"F_pD6V8k8F6n66V8xTQzFATiIVrA5DSsQTzSej3E8pQ",1791981008003]