- api.js полностью переписан: simple fetch с credentials:'include', без mode:cors - emailLogin: без 150мс паузы, без retry — same-origin не висит - nginx: /api/* → backend через тот же домен (убирает cross-subdomain) - backend cookie: SameSite=Lax, host-only
100 lines
4.1 KiB
JavaScript
100 lines
4.1 KiB
JavaScript
// Same-origin API wrapper (api проксируется через nginx на тот же домен)
|
||
// Никаких retry/timeout на login — только UI feedback
|
||
|
||
const DEFAULT_INIT = {
|
||
credentials: 'include',
|
||
cache: 'no-store',
|
||
redirect: 'follow',
|
||
};
|
||
|
||
export async function api(path, options = {}) {
|
||
const { json, headers: extraHeaders, ...rest } = options;
|
||
const headers = new Headers(extraHeaders || {});
|
||
let body = rest.body;
|
||
|
||
if (json !== undefined) {
|
||
headers.set('Content-Type', 'application/json');
|
||
body = JSON.stringify(json);
|
||
}
|
||
if (!headers.has('Accept')) {
|
||
headers.set('Accept', 'application/json');
|
||
}
|
||
|
||
const res = await fetch(path, {
|
||
...DEFAULT_INIT,
|
||
...rest,
|
||
headers,
|
||
body,
|
||
});
|
||
|
||
const ct = res.headers.get('content-type') || '';
|
||
const isJson = ct.includes('application/json');
|
||
const data = isJson ? await res.json().catch(() => null) : await res.text();
|
||
|
||
if (!res.ok) {
|
||
const err = new Error((data && (data.message || data.error)) || `HTTP ${res.status}`);
|
||
err.status = res.status;
|
||
err.data = data;
|
||
throw err;
|
||
}
|
||
return data;
|
||
}
|
||
|
||
export const apiGet = (path, opts) => api(path, { ...opts, method: 'GET' });
|
||
export const apiPost = (path, json, opts) => api(path, { ...opts, method: 'POST', json });
|
||
export const apiPut = (path, json, opts) => api(path, { ...opts, method: 'PUT', json });
|
||
export const apiDelete = (path, opts) => api(path, { ...opts, method: 'DELETE' });
|
||
|
||
// Совместимость со старым window.API.Auth
|
||
window.API = {
|
||
api, apiGet, apiPost, apiPut, apiDelete,
|
||
// обёртки для совместимости со старыми view
|
||
Auth: {
|
||
loginEmail: (email, password) => apiPost('/api/v1/auth/login', { email, password }),
|
||
registerEmail: (data) => apiPost('/api/v1/auth/register', data),
|
||
me: () => apiGet('/api/v1/me'),
|
||
updateMe: (p) => apiPut('/api/v1/me', p),
|
||
getPrefs: () => apiGet('/api/v1/me/prefs'),
|
||
updatePrefs: (p) => apiPut('/api/v1/me/prefs', p),
|
||
updateLocation: (lat, lng, visible) => apiPut('/api/v1/me/location', { lat, lng, visible }),
|
||
setVisibility: (v) => apiPut('/api/v1/me/visibility', { visible: v }),
|
||
searchNearby: (lat, lng, radius) => apiGet(`/api/v1/search/nearby?lat=${lat}&lng=${lng}&radius=${radius}`),
|
||
getUser: (id) => apiGet('/api/v1/users/' + id),
|
||
},
|
||
ChatApi: {
|
||
ensureChat: (otherId) => apiPost('/api/v1/chats', { other_id: otherId }),
|
||
listChats: () => apiGet('/api/v1/chats'),
|
||
listMessages: (chatId) => apiGet('/api/v1/chats/' + chatId + '/messages'),
|
||
sendMessage: (chatId, body, photoUrl) => apiPost('/api/v1/chats/' + chatId + '/messages', { body, photo_url: photoUrl }),
|
||
editMessage: (msgId, body) => apiPut('/api/v1/messages/' + msgId, { body }),
|
||
deleteMessage: (msgId) => apiDelete('/api/v1/messages/' + msgId),
|
||
markRead: (chatId) => apiPut('/api/v1/chats/' + chatId + '/read', {}),
|
||
block: (userId) => apiPost('/api/v1/blocks', { user_id: userId }),
|
||
report: (type, id, reason) => apiPost('/api/v1/reports', { target_type: type, target_id: id, reason }),
|
||
},
|
||
ReviewApi: {
|
||
create: (chatId, rating, body, anonymous) => apiPost('/api/v1/reviews', { chat_id: chatId, rating, body, anonymous }),
|
||
listForUser: (userId) => apiGet('/api/v1/users/' + userId + '/reviews'),
|
||
stats: (userId) => apiGet('/api/v1/users/' + userId + '/stats'),
|
||
},
|
||
ConsentApi: {
|
||
status: () => apiGet('/api/v1/auth/consents').catch(() => ({ all_accepted: false, accepted: {} })),
|
||
accept: (payload) => apiPost('/api/v1/auth/consents', payload),
|
||
},
|
||
PlacesApi: {
|
||
nearby: (lat, lng, radius, limit = 30) => apiGet(`/api/v1/places/nearby?lat=${lat}&lng=${lng}&radius=${radius || 5}&limit=${limit}`).catch((e) => ({ places: [], count: 0, error: e.message })),
|
||
},
|
||
Store: {
|
||
access: null,
|
||
refresh: null,
|
||
user: null,
|
||
save(tokens) {
|
||
if (!tokens) return;
|
||
this.access = tokens.Access || tokens.access;
|
||
this.refresh = tokens.Refresh || tokens.refresh;
|
||
},
|
||
load() { return this.access; },
|
||
clear() { this.access = null; this.refresh = null; this.user = null; },
|
||
},
|
||
};
|