Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. AuthContext: kim kirgan
- 2.1 Context + guard hook
- 2.2 Chiqish — unmount va cleanup
- 3. Sahifa yangilansa: localStorage va useSyncExternalStore
- 3.1 Holat — tashqi manbada
- 3.2 storage hodisasi yetarlimi?
- 4. Barcha tablarda chiqish: BroadcastChannel
- 4.1 Kanal va "bo'sh" xabar
- 4.2 Ikki "tab" bitta sahifada
- 5. Haqiqiy auth'da frontend nima qiladi
- 6. Hujumchi nigohi
- 7. Vazifalar qadami: soxta kirish (holat 42 = v8)
- 7.1 Fayllar
- 7.2 AuthProvider — kanondan
- 7.3 never eslatdi
- 7.4 Haqiqiy tekshiruv
- 7.5 Commit va v8 yakuni
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qayerda tekshiriladi?
- 2-mashq (o'rta): rolga qarab UI
- 3-mashq (qiyin): xotiradagi matnni xavfsiz o'qish
- 4-mashq: Vazifalar qadami
- 10. Real ishda
- Xulosa
- Manbalar
React'da auth holati: AuthContext, himoyalangan UI va barcha tablarda logout (soxta kirish)
Qisqacha: Frontend'da "kim kirgan" — butun ilovaga kerak bo'lgan holat, shuning uchun u Context'da turadi:
AuthProvider+useAuth(). Himoyalangan qism (<Himoyalangan>) kirmagan foydalanuvchiga kirish formasini, kirganga — ilovani ko'rsatadi; chiqilganda ilova butunlay o'chadi (unmount) va effektlar tozalanadi. Bir tabda «Chiqish» — hamma tablarda chiqish:BroadcastChannelbilan "auth o'zgardi" xabari. Muhim: bu darsdagi kirish — soxta, o'rganish uchun: parol yo'q, token yo'q, server yo'q. Frontend'dagi himoya faqat ko'rinishni yashiradi. Haqiqiy autentifikatsiya (parol, sessiya, httpOnly cookie) serverda bo'ladi — 28-qismda.
Bu darsda
AuthContext,AuthProviderva guard hookuseAuth()yozasiz.- Himoyalangan UI quriladi: kirmagan — forma, kirgan — ilova; chiqishda — unmount va cleanup.
- Auth holatini
localStoragevauseSyncExternalStorebilan saqlaysiz. - Bir tabdagi «Chiqish» ni hamma tablarga yetkazasiz — xabarda shaxsiy ma'lumotsiz.
vazifalarv8 ni yakunlaysiz: soxta kirish,SinxronXabarga yangi tur vanevereslatmasi, yakuniy o'lchovlar.
Oldin bilishingiz kerak: Context II, useSyncExternalStore, Frontend xavfsizligi React'da, Autentifikatsiyali so'rovlar va token yangilash.
1. Nega bu kerak?
«Bahor» ning ichki paneli bor: bugungi buyurtmalar, oshxona navbati. Uni faqat xodimlar ko'rishi kerak. Sardor shunday boshladi:
function App() {
const [user, setUser] = useState(null);
return user === null
? <LoginForm onLogin={setUser} />
: <Header user={user} onLogout={() => setUser(null)} />;
// … Orders, Kitchen, Settings — hammasiga user kerak
}Bir hafta o'tib muammolar ko'paydi:
userniOrdersichidagiOrderRowgacha props bilan uzatish kerak — props drilling.- Sahifa yangilansa —
useryo'qoladi, qayta kirish kerak. - Jasur aka ikkita tab ochgan edi. Birida «Chiqish» bosdi — ikkinchisida panel ochiq qoldi. Kompyuterdan boshqa odam foydalansa — buyurtmalar ko'rinib turibdi.
Bu darsda uchala muammoni hal qilamiz. Lekin avval eng muhim ogohlantirish.
Diqqat: Bu darsdagi kirish — SOXTA. Unda parol yo'q, token yo'q, server tekshiruvi yo'q. Foydalanuvchi faqat ismini yozadi va u brauzerda saqlanadi. Biz faqat UI tomonini o'rganamiz: holat qayerda turadi, himoyalangan qism qanday quriladi, tablar qanday sinxronlanadi. Haqiqiy autentifikatsiya — parol xeshi, sessiya yoki JWT,
httpOnlycookie — 28-qismda, serverda (Session-based auth, Access va refresh token strategiyasi). Bu darsdagi kodni haqiqiy loyihada "himoya" sifatida ishlatmang.
Hayotiy o'xshatish: oshxonaga kiraverishdagi "Faqat xodimlar uchun" yozuvi. U mijozni chalg'itmaydi, yo'lni ko'rsatadi — lekin eshikni qulflamaydi. Qulf — server. Bugun biz yozuvni chiroyli va to'g'ri joyga osamiz. Qulfni 28-qismda o'rnatamiz.
2. AuthContext: kim kirgan
2.1 Context + guard hook
React + TS II dagi naqsh — createContext(null) va null ni tekshiradigan hook. Auth uchun qiymat — foydalanuvchi va ikki amal:
import { createContext, use, useState } from "react";
const AuthContext = createContext(null);
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const value = {
user,
login: (name) => setUser({ name: name.trim() }),
logout: () => setUser(null),
};
return <AuthContext value={value}>{children}</AuthContext>;
}
function useAuth() {
const auth = use(AuthContext);
if (auth === null) {
throw new Error("useAuth() — <AuthProvider> ichida chaqirilsin");
}
return auth;
}
function LoginForm() {
const { login } = useAuth();
return (
<form
onSubmit={(e) => {
e.preventDefault();
login(new FormData(e.currentTarget).get("ism"));
}}
>
<label htmlFor="ism">Ismingiz</label>{" "}
<input id="ism" name="ism" />
<button>Kirish</button>
</form>
);
}
function Protected({ children }) {
const { user } = useAuth();
return user === null ? <LoginForm /> : children;
}
function Header() {
const { user, logout } = useAuth();
return (
<p>
{user.name} — kirgansiz{" "}
<button onClick={logout}>Chiqish</button>
</p>
);
}
export default function App() {
return (
<AuthProvider>
<Protected>
<Header />
<p>Bugungi buyurtmalar: 12</p>
</Protected>
</AuthProvider>
);
}Ekranda:
Malika — kirgansiz Chiqish
Bugungi buyurtmalar: 12Tuzilmani o'qing:
AuthProvider— holatning yagona joyi.uservalogin/logoutContext orqali hammaga.useAuth()— guard hook: Provider unutilsa — aniq xato.Protected— himoyalangan UI. Kirmagan bo'lsa — forma, kirgan bo'lsa —children.Headerva buyurtmalaruserni props'siz oladi.Headerichidauser.name— tekshiruvsiz. Mumkin, chunkiHeaderfaqatProtectedichida chiziladi, u yerdauserdoim bor.
Kompozitsiya diagrammada:
flowchart TD
P["AuthProvider: user, login, logout"] --> H{"Himoyalangan: user bormi?"}
H -- "yo'q" --> K["KirishFormasi"]
H -- "ha" --> I["Ilova: Header, buyurtmalar"]
I -- "«Chiqish»" --> L["logout(): user = null"]
L --> H
K -- "«Kirish»" --> N["login(ism)"]
N --> HDiagrammadagi halqa — butun auth UI'ning mantig'i: har o'zgarish Himoyalangan dagi bitta shartga qaytadi.
2.2 Chiqish — unmount va cleanup
Protected chiqishda children ni o'chiradi. Bu muhim: ilova ichidagi effektlar (server so'rovlari, obunalar, taymerlar) cleanup bilan to'xtaydi. Ko'ramiz:
import { useEffect, useState } from "react";
function Orders() {
useEffect(() => {
console.log("buyurtmalar: obuna ulandi");
return () => console.log("buyurtmalar: obuna uzildi");
}, []);
return <p>Bugungi buyurtmalar: 12</p>;
}
export default function App() {
const [user, setUser] = useState(null);
if (user === null) {
return <button onClick={() => setUser("Malika")}>Kirish</button>;
}
return (
<div>
<button onClick={() => setUser(null)}>Chiqish</button>
<Orders />
</div>
);
}Konsolda:
buyurtmalar: obuna ulandi
buyurtmalar: obuna uzildiEkranda:
KirishChiqishda Orders unmount bo'ldi — obuna uzildi. Agar ilovani display: none bilan yashirganimizda edi, obuna ishlayverar, buyurtmalar kelaverardi — chiqqan foydalanuvchi uchun. Shuning uchun himoyalangan qism shart bilan chiziladi, yashirilmaydi. Qayta kirishda ilova noldan o'rnatiladi — eski holat qolmaydi.
Tekshirib ko'ring:
Protectedo'rniga<div hidden={user === null}><App /></div>yozilsa, xavfsizlik nuqtai nazaridan nima o'zgaradi?
Javob
Ilova DOM'da qoladi: DevTools'da hidden ni o'chirsa — buyurtmalar ko'rinadi. Effektlar ishlayveradi — server so'rovlari, boshqa tab xabarlari. Shartli chizishda esa kirmagan foydalanuvchi uchun ilova umuman yo'q. Lekin unutmang: ikkalasi ham faqat ko'rinish. Ma'lumot (bundle, localStorage) baribir brauzerda — haqiqiy himoya serverda, har so'rovda.
3. Sahifa yangilansa: localStorage va useSyncExternalStore
3.1 Holat — tashqi manbada
Yangilanganda kirish yo'qolmasligi uchun foydalanuvchi brauzer xotirasida saqlanadi. Endi "haqiqat manbai" — useState emas, localStorage. React uchun bu tashqi manba — useSyncExternalStore aynan shunga mo'ljallangan:
const KEY = "bahor:foydalanuvchi";
function subscribe(callback) {
window.addEventListener("storage", callback); // boshqa tablar
return () => window.removeEventListener("storage", callback);
}
function getSnapshot() {
return localStorage.getItem(KEY); // satr yoki null — barqaror
}
function AuthProvider({ children }) {
const raw = useSyncExternalStore(
subscribe,
getSnapshot,
() => null,
);
const user = useMemo(() => parseUser(raw), [raw]);
// login: localStorage.setItem(KEY, JSON.stringify(...))
// logout: localStorage.removeItem(KEY)
// …
}Uch nozik joy:
- Snapshot — xom satr, obyekt emas.
getSnapshothar chaqirilgandaJSON.parseqilib yangi obyekt qaytarsa, React "o'zgardi" deb o'ylab cheksiz qayta chizadi (useSyncExternalStore dagi tuzoq). Satr esa qiymat bo'yicha solishtiriladi. Obyekt —useMemobilan, faqat satr o'zgarganda. parseUser— tashqi ma'lumot tekshiruvi.localStoragedagi matnni foydalanuvchi DevTools'da qo'lda o'zgartirishi, eski versiya yozib qoldirishi mumkin.unknownsifatida olib, shaklini tekshiring. Buzuq — "kirilmagan" (API ma'lumotlarini tiplash).- Saqlanadigan narsa — minimal. Bizda faqat ism. Parol hech qachon, hech qayerda saqlanmaydi. Haqiqiy loyihada
localStoragega token qo'yish ham yomon fikr (Frontend xavfsizligi React'da).
3.2 storage hodisasi yetarlimi?
Brauzer localStorage o'zgarganda boshqa tablarga storage hodisasini yuboradi. Demak, tablararo sinxronlash tayyor? Qisman. Ikki kamchilik:
- Hodisa faqat boshqa tablarga — shu tabdagi
login/logoutuchun o'zimiz obunachilarni chaqirishimiz kerak. - Hodisa faqat
localStorageo'zgarganda. Xotira yopiq bo'lsa (maxfiy rejim, cheklangan brauzer) — hech narsa kelmaydi.
Shuning uchun vazifalar aniqroq yo'lni tanladi — BroadcastChannel.
4. Barcha tablarda chiqish: BroadcastChannel
4.1 Kanal va "bo'sh" xabar
BroadcastChannel — bir xil origin'dagi tablar, oynalar va worker'lar o'rtasida xabar kanali. 11-qismda vazifalar ro'yxatini tablararo shu bilan sinxronlagan edik. Endi o'sha kanalga yangi xabar turi qo'shiladi: { tur: "auth" }.
Diqqat qiling: xabarda ma'lumot yo'q — ism ham, "kim chiqdi" ham emas. Faqat "auth o'zgardi". Har tab buni olgach, holatni o'z xotirasidan qayta o'qiydi. Nega?
- Maxfiylik. Kanalga shaxsiy ma'lumot chiqmaydi. Bitta haqiqat manbai — xotira.
- Ishonch. Xabar matnini tekshirish shart emas — u hech narsani "aytmaydi", faqat "qayta o'qi" deydi.
- Soddalik. Kirish ham, chiqish ham — bitta xabar turi.
4.2 Ikki "tab" bitta sahifada
Haqiqiy ikki tab — ikki alohida sahifa. Tekshiruvchimizning brauzer modelida BroadcastChannel yo'q, shuning uchun mexanizmni bitta sahifada ko'rsatamiz. Kanal o'rnida — EventTarget bilan yasalgan kichik model (API'si BroadcastChannel ga o'xshash), "xotira" esa — umumiy obyekt (localStorage o'rnida):
import { useSyncExternalStore } from "react";
// localStorage modeli — ikkala "tab" uchun umumiy
const storage = { user: "Malika" };
// BroadcastChannel modeli: xabar yuboruvchining o'ziga kelmaydi
const bus = new EventTarget();
function openChannel(tabId) {
const own = new Set(); // shu "tab"dagi obunachilar
return {
subscribe(notify) {
own.add(notify);
const onMessage = (e) => {
if (e.detail.from === tabId) return;
const data = JSON.stringify(e.detail.data);
console.log(`${tabId}-tab: xabar`, data);
notify(); // qayta o'qi
};
bus.addEventListener("xabar", onMessage);
return () => {
own.delete(notify);
bus.removeEventListener("xabar", onMessage);
};
},
changed() {
const detail = { from: tabId, data: { tur: "auth" } };
bus.dispatchEvent(new CustomEvent("xabar", { detail }));
own.forEach((notify) => notify()); // shu tab — qo'lda
},
};
}
const channels = { 1: openChannel(1), 2: openChannel(2) };
function Tab({ id }) {
const channel = channels[id];
const user = useSyncExternalStore(
channel.subscribe,
() => storage.user,
);
function logout() {
storage.user = null; // xotiradan o'chirish
channel.changed(); // boshqa tablar + shu tab
}
return (
<p>
{id}-tab: {user ?? "kirish oynasi"}{" "}
{user !== null && (
<button onClick={logout}>{`${id}-tab: Chiqish`}</button>
)}
</p>
);
}
export default function App() {
return (
<div>
<Tab id={1} />
<Tab id={2} />
</div>
);
}Konsolda:
2-tab: xabar {"tur":"auth"}Ekranda:
1-tab: kirish oynasi
2-tab: kirish oynasiQadamma-qadam:
| Qadam | Ekranda | Konsolda |
|---|---|---|
| boshida | ikkala tab: «Malika» | — |
1-tab: «Chiqish» → storage.user = null, changed() |
hali o'zgarmagan | — |
kanal xabari 2-tabga yetdi → notify() |
hali o'zgarmagan (render navbatda) | 2-tab: xabar {"tur":"auth"} |
1-tab obunachilari qo'lda (own.forEach) |
hali o'zgarmagan (render navbatda) | — |
| ishlovchi tugadi → React ikkala tabni qayta chizadi | ikkala tab: kirish oynasi | — |
Ketma-ketlik: 1-tab xotirani o'chirdi va changed() ni chaqirdi. Xabar faqat 2-tabga keldi — yuboruvchining o'ziga emas, xuddi haqiqiy BroadcastChannel dagidek. 2-tab xotirani qayta o'qidi: null — kirish oynasi. 1-tab esa xabar olmadi, shuning uchun uning obunachilari qo'lda chaqirildi (own.forEach). Bu qatorni olib tashlasangiz, 1-tab "Malika" bo'lib qoladi — kanal o'z tabiga yubormaydi. Kanondagi xabarBer() aynan shu ish uchun.
Haqiqiy kodda model o'rniga bitta qator: const kanal = new BroadcastChannel("vazifalar"), kanal.postMessage(…), kanal.onmessage = ….
Tekshirib ko'ring: Nega xabar
{ tur: "auth", ism: "Malika" }emas, faqat{ tur: "auth" }?
Javob
Kanal — "ochiq radio": shu origin'dagi har skript (masalan, kelajakda qo'shilgan begona widget) uni tinglay oladi. Unga shaxsiy ma'lumot chiqarmaslik — "minimal ma'lumot" tamoyili. Bundan tashqari, xabar va xotira bir-biriga zid bo'lishi mumkin (ikki tab bir vaqtda kirsa). Xabarda ma'lumot yo'q ekan, ziddiyat ham yo'q: har tab yagona haqiqat manbaini — xotirani o'qiydi.
5. Haqiqiy auth'da frontend nima qiladi
Bugungi soxta kirish UI qismini o'rgatdi. Haqiqiy tizimda (28-qism) frontend'ning vazifasi kengayadi. Oldindan xarita:
| Qism | Soxta (bugun) | Haqiqiy (28-qism) |
|---|---|---|
| kirish formasi | ism | email/telefon + parol, type="password", autoComplete="current-password" |
| kim kirgan | localStorage dagi ism |
serverdan: GET /me (cookie bilan) |
| himoya | faqat UI | server har so'rovda tekshiradi |
| saqlash | localStorage |
sessiya yoki refresh token — httpOnly cookie |
| chiqish | xotirani o'chirish | POST /logout — server sessiyani bekor qiladi |
| tablararo | BroadcastChannel |
o'sha — BroadcastChannel |
Ikki atama — oldindan tanishtirish (11-qismda ko'rgansiz, Autentifikatsiyali so'rovlar va token yangilash):
- Kirish tokeni (access token) — qisqa umrli (masalan, 15 daqiqa) "ruxsatnoma", har API so'rovi bilan yuboriladi.
- Yangilash tokeni (refresh token) — uzoq umrli, faqat yangi kirish tokeni olish uchun.
httpOnlycookie'da yashaydi — JS uni ko'rmaydi.
Kirish tokeni muddati tugaganda so'rov 401 qaytaradi. Frontend uni ushlab (axios interceptor yoki fetch o'rami), yangilash so'rovini yuboradi va asl so'rovni takrorlaydi. Bir vaqtda 5 ta so'rov 401 olsa — 5 ta yangilash emas, bitta: qolganlari navbatda kutadi. Bu "refresh navbati" — token yangilash darsida yozgan edingiz. Bugungi AuthProvider haqiqiy tizimda shu mexanizm ustida turadi. useAuth() va <Himoyalangan> esa deyarli o'zgarmaydi — bugungi UI qismi o'shanda ham to'g'ri.
Marshrutli ilovada himoya sahifa darajasida bo'ladi: /panel ga kirmagan foydalanuvchi /kirish ga yo'naltiriladi (Himoyalangan marshrutlar).
6. Hujumchi nigohi
Soxta kirishni "buzish" — bir daqiqalik ish, va bu ataylab shunday:
| Hujumchi qiladi | Natija | Nega bu bugun normal |
|---|---|---|
DevTools → Application → localStorage ga {"ism":"Admin"} |
"kirdi" | soxta kirish — himoya emas, faqat UI |
| bundle'ni o'qiydi | ilovaning hamma kodi ko'rinadi | frontend kodi doim ochiq |
BroadcastChannel ga { tur: "auth" } yuboradi |
tablar qayta o'qiydi — boshqa hech narsa | xabarda ma'lumot va buyruq yo'q |
localStorage ga buzuq JSON yozadi |
"kirilmagan", ilova yiqilmaydi | foydalanuvchiniOqi tekshiradi |
| parol yoki token qidiradi | yo'q | saqlanmaydi |
Birinchi qator — butun darsning asosiy saboqi. Frontend'dagi har qanday "himoya" — faqat qulaylik. Server ma'lumotni faqat haqiqiy sessiyasi bor foydalanuvchiga bersa, localStorage ni o'zgartirish hech narsa bermaydi. Forma o'rniga bo'sh ilova ochiladi, har so'rov 401 qaytaradi. 28-qismda aynan shu qulfni o'rnatamiz. Loyihadagi soxta kirish ham buni o'quvchiga ochiq aytadi — forma ostida: "Mashq kirishi: parol yo'q, ism faqat shu brauzerda saqlanadi".
7. Vazifalar qadami: soxta kirish (holat 42 = v8)
7.1 Fayllar
feat/soxta-auth branch'i — 18-qismning oxirgi qadami. Yangi xususiyat papkasi (Katta frontend loyiha arxitekturasi) — src/xususiyatlar/auth/:
| Fayl | Nima |
|---|---|
foydalanuvchi.ts |
sof: ism tekshiruvi (2–40 harf), xotiradagi matnni o'qish |
AuthKonteksti.tsx |
AuthProvider (useSyncExternalStore + kanal), useAuth |
Himoyalangan.tsx |
kirmagan — forma, kirgan — children |
KirishFormasi.tsx |
<form action> + useActionState, faqat ism |
FoydalanuvchiMenyusi.tsx |
ism va «Chiqish» — o'sha Menyu (compound) |
index.ts |
ochiq API: uchta eksport |
main.tsx dagi kompozitsiya (kanon sxemasi, izohlar telefon uchun tepaga ko'chirilgan):
<TemaProvider>
{/* kim kirgan — localStorage (tashqi manba) + tablar */}
<AuthProvider>
{/* kirmagan — <KirishFormasi />, kirgan — ilova */}
<Himoyalangan>
{/* App yuqori={<FoydalanuvchiMenyusi />} */}
<Ilova />
</Himoyalangan>
</AuthProvider>
</TemaProvider>App auth'ni bilmaydi. Unga faqat yuqori slot'i qo'shildi (Compound komponentlar va slot) — main.tsx u yerga <FoydalanuvchiMenyusi /> ni qo'yadi. Testlarda slot berilmaydi va App Provider'siz ham ishlaydi. Ilova — kichik o'ram: har kirishda ro'yxat va qoralamani xotiradan qayta o'qiydi (lazy init). Avval main.tsx ochilishdagi qiymatni uzatardi — chiqib-kirilsa, eskirgan ro'yxat serverga ketishi mumkin edi.
7.2 AuthProvider — kanondan
Asosiy qism (kanondan aynan, import va izohlar qisqartirilgan):
// Shu tabdagi obunachilar (uSES tinglovchilari)
const obunachilar = new Set<() => void>();
function xabarBer() {
for (const tinglovchi of obunachilar) {
tinglovchi();
}
}
function obuna(tinglovchi: () => void): () => void {
obunachilar.add(tinglovchi);
const tabdan = tingla((xabar) => {
if (xabar.tur === AUTH) {
tinglovchi();
}
});
return () => {
obunachilar.delete(tinglovchi);
tabdan();
};
}
function ozgardi() {
tarqat({ tur: AUTH }); // boshqa tablar
xabarBer(); // shu tab (BroadcastChannel o'z tabiga yubormaydi)
}
export function AuthProvider({ children }: { children: ReactNode }) {
// Snapshot — xom matn (satr — barqaror qiymat; har chaqiriqda yangi
// obyekt bersak, React cheksiz qayta chizardi), o'qish — useMemo'da
const matn = useSyncExternalStore(
obuna,
foydalanuvchiMatni,
() => null,
);
const foydalanuvchi = useMemo(() => foydalanuvchiniOqi(matn), [matn]);
const [hozirKirdi, setHozirKirdi] = useState(false);
// … qiymat: { foydalanuvchi, hozirKirdi, kirish(ism), chiqish() }
}Darsning hamma bo'limi shu yerda: obuna ikki manbadan (shu tab — xabarBer, boshqa tablar — tingla), snapshot — xom satr, o'qish — useMemo, xabar — ma'lumotsiz. hozirKirdi — fokus uchun: faqat shu tabda hozir kirilganda fokus foydalanuvchi tugmasiga ko'chadi. Sahifa sessiya bilan ochilsa — fokus o'g'irlanmaydi (Fokus boshqaruvi).
KirishFormasi dan bitta parcha — ogohlantirish matni foydalanuvchiga ham ko'rinadi:
<p id={izohId} className="izoh">
Mashq kirishi: parol yo'q, ism faqat shu brauzerda saqlanadi.
Haqiqiy kirish (parol, sessiya) — 28-qismda, serverda.
</p>7.3 never eslatdi
React + TS II da App.tsx dagi boshqa tab xabarlari switch ida never tekshiruvi borligini ko'rgan edik. Endi u haqiqatan ishladi. turlar.ts da SinxronXabar union'iga yangi variant qo'shildi:
export type SinxronXabar =
| {
readonly tur: "ozgardi";
readonly paket: string; // eksportMatni() natijasi
}
// 18/#42: kirish/chiqish — ma'lumotsiz ("o'zgardi" —
// qolganini har tab o'z xotirasidan o'qiydi)
| { readonly tur: "auth" };App.tsx ga case qo'shishdan oldin npm run tip (haqiqiy, natijalar/42-never.txt):
src/App.tsx(306,15): error TS2322: Type '"auth"' is not assignable to type 'never'."Sen auth ni ishlamading" — tsc qayerga qarash kerakligini aniq ko'rsatdi. Ikkinchi topilma qiziqroq. case AUTH: qo'shilgach ham xato qoldi, chunki 17-qismdagi qator const qolmadi: never = xabar.tur; edi:
src/App.tsx(306,15): error TS2322: Type 'any' is not assignable to type 'never'.
src/App.tsx(306,38): error TS2339: Property 'tur' does not exist on type 'never'.Hamma variant ishlangach, default da xabar ning o'zi never — undan .tur ni o'qib bo'lmaydi. Kanon tuzatishi — const qolmadi: never = xabar; (0 xato). Bitta variantli union'da xabar.tur ham ishlardi, ikkinchisi qo'shilganda ochildi.
7.4 Haqiqiy tekshiruv
e2e-18 (toza brauzer konteksti, ikki tab, build + CSP), natija-18-42.txt:
✅ auth: kirmagan — kirish formasi, ilova yo'q, fokus ism maydonida: [true,true,"kirish-ism"]
✅ auth: bo'sh ism — xato, aria-invalid: ["Ismingizni yozing (kamida 2 harf).","true"]
✅ auth: kirdi — ilova, fokus «Malika» tugmasida, parol hech qayerda yo'q: ["Malika","{\"ism\":\"Malika\"}",0]
✅ auth: ikkinchi tab — sessiya bilan (kirish oynasisiz): "Malika"
✅ auth: menyu — «Chiqish» fokusda: ["menuitem","Chiqish"]
✅ auth: chiqdi — kirish formasi, fokus ism maydonida, yozuv o'chdi: ["kirish-ism",null,true]
✅ auth: 2-tabda ham chiqildi (BroadcastChannel) — ilova o'chirildi: true
(2-tab 16 ms ichida kirish oynasiga o'tdi)
✅ auth: 2-tabda kirildi — 1-tabda ham (Bobur): "Bobur"
✅ auth: CSP buzilishi yo'q (ikkala tab): [[],[]]Uchinchi qatordagi 0 — sahifada input[type=password] soni. O'tgan darsdagi talab ("parol va token saqlanmaydi") shu bilan bajarildi: xotirada faqat {"ism":"Malika"}. 2-tab 16 ms ichida kirish oynasiga o'tdi. Oxirgi qatorlar teskari yo'nalishni ham ko'rsatadi: 2-tabda Bobur kirdi — 1-tab ham uni ko'rdi.
Testlar: node:test ℹ pass 173 (+3: ismXatosi, foydalanuvchiniOqi — to'g'ri, buzuq), Vitest Tests 49 passed (49) (+4: forma, xato ism, «Chiqish», boshqa tabdan chiqish). Brauzer — 159/159, e2e-18 — 72/72. Dev rejim — kirish, statistika paneli, CSP 0.
7.5 Commit va v8 yakuni
git add -A
git commitCommit (kanon): feat: soxta kirish — AuthContext, himoyalangan UI, chiqish hamma tablarda. Diff (holat 40 → 42, hujjatlar bilan): 19 fayl, +616 −56. Unda README, AGENTS va TEXNIK-QARZ ham yangilandi: "soxta auth chegaralari — 28-qismda haqiqiy auth bilan almashtiriladi". Service Worker keshi versiyasi v7- → v8-.
vazifalar v8 — 18-qism tugadi. Yakuniy o'lchovlar (haqiqiy, QADAMLAR):
| O'lchov | v7 | v8 |
|---|---|---|
| boshlang'ich yuklash (gzip) | 79.8 KB | 84.6 KB |
| testlar (node / Vitest) | — | 173 / 49 |
| Lighthouse, birinchi tashrif (kirish oynasi) | — | 99 / 100 / 100 / 100 |
| axe buzilishlari | — | 0 |
+4.8 KB evaziga: concurrent qidiruv, Actions va optimistik yozuv, error boundary, lazy statistika paneli, portal, Activity, virtual ro'yxat, Web Worker, Menyu, a11y va soxta kirish. Har biri o'lchab qo'shildi, kirmaganlari ham sababi bilan yozildi.
Deploy — 17-qismdagidek, Vercel'ga o'zingiz chiqarasiz: vercel.json dagi CSP va sarlavhalar bilan ham 159/159 tekshirilgan.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
user props bilan 5 qavat pastga |
Context yo'q | AuthProvider + useAuth |
| yangilansa — qayta kirish | holat faqat useState da |
localStorage + useSyncExternalStore |
| cheksiz qayta chizish | getSnapshot har safar yangi obyekt |
snapshot — xom satr, obyekt — useMemo |
| bir tabda chiqildi, ikkinchisida ochiq | tablararo xabar yo'q | BroadcastChannel |
| shu tabda chiqildi, UI yangilanmadi | BroadcastChannel o'z tabiga yubormaydi |
shu tab obunachilarini qo'lda chaqiring |
| chiqqandan keyin so'rovlar davom etyapti | ilova yashirilgan, unmount emas | shartli chizish |
localStorage dagi buzuq JSON — oq ekran |
JSON.parse tekshiruvsiz |
try/catch + shakl tekshiruvi → null |
| "frontend'da himoyaladim" | himoya faqat UI | server har so'rovda tekshiradi (28-qism) |
9. Mashqlar
Mashqlar kurs/mashqlar/18/42-auth/ da. Hammasi — soxta kirish, parolsiz.
1-mashq (oson): qayerda tekshiriladi?
frontend yoki server:
- "Kirmagan foydalanuvchiga kirish formasini ko'rsatish" —
- "Buyurtmalar ro'yxatini faqat xodimga berish" —
- "Chiqqanda boshqa tablarni ham yopish" —
- "Parolni tekshirish" —
Yechim
- frontend — bu UI qulayligi, ma'lumot himoyasi emas.
- server — ma'lumot faqat haqiqiy sessiya bilan beriladi. Frontend'dagi tekshiruvni aylanib o'tish bir daqiqalik ish.
- frontend — UI holati (
BroadcastChannel). Server tomonda esaPOST /logoutsessiyani bekor qiladi — ikkalasi kerak. - server — parol hech qachon frontend'da tekshirilmaydi va saqlanmaydi.
2-mashq (o'rta): rolga qarab UI
AuthProvider foydalanuvchiga role ham bersin: "oshpaz" yoki "kassir". Kirish — ikki tugma: «Oshpaz sifatida» va «Kassir sifatida» (ism — Sardor). RoleGate({ role, children }) — foydalanuvchi roli mos kelsa children, aks holda <p>Ruxsat yo'q</p>. Ilovada: «Oshxona navbati» — faqat oshpazga, «Kassa» — faqat kassirga. Ishora: «Context + guard hook» bo'limidagi Protected; rol — user.role.
Yechim
import { createContext, use, useState } from "react";
const AuthContext = createContext(null);
function useAuth() {
const auth = use(AuthContext);
if (auth === null) {
throw new Error("useAuth() — <AuthProvider> ichida");
}
return auth;
}
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const value = { user, login: setUser, logout: () => setUser(null) };
return <AuthContext value={value}>{children}</AuthContext>;
}
function Protected({ children }) {
const { user, login } = useAuth();
if (user !== null) return children;
const loginAs = (role) => login({ name: "Sardor", role });
return (
<p>
<button onClick={() => loginAs("oshpaz")}>
Oshpaz sifatida
</button>{" "}
<button onClick={() => loginAs("kassir")}>
Kassir sifatida
</button>
</p>
);
}
function RoleGate({ role, children }) {
const { user } = useAuth();
return user.role === role ? children : <p>Ruxsat yo'q</p>;
}
export default function App() {
return (
<AuthProvider>
<Protected>
<h2>Oshxona navbati</h2>
<RoleGate role="oshpaz">
<p>Osh × 3, Manti × 2</p>
</RoleGate>
<h2>Kassa</h2>
<RoleGate role="kassir">
<p>Bugun: 1 250 000 so'm</p>
</RoleGate>
</Protected>
</AuthProvider>
);
}Ekranda:
Oshxona navbati
Ruxsat yo'q
Kassa
Bugun: 1 250 000 so'mRoleGate — himoyalangan UI'ning kichik ukasi. Esda tuting: bu ham faqat ko'rinish. Haqiqiy ilovada kassa ma'lumotini server faqat kassir roli bor sessiyaga beradi (RBAC va resurs egaligi). Frontend'dagi RoleGate esa oshpazga keraksiz tugmalarni ko'rsatmaydi — qulaylik.
3-mashq (qiyin): xotiradagi matnni xavfsiz o'qish
parseUser(raw) — localStorage dagi matn (string | null) dan foydalanuvchini o'qiydi. Qoidalar: null → null; buzuq JSON → null; obyekt emas yoki name satr emas → null; name bo'shliqsiz 2–40 harf bo'lmasa → null; aks holda { name } (bo'shliqlar kesilgan). Beshta holatni konsolda sinang. Ishora: try/catch va typeof (Narrowing); vazifalar dagi foydalanuvchiniOqi — xuddi shu vazifa.
Yechim
function parseUser(raw) {
if (raw === null) return null;
let data;
try {
data = JSON.parse(raw);
} catch {
return null;
}
if (typeof data !== "object" || data === null) return null;
if (typeof data.name !== "string") return null;
const name = data.name.trim();
if (name.length < 2 || name.length > 40) return null;
return { name };
}
console.log(parseUser(null)); // null
console.log(parseUser("{buzuq")); // null
console.log(parseUser('{"name": 42}')); // null
console.log(parseUser('{"name": " A "}')); // null
console.log(parseUser('{"name": " Malika "}')); // { name: 'Malika' }Har tekshiruv — "kirilmagan" tomonga: shubha bo'lsa, null. Ilova yiqilmaydi, foydalanuvchi shunchaki kirish oynasini ko'radi. typeof data !== "object" || data === null — JSON.parse("null") ham, "42" ham to'g'ri JSON, lekin obyekt emas. Bu funksiya React'siz — node:test bilan to'g'ridan-to'g'ri sinaladi (vazifalar da foydalanuvchi.test.js).
4-mashq: Vazifalar qadami
feat/soxta-auth branch'ida src/xususiyatlar/auth/ papkasini «Vazifalar qadami» bo'limidagi jadval bo'yicha yarating. SinxronXabar ga { tur: "auth" } qo'shing va case yozishdan oldin npm run tip ni ishga tushiring — never xatosini ko'ring. Keyin ikki tabda sinang: biri «Chiqish» — ikkinchisi kirish oynasiga. Commit qiling va Vercel'ga chiqaring.
Yechim
Tekshiruv ro'yxati:
npm run tip(case'siz) —TS2322: Type '"auth"' is not assignable to type 'never'.case AUTH: return;vaconst qolmadi: never = xabar;— 0 xato.npm run check— node 173, Vitest 49, lint, format, tip, knip — toza.npm run build && npm run preview: ikki tab — kirish, ikkinchi tab sessiya bilan ochiladi; birida «Chiqish» — ikkinchisi darhol kirish oynasiga. DevTools → Application → Local Storage: faqatvazifalar:foydalanuvchi={"ism":"…"}, parol yo'q.- Forma ostidagi ogohlantirish ko'rinadi: "Mashq kirishi: parol yo'q…".
- Commit —
feat: soxta kirish — AuthContext, himoyalangan UI, chiqish hamma tablarda. PR, birlashtirish,vercel --prod(17-qismdagidek).
10. Real ishda
- Auth kutubxonalari va xizmatlari — Auth.js, Clerk, Supabase Auth, Firebase Auth — hammasi frontend'ga deyarli bir xil API beradi: Provider,
useSession()/useAuth(), himoyalangan qism (Auth kutubxonalari va xizmatlari). Bugungi uch qism — ularning ichki modeli. - Bank va to'lov ilovalari (Click, Payme veb-kabinetlari) — bir tabda chiqish hamma tablarda chiqishni talab qiladi; ko'pincha "faolsizlik taymeri" ham qo'shiladi.
- Kod ko'rigi da: "bu tekshiruv serverda ham bormi?" — frontend auth kodiga eng ko'p beriladigan savol.
- Intervyu savollari: "React'da auth holatini qayerda saqlaysiz?", "himoyalangan sahifani qanday qilasiz va u nega yetarli emas?", "barcha tablarda logout?", "access va refresh token farqi?".
Xulosa
- Auth holati — Context'da:
AuthProvider+ guard hookuseAuth(); ilova props'siz oladi. - Himoyalangan UI — shartli chizish: chiqishda ilova unmount, effektlar tozalanadi;
hiddenemas. - Saqlash — tashqi manba:
localStorage+useSyncExternalStore, snapshot — xom satr, o'qish — tekshiruv bilan. - Barcha tablarda —
BroadcastChannel, xabar ma'lumotsiz ("qayta o'qi"); shu tab — qo'lda xabardor qilinadi. - Frontend himoyasi — faqat ko'rinish. Bugungi kirish soxta; haqiqiy auth (parol, sessiya,
httpOnlycookie) — 28-qismda, serverda. vazifalarv8 tayyor: soxta kirish,nevereslatmasi, e2e-18 72/72, node 173, Vitest 49, birinchi tashrif Lighthouse 99/100/100/100.
Keyingi dars: SPA routing qanday ishlaydi — 19-qism: bitta sahifali ilovada URL va sahifalar qanday almashadi.
Manbalar
- React:
createContext,use,useSyncExternalStore— react.dev/reference/react (2026-10 holatiga, React 19.3) - MDN: Broadcast Channel API — developer.mozilla.org/docs/Web/API/Broadcast_Channel_API
- MDN:
Window: storage event— developer.mozilla.org/docs/Web/API/Window/storage_event - OWASP: Authentication Cheat Sheet, Session Management Cheat Sheet — cheatsheetseries.owasp.org
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!