Poprawki bezpieczenstwa: maile, kredyty, token strumienia, lokalizacja, spotkania, zgloszenia
Deploy Polska Lokalnie / deploy (push) Successful in 13s
Deploy Polska Lokalnie / deploy (push) Successful in 13s
- Imie, tytul artykulu i powod odrzucenia oraz {{name}} w kampaniach
escapowane w HTML maili; limit dlugosci imienia.
- Kredyty promowania zuzywane i dopisywane warunkowym UPDATE, ITN blokuje
wiersz zamowienia; zakup i kredyt tylko na aktywny plan/pakiet.
- Strumien powiadomien na krotkim (60 s) tokenie ?ticket= pobieranym przez
POST /api/notifications/stream-token; token sesji nie jest przyjmowany z adresu.
- Przyblizona lokalizacja jako srodek kratki 300 m zamiast odwracalnego
przesuniecia liczonego z id.
- Propozycja spotkania respektuje blokade i moderacje, bez e-maila w nazwie.
- Zglaszajacy dostaje samo potwierdzenie; zgloszenia tylko opublikowanych
ogloszen; nie zapisujemy rozmowy przyslanej przez przegladarke.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -107,7 +107,8 @@ export function NotificationsProvider({ children }: { children: ReactNode }) {
|
||||
.catch(() => { /* cicho - brak sieci/tokenu */ });
|
||||
}, []);
|
||||
|
||||
// Ładowanie listy + realtime (SSE) gdy użytkownik zalogowany. Token w query param (EventSource nie ustawia nagłówków).
|
||||
// Ładowanie listy + realtime (SSE) gdy użytkownik zalogowany. EventSource nie ustawia nagłówków,
|
||||
// więc w adresie idzie krótki (60 s) token samego strumienia - nie token sesji, który trafiałby do logów.
|
||||
useEffect(() => {
|
||||
const token = getToken();
|
||||
if (!user || !token) {
|
||||
@@ -118,22 +119,52 @@ export function NotificationsProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
refresh();
|
||||
|
||||
const source = new EventSource(`/api/notifications/stream?access_token=${encodeURIComponent(token)}`);
|
||||
source.addEventListener('notification', (event) => {
|
||||
let disposed = false;
|
||||
let source: EventSource | null = null;
|
||||
let reconnectId: number | undefined;
|
||||
|
||||
const connect = async () => {
|
||||
try {
|
||||
const incoming = JSON.parse((event as MessageEvent).data) as ServerNotification;
|
||||
setNotifications((current) => (current.some((item) => item.id === incoming.id) ? current : [incoming, ...current]));
|
||||
const { token: ticket } = await apiFetch<{ token: string }>('/notifications/stream-token', { method: 'POST' });
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
source = new EventSource(`/api/notifications/stream?ticket=${encodeURIComponent(ticket)}`);
|
||||
source.addEventListener('notification', (event) => {
|
||||
try {
|
||||
const incoming = JSON.parse((event as MessageEvent).data) as ServerNotification;
|
||||
setNotifications((current) => (current.some((item) => item.id === incoming.id) ? current : [incoming, ...current]));
|
||||
} catch {
|
||||
/* ignoruj nieparsowalne zdarzenie */
|
||||
}
|
||||
});
|
||||
// Token strumienia wygasa po minucie, więc samoczynne wznowienie EventSource pod tym samym
|
||||
// adresem by nie zadziałało - po zerwaniu łączymy się od nowa z nowym tokenem.
|
||||
source.onerror = () => {
|
||||
source?.close();
|
||||
scheduleReconnect();
|
||||
};
|
||||
} catch {
|
||||
/* ignoruj nieparsowalne zdarzenie */
|
||||
scheduleReconnect();
|
||||
}
|
||||
});
|
||||
// onerror: EventSource sam ponawia połączenie.
|
||||
};
|
||||
|
||||
const scheduleReconnect = () => {
|
||||
if (!disposed) {
|
||||
window.clearTimeout(reconnectId);
|
||||
reconnectId = window.setTimeout(() => { void connect(); }, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
void connect();
|
||||
|
||||
// Zapasowy polling na wypadek zerwanego SSE.
|
||||
const pollId = window.setInterval(refresh, 60000);
|
||||
|
||||
return () => {
|
||||
source.close();
|
||||
disposed = true;
|
||||
source?.close();
|
||||
window.clearTimeout(reconnectId);
|
||||
window.clearInterval(pollId);
|
||||
};
|
||||
}, [user, refresh]);
|
||||
|
||||
Reference in New Issue
Block a user