# Conflicts:
#	shine-UI/js/app.js
This commit is contained in:
AidarKC
2026-04-22 14:58:28 +03:00
20 changed files with 1094 additions and 37 deletions
+10
View File
@@ -1639,6 +1639,16 @@ export class AuthService {
return response.payload || {};
}
async reportClientDebug({ runId = '', level = 'info', message = '', details = '' } = {}) {
try {
const response = await this.ws.request('ClientDebugLog', { runId, level, message, details }, 3000);
return response?.status === 200;
} catch {
return false;
}
}
async reportClientError(details) {
try {
const response = await this.ws.request('ClientErrorLog', details || {}, 3000);
+172 -34
View File
@@ -1,4 +1,4 @@
import { addChatMessage, state, authService } from '../state.js';
import { addChatMessage, authService } from '../state.js';
const TYPES = {
INVITE: 100,
@@ -14,6 +14,7 @@ const TYPES = {
const calls = new Map();
let activeCallId = '';
let debugReporter = null;
function nowMs() {
return Date.now();
@@ -27,9 +28,26 @@ function getCall(callId) {
return calls.get(callId) || null;
}
function toErrorText(error) {
return error?.message || String(error || 'unknown');
}
async function emitDebug(call, level, message, details = '') {
if (!call?.debugRunId || typeof debugReporter !== 'function') return;
try {
await debugReporter({
runId: call.debugRunId,
level,
message,
details,
});
} catch {}
}
function setStatus(call, text) {
call.status = text;
addChatMessage(call.peerLogin, `[call] ${text}`);
addChatMessage(call.peerLogin || 'debug-peer', `[call] ${text}`);
void emitDebug(call, 'info', `call_status: ${text}`, `callId=${call.callId}`);
}
function cleanupTimers(call) {
@@ -61,10 +79,30 @@ async function finishCall(call, reason, notifyRemote = false) {
}
await closeMedia(call);
setStatus(call, `завершен: ${reason}`);
if (String(reason || '').toLowerCase().includes('connected')) {
await emitDebug(call, 'info', 'debug_connection_success', reason);
}
calls.delete(call.callId);
if (activeCallId === call.callId) activeCallId = '';
}
async function sendSignal(call, type, data = '') {
if (!call.remoteSessionId) return;
try {
await authService.callSignalToSession({
toLogin: call.peerLogin,
targetSessionId: call.remoteSessionId,
callId: call.callId,
type,
data,
});
await emitDebug(call, 'info', `signal_sent_${type}`, `len=${String(data || '').length}`);
} catch (error) {
await emitDebug(call, 'error', `signal_send_failed_${type}`, toErrorText(error));
throw error;
}
}
async function ensurePeerConnection(call) {
if (call.pc) return call.pc;
@@ -72,25 +110,45 @@ async function ensurePeerConnection(call) {
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});
if (call.debugMode && call.debugRole === 'initiator') {
const dc = pc.createDataChannel('debug-ping');
dc.onopen = () => {
try { dc.send('ping'); } catch {}
void emitDebug(call, 'info', 'debug_datachannel_open', 'sent ping');
};
dc.onmessage = (evt) => {
void emitDebug(call, 'info', 'debug_datachannel_message', String(evt?.data || ''));
};
}
pc.ondatachannel = (evt) => {
const ch = evt?.channel;
if (!ch) return;
ch.onmessage = (msg) => {
const incoming = String(msg?.data || '');
void emitDebug(call, 'info', 'debug_datachannel_message_in', incoming);
if (incoming === 'ping') {
try { ch.send('pong'); } catch {}
}
};
};
pc.onicecandidate = async (event) => {
if (!event.candidate || !call.remoteSessionId) return;
try {
await authService.callSignalToSession({
toLogin: call.peerLogin,
targetSessionId: call.remoteSessionId,
callId: call.callId,
type: TYPES.ICE,
data: JSON.stringify(event.candidate),
});
await sendSignal(call, TYPES.ICE, JSON.stringify(event.candidate));
} catch {}
};
pc.onconnectionstatechange = () => {
if (pc.connectionState === 'connected') {
const state = pc.connectionState;
if (state === 'connected') {
setStatus(call, 'соединение установлено');
void emitDebug(call, 'info', 'peer_connection_connected', `callId=${call.callId}`);
}
if (pc.connectionState === 'failed' || pc.connectionState === 'closed' || pc.connectionState === 'disconnected') {
finishCall(call, `state=${pc.connectionState}`, false);
if (state === 'failed' || state === 'closed' || state === 'disconnected') {
void emitDebug(call, 'warn', 'peer_connection_closed', `state=${state}`);
finishCall(call, `state=${state}`, false);
}
};
@@ -100,6 +158,7 @@ async function ensurePeerConnection(call) {
stream.getTracks().forEach((track) => pc.addTrack(track, stream));
} catch (e) {
setStatus(call, `нет доступа к микрофону: ${e?.message || 'unknown'}`);
await emitDebug(call, 'warn', 'microphone_access_failed', toErrorText(e));
}
pc.ontrack = (evt) => {
@@ -113,17 +172,6 @@ async function ensurePeerConnection(call) {
return pc;
}
async function sendSignal(call, type, data = '') {
if (!call.remoteSessionId) return;
await authService.callSignalToSession({
toLogin: call.peerLogin,
targetSessionId: call.remoteSessionId,
callId: call.callId,
type,
data,
});
}
async function onAccept(call) {
cleanupTimers(call);
const pc = await ensurePeerConnection(call);
@@ -133,6 +181,72 @@ async function onAccept(call) {
setStatus(call, 'отправлен offer');
}
export function setCallDebugReporter(fn) {
debugReporter = typeof fn === 'function' ? fn : null;
}
export async function startDebugConnectionAsResponder({ runId, callId, peerLogin, peerSessionId }) {
const cleanCallId = String(callId || '').trim();
const cleanPeerLogin = String(peerLogin || '').trim();
const cleanPeerSessionId = String(peerSessionId || '').trim();
if (!cleanCallId || !cleanPeerLogin || !cleanPeerSessionId) return;
let call = getCall(cleanCallId);
if (!call) {
call = {
callId: cleanCallId,
peerLogin: cleanPeerLogin,
direction: 'in',
state: 'accepted',
remoteSessionId: cleanPeerSessionId,
timers: {},
startedAtMs: nowMs(),
pc: null,
localStream: null,
debugMode: true,
debugRunId: String(runId || '').trim(),
debugRole: 'responder',
};
calls.set(cleanCallId, call);
}
activeCallId = cleanCallId;
await emitDebug(call, 'info', 'debug_prepare_responder', `peerSessionId=${cleanPeerSessionId}`);
setStatus(call, 'debug: responder готов, ждём offer');
}
export async function startDebugConnectionAsInitiator({ runId, callId, peerLogin, peerSessionId }) {
const cleanCallId = String(callId || '').trim();
const cleanPeerLogin = String(peerLogin || '').trim();
const cleanPeerSessionId = String(peerSessionId || '').trim();
if (!cleanCallId || !cleanPeerLogin || !cleanPeerSessionId) return;
const call = {
callId: cleanCallId,
peerLogin: cleanPeerLogin,
direction: 'out',
state: 'accepted',
remoteSessionId: cleanPeerSessionId,
timers: {},
startedAtMs: nowMs(),
pc: null,
localStream: null,
debugMode: true,
debugRunId: String(runId || '').trim(),
debugRole: 'initiator',
};
calls.set(cleanCallId, call);
activeCallId = cleanCallId;
await emitDebug(call, 'info', 'debug_start_initiator', `peerSessionId=${cleanPeerSessionId}`);
try {
await onAccept(call);
} catch (error) {
await emitDebug(call, 'error', 'debug_initiator_start_failed', toErrorText(error));
await finishCall(call, `debug start failed: ${toErrorText(error)}`, false);
}
}
export async function startOutgoingCall(peerLogin) {
const cleanPeer = String(peerLogin || '').trim();
if (!cleanPeer) return;
@@ -153,6 +267,9 @@ export async function startOutgoingCall(peerLogin) {
startedAtMs: nowMs(),
pc: null,
localStream: null,
debugMode: false,
debugRunId: '',
debugRole: '',
};
calls.set(callId, call);
activeCallId = callId;
@@ -203,6 +320,9 @@ export async function handleIncomingCallInvite(evt) {
startedAtMs: nowMs(),
pc: null,
localStream: null,
debugMode: false,
debugRunId: '',
debugRole: '',
};
calls.set(callId, call);
}
@@ -273,25 +393,43 @@ export async function handleIncomingCallSignal(evt) {
}
if (type === TYPES.OFFER) {
const pc = await ensurePeerConnection(call);
await pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(data)));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
await sendSignal(call, TYPES.ANSWER, JSON.stringify(answer));
setStatus(call, 'получен offer, отправлен answer');
try {
const pc = await ensurePeerConnection(call);
await pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(data)));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
await sendSignal(call, TYPES.ANSWER, JSON.stringify(answer));
setStatus(call, 'получен offer, отправлен answer');
await emitDebug(call, 'info', 'offer_processed', 'answer sent');
} catch (error) {
await emitDebug(call, 'error', 'offer_process_failed', toErrorText(error));
throw error;
}
return;
}
if (type === TYPES.ANSWER) {
const pc = await ensurePeerConnection(call);
await pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(data)));
setStatus(call, 'получен answer');
try {
const pc = await ensurePeerConnection(call);
await pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(data)));
setStatus(call, 'получен answer');
await emitDebug(call, 'info', 'answer_processed', 'remote description set');
} catch (error) {
await emitDebug(call, 'error', 'answer_process_failed', toErrorText(error));
throw error;
}
return;
}
if (type === TYPES.ICE) {
const pc = await ensurePeerConnection(call);
await pc.addIceCandidate(new RTCIceCandidate(JSON.parse(data)));
try {
const pc = await ensurePeerConnection(call);
await pc.addIceCandidate(new RTCIceCandidate(JSON.parse(data)));
await emitDebug(call, 'info', 'ice_processed', 'candidate added');
} catch (error) {
await emitDebug(call, 'error', 'ice_process_failed', toErrorText(error));
throw error;
}
}
}