[{"data":1,"prerenderedAt":807},["ShallowReactive",2],{"docs:doc:\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fprzegladarka":3,"docs:pages:docs_pl":737},{"id":4,"title":5,"alt":6,"body":7,"description":727,"extension":728,"key":729,"meta":730,"navigation":90,"order":731,"path":732,"placeholder":733,"seo":734,"stem":735,"__hash__":736},"docs_pl\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fprzegladarka.md","Zwykła przeglądarka","\u002Fen\u002Fdocs\u002Fguides\u002Fquickstarts\u002Fbrowser",{"type":8,"value":9,"toc":720},"minimark",[10,19,44,47,52,55,187,213,251,255,262,391,404,414,418,488,504,507,573,588,592,655,669,682,686,711,716],[11,12,13,14,18],"p",{},"Dla aplikacji jednostronicowej bez naszego modułu: Vite i czysty TypeScript,\nReact, Svelte, Solid albo framework, o którym nigdy nie słyszeliśmy.\n",[15,16,17],"code",{},"@kleora-io\u002Fbrowser"," to klient authorization code z PKCE i nie wie nic o\nTwoim frameworku.",[20,21,26],"pre",{"className":22,"code":23,"language":24,"meta":25,"style":25},"language-bash shiki shiki-themes github-light","npm install @kleora-io\u002Fbrowser\n","bash","",[15,27,28],{"__ignoreMap":25},[29,30,33,37,41],"span",{"class":31,"line":32},"line",1,[29,34,36],{"class":35},"s7eDp","npm",[29,38,40],{"class":39},"sYBdl"," install",[29,42,43],{"class":39}," @kleora-io\u002Fbrowser\n",[11,45,46],{},"Bez zależności w czasie działania, wyłącznie moduły ES.",[48,49,51],"h2",{"id":50},"_1-utwórz-klienta-raz","1. Utwórz klienta raz",[11,53,54],{},"Jedna instancja na całą aplikację, we własnym module. Druga instancja to\nnajprostszy sposób, by dwie połowy tej samej strony miały dwa różne zdania o\ntym, kto jest zalogowany.",[20,56,60],{"className":57,"code":58,"language":59,"meta":25,"style":25},"language-ts shiki shiki-themes github-light","\u002F\u002F src\u002Fkleora.ts\nimport { createKleora } from '@kleora-io\u002Fbrowser'\n\nexport const kleora = createKleora({\n  issuer: import.meta.env.VITE_KLEORA_ISSUER,\n  clientId: import.meta.env.VITE_KLEORA_CLIENT_ID,\n  redirectUri: location.origin + '\u002Fcallback',\n  storage: 'session',\n})\n","ts",[15,61,62,68,85,92,114,137,156,170,181],{"__ignoreMap":25},[29,63,64],{"class":31,"line":32},[29,65,67],{"class":66},"sAwPA","\u002F\u002F src\u002Fkleora.ts\n",[29,69,71,75,79,82],{"class":31,"line":70},2,[29,72,74],{"class":73},"sD7c4","import",[29,76,78],{"class":77},"sgsFI"," { createKleora } ",[29,80,81],{"class":73},"from",[29,83,84],{"class":39}," '@kleora-io\u002Fbrowser'\n",[29,86,88],{"class":31,"line":87},3,[29,89,91],{"emptyLinePlaceholder":90},true,"\n",[29,93,95,98,101,105,108,111],{"class":31,"line":94},4,[29,96,97],{"class":73},"export",[29,99,100],{"class":73}," const",[29,102,104],{"class":103},"sYu0t"," kleora",[29,106,107],{"class":73}," =",[29,109,110],{"class":35}," createKleora",[29,112,113],{"class":77},"({\n",[29,115,117,120,122,125,128,131,134],{"class":31,"line":116},5,[29,118,119],{"class":77},"  issuer: ",[29,121,74],{"class":73},[29,123,124],{"class":77},".",[29,126,127],{"class":103},"meta",[29,129,130],{"class":77},".env.",[29,132,133],{"class":103},"VITE_KLEORA_ISSUER",[29,135,136],{"class":77},",\n",[29,138,140,143,145,147,149,151,154],{"class":31,"line":139},6,[29,141,142],{"class":77},"  clientId: ",[29,144,74],{"class":73},[29,146,124],{"class":77},[29,148,127],{"class":103},[29,150,130],{"class":77},[29,152,153],{"class":103},"VITE_KLEORA_CLIENT_ID",[29,155,136],{"class":77},[29,157,159,162,165,168],{"class":31,"line":158},7,[29,160,161],{"class":77},"  redirectUri: location.origin ",[29,163,164],{"class":73},"+",[29,166,167],{"class":39}," '\u002Fcallback'",[29,169,136],{"class":77},[29,171,173,176,179],{"class":31,"line":172},8,[29,174,175],{"class":77},"  storage: ",[29,177,178],{"class":39},"'session'",[29,180,136],{"class":77},[29,182,184],{"class":31,"line":183},9,[29,185,186],{"class":77},"})\n",[11,188,189,192,193,196,197,200,201,204,205,208,209,212],{},[15,190,191],{},"issuer",", ",[15,194,195],{},"clientId"," i ",[15,198,199],{},"redirectUri"," są wymagane; reszta ma wartości domyślne.\n",[15,202,203],{},"scope"," to domyślnie ",[15,206,207],{},"openid profile email offline_access"," — zostaw w nim\n",[15,210,211],{},"offline_access",", bo inaczej nie zostanie wydany token odświeżania i nic nie\nodnowi się po cichu.",[214,215,218],"callout",{"title":216,"type":217},"Skąd tutaj storage: 'session'","note",[11,219,220,221,224,225,228,229,196,232,235,236,238,239,242,243,246,247,250],{},"Domyślne jest ",[15,222,223],{},"memory",": oba tokeny siedzą w domknięciu i nie przeżywają\nniczego, więc przeładowanie strony zależy od ",[15,226,227],{},"checkSession()",", a jego cicha\nścieżka działa tylko wtedy, gdy aplikacja i jej issuer są tą samą witryną.\n",[15,230,231],{},"http:\u002F\u002Flocalhost:5173",[15,233,234],{},"acme.sandbox.kleora.eu"," nią nie są, więc na\nmaszynie deweloperskiej ",[15,237,223],{}," oznacza widoczny objazd przy każdym\nprzeładowaniu. ",[15,240,241],{},"session"," trzyma token odświeżania w ",[15,244,245],{},"sessionStorage",": należy on\ndo jednej karty, znika razem z nią i wystarcza, by przeżyć przeładowanie bez\nobjazdu. ",[15,248,249],{},"localStorage"," nie jest opcją i nie będzie.",[48,252,254],{"id":253},"_2-obsłuż-powrót","2. Obsłuż powrót",[11,256,257,258,261],{},"Issuer odsyła przeglądarkę na ",[15,259,260],{},"\u002Fcallback"," z kodem autoryzacyjnym. Wymień go tam\ni odstaw człowieka tam, skąd wyszedł:",[20,263,265],{"className":57,"code":264,"language":59,"meta":25,"style":25},"import { kleora } from '.\u002Fkleora'\n\nif (location.pathname === '\u002Fcallback') {\n  const { appState } = await kleora.handleRedirectCallback()\n  const returnTo = (appState as { returnTo?: string })?.returnTo ?? '\u002F'\n  history.replaceState(null, '', returnTo)\n}\n",[15,266,267,279,283,299,328,364,386],{"__ignoreMap":25},[29,268,269,271,274,276],{"class":31,"line":32},[29,270,74],{"class":73},[29,272,273],{"class":77}," { kleora } ",[29,275,81],{"class":73},[29,277,278],{"class":39}," '.\u002Fkleora'\n",[29,280,281],{"class":31,"line":70},[29,282,91],{"emptyLinePlaceholder":90},[29,284,285,288,291,294,296],{"class":31,"line":87},[29,286,287],{"class":73},"if",[29,289,290],{"class":77}," (location.pathname ",[29,292,293],{"class":73},"===",[29,295,167],{"class":39},[29,297,298],{"class":77},") {\n",[29,300,301,304,307,310,313,316,319,322,325],{"class":31,"line":94},[29,302,303],{"class":73},"  const",[29,305,306],{"class":77}," { ",[29,308,309],{"class":103},"appState",[29,311,312],{"class":77}," } ",[29,314,315],{"class":73},"=",[29,317,318],{"class":73}," await",[29,320,321],{"class":77}," kleora.",[29,323,324],{"class":35},"handleRedirectCallback",[29,326,327],{"class":77},"()\n",[29,329,330,332,335,337,340,343,345,349,352,355,358,361],{"class":31,"line":116},[29,331,303],{"class":73},[29,333,334],{"class":103}," returnTo",[29,336,107],{"class":73},[29,338,339],{"class":77}," (appState ",[29,341,342],{"class":73},"as",[29,344,306],{"class":77},[29,346,348],{"class":347},"sqxcx","returnTo",[29,350,351],{"class":73},"?:",[29,353,354],{"class":103}," string",[29,356,357],{"class":77}," })?.returnTo ",[29,359,360],{"class":73},"??",[29,362,363],{"class":39}," '\u002F'\n",[29,365,366,369,372,375,378,380,383],{"class":31,"line":139},[29,367,368],{"class":77},"  history.",[29,370,371],{"class":35},"replaceState",[29,373,374],{"class":77},"(",[29,376,377],{"class":103},"null",[29,379,192],{"class":77},[29,381,382],{"class":39},"''",[29,384,385],{"class":77},", returnTo)\n",[29,387,388],{"class":31,"line":158},[29,389,390],{"class":77},"}\n",[11,392,393,396,397,196,400,403],{},[15,394,395],{},"handleRedirectCallback()"," sprawdza ",[15,398,399],{},"state",[15,401,402],{},"iss",", wymienia kod z użyciem\nweryfikatora PKCE, weryfikuje ID token względem zestawu kluczy issuera,\nzapisuje tokeny i usuwa dane logowania z paska adresu.",[11,405,406,407,409,410,413],{},"Twój serwer deweloperski musi oddawać HTML aplikacji także pod ",[15,408,260],{},", nie\ntylko pod ",[15,411,412],{},"\u002F",". Serwer deweloperski Vite robi to dla nieznanych ścieżek sam;\nzwykły serwer plików statycznych nie robi i odpowiada 404 na jedyną nawigację,\nktóra ma tu znaczenie.",[48,415,417],{"id":416},"_3-zaloguj-i-wiedz-kiedy-jesteś-zalogowany","3. Zaloguj i wiedz, kiedy jesteś zalogowany",[20,419,421],{"className":57,"code":420,"language":59,"meta":25,"style":25},"if (!(await kleora.checkSession())) {\n  await kleora.loginWithRedirect({ returnTo: location.pathname })\n}\n\nconst user = kleora.getUser() \u002F\u002F sub, email, name, tenant, tenant_slug…\n",[15,422,423,446,459,463,467],{"__ignoreMap":25},[29,424,425,427,430,433,435,438,440,443],{"class":31,"line":32},[29,426,287],{"class":73},[29,428,429],{"class":77}," (",[29,431,432],{"class":73},"!",[29,434,374],{"class":77},[29,436,437],{"class":73},"await",[29,439,321],{"class":77},[29,441,442],{"class":35},"checkSession",[29,444,445],{"class":77},"())) {\n",[29,447,448,451,453,456],{"class":31,"line":70},[29,449,450],{"class":73},"  await",[29,452,321],{"class":77},[29,454,455],{"class":35},"loginWithRedirect",[29,457,458],{"class":77},"({ returnTo: location.pathname })\n",[29,460,461],{"class":31,"line":87},[29,462,390],{"class":77},[29,464,465],{"class":31,"line":94},[29,466,91],{"emptyLinePlaceholder":90},[29,468,469,472,475,477,479,482,485],{"class":31,"line":116},[29,470,471],{"class":73},"const",[29,473,474],{"class":103}," user",[29,476,107],{"class":73},[29,478,321],{"class":77},[29,480,481],{"class":35},"getUser",[29,483,484],{"class":77},"() ",[29,486,487],{"class":66},"\u002F\u002F sub, email, name, tenant, tenant_slug…\n",[11,489,490,492,493,496,497,499,500,503],{},[15,491,227],{}," próbuje po kolei: świeżego tokenu z pamięci, zapisanego tokenu\nodświeżania, a dopiero na końcu cichego ",[15,494,495],{},"prompt=none"," w ukrytej ramce.\n",[15,498,348],{}," przechodzi przez przekierowanie i wraca jako ",[15,501,502],{},"appState.returnTo"," —\nczyli dokładnie to, co czyta krok 2.",[11,505,506],{},"Zamiast odpytywać stan, można na niego reagować:",[20,508,510],{"className":57,"code":509,"language":59,"meta":25,"style":25},"const unsubscribe = kleora.onAuthChange(({ user, isAuthenticated }) => {\n  render(isAuthenticated ? user : null)\n})\n",[15,511,512,546,569],{"__ignoreMap":25},[29,513,514,516,519,521,523,526,529,532,534,537,540,543],{"class":31,"line":32},[29,515,471],{"class":73},[29,517,518],{"class":103}," unsubscribe",[29,520,107],{"class":73},[29,522,321],{"class":77},[29,524,525],{"class":35},"onAuthChange",[29,527,528],{"class":77},"(({ ",[29,530,531],{"class":347},"user",[29,533,192],{"class":77},[29,535,536],{"class":347},"isAuthenticated",[29,538,539],{"class":77}," }) ",[29,541,542],{"class":73},"=>",[29,544,545],{"class":77}," {\n",[29,547,548,551,554,557,560,563,566],{"class":31,"line":70},[29,549,550],{"class":35},"  render",[29,552,553],{"class":77},"(isAuthenticated ",[29,555,556],{"class":73},"?",[29,558,559],{"class":77}," user ",[29,561,562],{"class":73},":",[29,564,565],{"class":103}," null",[29,567,568],{"class":77},")\n",[29,570,571],{"class":31,"line":87},[29,572,186],{"class":77},[11,574,575,576,579,580,584,585,587],{},"Jest jeszcze ",[15,577,578],{},"kleora.hasLikelySession()",", które mówi, czy ta przeglądarka\ndokończyła kiedyś logowanie, po którym nie było wylogowania. To ",[581,582,583],"strong",{},"podpowiedź do\nrenderowania"," — pozwala pokazać szkielet interfejsu zamiast mignięcia ekranem\nlogowania, kiedy weryfikacja jeszcze trwa. Nigdy nie jest decyzją autoryzacyjną\nani dowodem żywej sesji; dowodem jest tylko ",[15,586,227],{}," albo udane\nwywołanie API.",[48,589,591],{"id":590},"_4-wołaj-własne-api","4. Wołaj własne API",[20,593,595],{"className":57,"code":594,"language":59,"meta":25,"style":25},"const token = await kleora.getAccessToken()\n\nawait fetch('\u002Fapi\u002Finvoices', {\n  headers: { Authorization: `Bearer ${token}` },\n})\n",[15,596,597,615,619,634,651],{"__ignoreMap":25},[29,598,599,601,604,606,608,610,613],{"class":31,"line":32},[29,600,471],{"class":73},[29,602,603],{"class":103}," token",[29,605,107],{"class":73},[29,607,318],{"class":73},[29,609,321],{"class":77},[29,611,612],{"class":35},"getAccessToken",[29,614,327],{"class":77},[29,616,617],{"class":31,"line":70},[29,618,91],{"emptyLinePlaceholder":90},[29,620,621,623,626,628,631],{"class":31,"line":87},[29,622,437],{"class":73},[29,624,625],{"class":35}," fetch",[29,627,374],{"class":77},[29,629,630],{"class":39},"'\u002Fapi\u002Finvoices'",[29,632,633],{"class":77},", {\n",[29,635,636,639,642,645,648],{"class":31,"line":94},[29,637,638],{"class":77},"  headers: { Authorization: ",[29,640,641],{"class":39},"`Bearer ${",[29,643,644],{"class":77},"token",[29,646,647],{"class":39},"}`",[29,649,650],{"class":77}," },\n",[29,652,653],{"class":31,"line":116},[29,654,186],{"class":77},[11,656,657,660,661,664,665,668],{},[15,658,659],{},"getAccessToken()"," oddaje token z pamięci, dopóki do wygaśnięcia zostało więcej\nniż 30 sekund, a potem odświeża; równoległe wywołania dzielą jedno odświeżenie.\nTokeny odświeżania rotują przy każdym użyciu. Jeśli Twoje API odpowie ",[15,662,663],{},"401"," na\ntoken, w który ten klient wciąż wierzy, ponów raz z\n",[15,666,667],{},"getAccessToken({ force: true })"," — to wyrzuca token z pamięci, zamiast wysyłać\npo raz drugi ten sam odrzucony.",[11,670,671,672,677,678,681],{},"Sprawdzanie tego tokenu opisuje ",[673,674,676],"a",{"href":675},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fexpress","strona o\nExpress"," — albo ",[15,679,680],{},"verify_access_token",",\njeśli Twoje API jest w Pythonie.",[48,683,685],{"id":684},"_5-wyloguj","5. Wyloguj",[20,687,689],{"className":57,"code":688,"language":59,"meta":25,"style":25},"await kleora.logout({ returnTo: location.origin + '\u002F' })\n",[15,690,691],{"__ignoreMap":25},[29,692,693,695,697,700,703,705,708],{"class":31,"line":32},[29,694,437],{"class":73},[29,696,321],{"class":77},[29,698,699],{"class":35},"logout",[29,701,702],{"class":77},"({ returnTo: location.origin ",[29,704,164],{"class":73},[29,706,707],{"class":39}," '\u002F'",[29,709,710],{"class":77}," })\n",[11,712,713,715],{},[15,714,348],{}," jest wymagane i musi być jednym z zarejestrowanych adresów po\nwylogowaniu, porównywanym dokładnie. Stan lokalny czyszczony jest przed\njakimkolwiek pobraniem, więc wylogowanie kończy się lokalnie nawet wtedy, gdy\nissuer jest nieosiągalny.",[717,718,719],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}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 pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":25,"searchDepth":87,"depth":87,"links":721},[722,723,724,725,726],{"id":50,"depth":70,"text":51},{"id":253,"depth":70,"text":254},{"id":416,"depth":70,"text":417},{"id":590,"depth":70,"text":591},{"id":684,"depth":70,"text":685},"Jedna instancja klienta, jedna trasa powrotna i token dostępu do każdego żądania, które wysyła Twoja aplikacja.","md","guides\u002Fquickstarts\u002Fbrowser",{},10,"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fprzegladarka",false,{"title":5,"description":727},"pl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fprzegladarka","VKuAyiqBEpOr1NQGKa8WUwnQd1F_jiTyEl8XdVXJ-fY",[738,744,749,755,760,766,771,776,781,786,787,792,797,802],{"path":739,"title":740,"description":741,"order":742,"key":743},"\u002Fpl\u002Fdocs","Dokumentacja Kleora","Dodaj hostowane logowanie do swojej aplikacji, sprawdź token w swoim API i zarządzaj użytkownikami, przestrzeniami roboczymi i rolami z jednej konsoli.",0,"index",{"path":745,"title":746,"description":747,"order":731,"key":748},"\u002Fpl\u002Fdocs\u002Fpierwsze-kroki","Pierwsze kroki","Od pustego projektu do działającego logowania — i paczki, które do tego prowadzą.","getting-started\u002Findex",{"path":750,"title":751,"description":752,"order":753,"key":754},"\u002Fpl\u002Fdocs\u002Fpierwsze-kroki\u002Finstalacja","Instalacja SDK","Pięć paczek, które publikujemy, do czego służy każda z nich i jakiej konfiguracji wymaga.",20,"getting-started\u002Finstallation",{"path":756,"title":757,"description":758,"order":731,"key":759},"\u002Fpl\u002Fdocs\u002Fpierwsze-kroki\u002Fszybki-start","Szybki start","Załóż aplikację, podłącz SDK przeglądarkowe i zaloguj się po raz pierwszy — w całości w sandboksie.","getting-started\u002Fquickstart",{"path":761,"title":762,"description":763,"order":764,"key":765},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Farchitektura","Jak zbudowana jest Kleora","Kształt usługi hostowanej — jeden adres na środowisko, dwie powierzchnie API i miejsce, w którym leży stan. Oraz dlaczego hostowania u siebie dzisiaj nie wspieramy.",30,"guides\u002Farchitecture",{"path":675,"title":767,"description":768,"order":769,"key":770},"Express","Sprawdzanie tokenu dostępu we własnym API — middleware, uprawnienia i trzy odpowiedzi, jakie może mieć odmowa.",40,"guides\u002Fquickstarts\u002Fexpress",{"path":772,"title":773,"description":774,"order":731,"key":775},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki","Szybki start dla frameworków","Po jednej stronie na stos — Next.js, Nuxt, Express i zwykła przeglądarka — i dwa adresy powrotne, które łączą je wszystkie.","guides\u002Fquickstarts\u002Findex",{"path":777,"title":778,"description":779,"order":753,"key":780},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fnextjs","Next.js","SDK przeglądarkowe w aplikacji na App Router — leniwy klient, trasa powrotna i sprawdzanie tokenu w route handlerze.","guides\u002Fquickstarts\u002Fnextjs",{"path":782,"title":783,"description":784,"order":764,"key":785},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fframeworki\u002Fnuxt","Nuxt","Moduł dla Nuxt — dwie linijki konfiguracji, strona powrotu, której nie piszesz, i middleware chroniący stronę z jej własnych metadanych.","guides\u002Fquickstarts\u002Fnuxt",{"path":732,"title":5,"description":727,"order":731,"key":729},{"path":788,"title":789,"description":790,"order":753,"key":791},"\u002Fpl\u002Fdocs\u002Fprzewodniki","Przewodniki","Jak podłączyć usługę Kleora do swojego stosu, jakich słów używa produkt i jak zbudowana jest usługa hostowana.","guides\u002Findex",{"path":793,"title":794,"description":795,"order":753,"key":796},"\u002Fpl\u002Fdocs\u002Fprzewodniki\u002Fpojecia","Przestrzenie robocze, role i środowiska","Pięć słów, z których zbudowany jest produkt, to, które z nich niosą Twoje tokeny, i jak zmiana roli dociera do działającej aplikacji.","guides\u002Fconcepts",{"path":798,"title":799,"description":800,"order":764,"key":801},"\u002Fpl\u002Fdocs\u002Freferencje","Referencje","Materiały referencyjne dla API HTTP i paczek SDK.","reference\u002Findex",{"path":803,"title":804,"description":805,"order":731,"key":806},"\u002Fpl\u002Fdocs\u002Freferencje\u002Fpodswietlanie","Podświetlanie kodu","Po jednym bloku na każdą wstępnie wczytaną gramatykę Shiki — próbka, nie strona referencyjna.","reference\u002Fcode-highlighting",1790698198071]