chat: same-origin /chat/api proxy вместо прямого CORS к Mattermost
Браузер больше не ходит на chat.* с portal.* — REST идёт через Django с PAT; WS backoff без бесконечных 403; utilities.js ждёт DOMContentLoaded. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -131,6 +131,7 @@ function chatInit() {
|
||||
chatShowArchived = false;
|
||||
chatPrefDirectShow = {};
|
||||
chatPrefGroupShow = {};
|
||||
chatWsFailures = 0;
|
||||
|
||||
chatLoadTeams().then(chatLoadPreferences).then(chatLoadChannels).then(chatOpenPendingDm);
|
||||
chatConnectSocket();
|
||||
@@ -172,17 +173,45 @@ function chatOpenDmWithUser(userId) {
|
||||
}).catch(function (e) { console.error(e); });
|
||||
}
|
||||
|
||||
function chatCsrfToken() {
|
||||
var m = document.cookie.match(/(?:^|; )csrftoken=([^;]*)/);
|
||||
return m ? decodeURIComponent(m[1]) : '';
|
||||
}
|
||||
|
||||
function chatApiFetch(path, options) {
|
||||
options = options || {};
|
||||
options.headers = Object.assign({'Authorization': 'Bearer ' + chatConfig.token}, options.headers || {});
|
||||
var headers = Object.assign({
|
||||
'Authorization': 'Bearer ' + chatConfig.token,
|
||||
'X-CSRFToken': chatCsrfToken(),
|
||||
}, options.headers || {});
|
||||
// FormData: не фиксируем Content-Type — браузер сам поставит boundary
|
||||
if (typeof FormData !== 'undefined' && options.body instanceof FormData) {
|
||||
delete headers['Content-Type'];
|
||||
}
|
||||
options.headers = headers;
|
||||
return fetch(chatConfig.apiBase + path, options).then(function (res) {
|
||||
if (!res.ok) throw new Error('Mattermost API ' + path + ' -> ' + res.status);
|
||||
return res.json();
|
||||
if (!res.ok) {
|
||||
var err = new Error('Mattermost API ' + path + ' -> ' + res.status);
|
||||
err.status = res.status;
|
||||
throw err;
|
||||
}
|
||||
var ct = res.headers.get('content-type') || '';
|
||||
if (ct.indexOf('application/json') >= 0 || ct.indexOf('+json') >= 0) {
|
||||
return res.json();
|
||||
}
|
||||
return res.text().then(function (t) {
|
||||
try { return JSON.parse(t); } catch (e) { return t; }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function chatApiFetchBlob(path) {
|
||||
return fetch(chatConfig.apiBase + path, {headers: {'Authorization': 'Bearer ' + chatConfig.token}}).then(function (res) {
|
||||
return fetch(chatConfig.apiBase + path, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + chatConfig.token,
|
||||
'X-CSRFToken': chatCsrfToken(),
|
||||
},
|
||||
}).then(function (res) {
|
||||
if (!res.ok) throw new Error('Mattermost API ' + path + ' -> ' + res.status);
|
||||
return res.blob();
|
||||
});
|
||||
@@ -306,8 +335,9 @@ function chatLoadChannels() {
|
||||
chatLoadLastActivity(); // уточнюємо порядок за реальним часом останніх постів
|
||||
}).catch(function (e) {
|
||||
console.error(e);
|
||||
var detail = (e && e.status) ? (' (' + e.status + ')') : '';
|
||||
var msg = '<div style="padding:1rem; text-align:center; font-size:0.8125rem; color:var(--color-destructive);">'
|
||||
+ gettext('Не удалось загрузить каналы') + '</div>';
|
||||
+ gettext('Не удалось загрузить каналы') + detail + '</div>';
|
||||
var el = document.getElementById('chat-channel-list');
|
||||
if (el) el.innerHTML = msg;
|
||||
var dm = document.getElementById('chat-dm-list');
|
||||
@@ -1169,12 +1199,27 @@ function chatStartNewDm() {
|
||||
}).catch(function (e) { console.error(e); });
|
||||
}
|
||||
|
||||
var chatWsFailures = 0;
|
||||
var chatWsMaxBackoff = 60000;
|
||||
|
||||
function chatConnectSocket() {
|
||||
if (!chatConfig) return;
|
||||
var socket = new WebSocket(chatConfig.wsUrl);
|
||||
if (!chatConfig || !chatConfig.wsUrl) return;
|
||||
// Live WS идёт на public_url Mattermost (не через Django). Без AllowCorsFrom /
|
||||
// разрешения Origin на chat.* будет 403 — не долбим реконнект бесконечно.
|
||||
if (chatWsFailures >= 8) return;
|
||||
|
||||
var socket;
|
||||
try {
|
||||
socket = new WebSocket(chatConfig.wsUrl);
|
||||
} catch (e) {
|
||||
console.warn('chat websocket create failed', e);
|
||||
chatWsFailures += 1;
|
||||
return;
|
||||
}
|
||||
chatSocket = socket;
|
||||
|
||||
socket.onopen = function () {
|
||||
chatWsFailures = 0;
|
||||
socket.send(JSON.stringify({seq: 1, action: 'authentication_challenge', data: {token: chatConfig.token}}));
|
||||
};
|
||||
|
||||
@@ -1187,11 +1232,13 @@ function chatConnectSocket() {
|
||||
|
||||
socket.onclose = function () {
|
||||
if (!document.getElementById('chat-app')) return;
|
||||
chatReconnectTimer = setTimeout(chatConnectSocket, 3000);
|
||||
chatWsFailures += 1;
|
||||
var delay = Math.min(chatWsMaxBackoff, 3000 * Math.pow(2, Math.min(chatWsFailures - 1, 4)));
|
||||
chatReconnectTimer = setTimeout(chatConnectSocket, delay);
|
||||
};
|
||||
|
||||
socket.onerror = function () {
|
||||
socket.close();
|
||||
try { socket.close(); } catch (e) { /* ignore */ }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user