diff --git a/public/js/api.js b/public/js/api.js index 1043460..d6b916a 100644 --- a/public/js/api.js +++ b/public/js/api.js @@ -1,5 +1,5 @@ -// Same-origin API wrapper (api проксируется через nginx на тот же домен) -// Никаких retry/timeout на login — только UI feedback +// Same-origin API wrapper — обычный скрипт, без ES modules (Safari iOS) +// Используем только credentials:'include' + кэш отключен const DEFAULT_INIT = { credentials: 'include', @@ -7,8 +7,14 @@ const DEFAULT_INIT = { redirect: 'follow', }; -export async function api(path, options = {}) { - const { json, headers: extraHeaders, ...rest } = options; +async function api(path, options) { + options = options || {}; + const json = options.json; + const extraHeaders = options.headers; + const rest = options; + delete rest.json; + delete rest.headers; + const headers = new Headers(extraHeaders || {}); let body = rest.body; @@ -28,11 +34,11 @@ export async function api(path, options = {}) { }); const ct = res.headers.get('content-type') || ''; - const isJson = ct.includes('application/json'); - const data = isJson ? await res.json().catch(() => null) : await res.text(); + const isJson = ct.indexOf('application/json') >= 0; + const data = isJson ? await res.json().catch(function () { return null; }) : await res.text(); if (!res.ok) { - const err = new Error((data && (data.message || data.error)) || `HTTP ${res.status}`); + const err = new Error((data && (data.message || data.error)) || ('HTTP ' + res.status)); err.status = res.status; err.data = data; throw err; @@ -40,60 +46,70 @@ export async function api(path, options = {}) { 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' }); +const apiGet = function (path, opts) { return api(path, Object.assign({}, opts, { method: 'GET' })); }; +const apiPost = function (path, json, opts) { return api(path, Object.assign({}, opts, { method: 'POST', json: json })); }; +const apiPut = function (path, json, opts) { return api(path, Object.assign({}, opts, { method: 'PUT', json: json })); }; +const apiDelete = function (path, opts) { return api(path, Object.assign({}, 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), +const Store = { + access: null, + refresh: null, + user: null, + save: function (tokens) { + if (!tokens) return; + this.access = tokens.Access || tokens.access; + this.refresh = tokens.Refresh || tokens.refresh; }, - 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; }, + load: function () { return this.access; }, + clear: function () { this.access = null; this.refresh = null; this.user = null; }, +}; + +const Auth = { + loginEmail: function (email, password) { return apiPost('/api/v1/auth/login', { email: email, password: password }); }, + registerEmail: function (data) { return apiPost('/api/v1/auth/register', data); }, + me: function () { return apiGet('/api/v1/me'); }, + updateMe: function (p) { return apiPut('/api/v1/me', p); }, + getPrefs: function () { return apiGet('/api/v1/me/prefs'); }, + updatePrefs: function (p) { return apiPut('/api/v1/me/prefs', p); }, + updateLocation: function (lat, lng, visible) { return apiPut('/api/v1/me/location', { lat: lat, lng: lng, visible: visible }); }, + setVisibility: function (v) { return apiPut('/api/v1/me/visibility', { visible: v }); }, + searchNearby: function (lat, lng, radius) { return apiGet('/api/v1/search/nearby?lat=' + lat + '&lng=' + lng + '&radius=' + radius); }, + getUser: function (id) { return apiGet('/api/v1/users/' + id); }, +}; + +const ChatApi = { + ensureChat: function (otherId) { return apiPost('/api/v1/chats', { other_id: otherId }); }, + listChats: function () { return apiGet('/api/v1/chats'); }, + listMessages: function (chatId) { return apiGet('/api/v1/chats/' + chatId + '/messages'); }, + sendMessage: function (chatId, body, photoUrl) { return apiPost('/api/v1/chats/' + chatId + '/messages', { body: body, photo_url: photoUrl }); }, + editMessage: function (msgId, body) { return apiPut('/api/v1/messages/' + msgId, { body: body }); }, + deleteMessage: function (msgId) { return apiDelete('/api/v1/messages/' + msgId); }, + markRead: function (chatId) { return apiPut('/api/v1/chats/' + chatId + '/read', {}); }, + block: function (userId) { return apiPost('/api/v1/blocks', { user_id: userId }); }, + report: function (type, id, reason) { return apiPost('/api/v1/reports', { target_type: type, target_id: id, reason: reason }); }, +}; + +const ReviewApi = { + create: function (chatId, rating, body, anonymous) { return apiPost('/api/v1/reviews', { chat_id: chatId, rating: rating, body: body, anonymous: anonymous }); }, + listForUser: function (userId) { return apiGet('/api/v1/users/' + userId + '/reviews'); }, + stats: function (userId) { return apiGet('/api/v1/users/' + userId + '/stats'); }, +}; + +const ConsentApi = { + status: function () { return apiGet('/api/v1/auth/consents').catch(function () { return { all_accepted: false, accepted: {} }; }); }, + accept: function (payload) { return apiPost('/api/v1/auth/consents', payload); }, +}; + +const PlacesApi = { + nearby: function (lat, lng, radius, limit) { + limit = limit || 30; + return apiGet('/api/v1/places/nearby?lat=' + lat + '&lng=' + lng + '&radius=' + (radius || 5) + '&limit=' + limit) + .catch(function (e) { return { places: [], count: 0, error: e.message }; }); }, }; + +window.API = { + api: api, apiGet: apiGet, apiPost: apiPost, apiPut: apiPut, apiDelete: apiDelete, + Auth: Auth, ChatApi: ChatApi, ReviewApi: ReviewApi, ConsentApi: ConsentApi, PlacesApi: PlacesApi, + Store: Store, +};