merge: codex/outline-call-request-workflow into main

This commit is contained in:
AidarKC
2026-04-16 01:27:48 +03:00
13 changed files with 669 additions and 2 deletions
@@ -59,9 +59,13 @@ import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_GetUserCo
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_AddCloseFriend_Request;
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_ListContacts_Request;
import server.logic.ws_protocol.JSON.messages.Net_AckIncomingMessage_Handler;
import server.logic.ws_protocol.JSON.messages.Net_CallInviteBroadcast_Handler;
import server.logic.ws_protocol.JSON.messages.Net_CallSignalToSession_Handler;
import server.logic.ws_protocol.JSON.messages.Net_SendDirectMessage_Handler;
import server.logic.ws_protocol.JSON.messages.Net_UpsertPushToken_Handler;
import server.logic.ws_protocol.JSON.messages.entyties.Net_AckIncomingMessage_Request;
import server.logic.ws_protocol.JSON.messages.entyties.Net_CallInviteBroadcast_Request;
import server.logic.ws_protocol.JSON.messages.entyties.Net_CallSignalToSession_Request;
import server.logic.ws_protocol.JSON.messages.entyties.Net_SendDirectMessage_Request;
import server.logic.ws_protocol.JSON.messages.entyties.Net_UpsertPushToken_Request;
@@ -121,6 +125,8 @@ public final class JsonHandlerRegistry {
Map.entry("UpsertPushToken", new Net_UpsertPushToken_Handler()),
Map.entry("SendDirectMessage", new Net_SendDirectMessage_Handler()),
Map.entry("AckIncomingMessage", new Net_AckIncomingMessage_Handler()),
Map.entry("CallInviteBroadcast", new Net_CallInviteBroadcast_Handler()),
Map.entry("CallSignalToSession", new Net_CallSignalToSession_Handler()),
// --- system ---
Map.entry("Ping", new Net_Ping_Handler()),
@@ -167,6 +173,8 @@ public final class JsonHandlerRegistry {
Map.entry("UpsertPushToken", Net_UpsertPushToken_Request.class),
Map.entry("SendDirectMessage", Net_SendDirectMessage_Request.class),
Map.entry("AckIncomingMessage", Net_AckIncomingMessage_Request.class),
Map.entry("CallInviteBroadcast", Net_CallInviteBroadcast_Request.class),
Map.entry("CallSignalToSession", Net_CallSignalToSession_Request.class),
// --- system ---
Map.entry("Ping", Net_Ping_Request.class),
@@ -0,0 +1,96 @@
package server.logic.ws_protocol.JSON.messages;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.messages.entyties.Net_CallInviteBroadcast_Request;
import server.logic.ws_protocol.JSON.messages.entyties.Net_CallInviteBroadcast_Response;
import server.logic.ws_protocol.JSON.push.FcmPushSender;
import server.logic.ws_protocol.JSON.push.WsEventSender;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.JSON.utils.NetIdGenerator;
import server.logic.ws_protocol.WireCodes;
import shine.db.dao.PushTokensDAO;
import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.PushTokenEntry;
import shine.db.entities.SolanaUserEntry;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Net_CallInviteBroadcast_Handler implements JsonMessageHandler {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final int TYPE_INVITE = 100;
@Override
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) throws Exception {
Net_CallInviteBroadcast_Request req = (Net_CallInviteBroadcast_Request) baseRequest;
if (ctx == null || !ctx.isAuthenticatedUser()) {
return NetExceptionResponseFactory.error(req, WireCodes.Status.UNVERIFIED, "NOT_AUTHENTICATED", "Требуется авторизация");
}
String toRequest = req.getToLogin() == null ? "" : req.getToLogin().trim();
String callId = req.getCallId() == null ? "" : req.getCallId().trim();
int type = req.getType() == null ? TYPE_INVITE : req.getType();
if (toRequest.isBlank() || callId.isBlank() || type != TYPE_INVITE) {
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_FIELDS", "toLogin/callId/type=100 обязательны");
}
SolanaUserEntry targetUser = SolanaUsersDAO.getInstance().getByLogin(toRequest);
if (targetUser == null) {
return NetExceptionResponseFactory.error(req, 404, "USER_NOT_FOUND", "Пользователь не найден");
}
String from = ctx.getLogin();
String to = targetUser.getLogin();
long timeMs = System.currentTimeMillis();
Set<ConnectionContext> activeSessions = ActiveConnectionsRegistry.getInstance().getByLogin(to);
List<PushTokenEntry> tokens = PushTokensDAO.getInstance().listByLogin(to);
int wsDelivered = 0;
int fcmDelivered = 0;
Set<String> activeSessionIds = new HashSet<>();
for (ConnectionContext targetCtx : activeSessions) {
activeSessionIds.add(targetCtx.getSessionId());
String eventId = NetIdGenerator.eventId("evt");
ObjectNode payload = MAPPER.createObjectNode();
payload.put("eventId", eventId);
payload.put("fromLogin", from);
payload.put("fromSessionId", ctx.getSessionId());
payload.put("toLogin", to);
payload.put("callId", callId);
payload.put("type", TYPE_INVITE);
payload.put("timeMs", timeMs);
boolean sent = WsEventSender.sendEvent(targetCtx, "IncomingCallInvite", eventId, payload);
if (sent) wsDelivered++;
}
for (PushTokenEntry token : tokens) {
boolean pushed = FcmPushSender.sendNotification(
token.getToken(),
"Входящий звонок",
from + " пытается дозвониться",
callId
);
if (pushed) fcmDelivered++;
}
Net_CallInviteBroadcast_Response resp = new Net_CallInviteBroadcast_Response();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(WireCodes.Status.OK);
resp.setCallId(callId);
resp.setDeliveredWsSessions(wsDelivered);
resp.setDeliveredFcmSessions(fcmDelivered);
return resp;
}
}
@@ -0,0 +1,70 @@
package server.logic.ws_protocol.JSON.messages;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.messages.entyties.Net_CallSignalToSession_Request;
import server.logic.ws_protocol.JSON.messages.entyties.Net_CallSignalToSession_Response;
import server.logic.ws_protocol.JSON.push.WsEventSender;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.JSON.utils.NetIdGenerator;
import server.logic.ws_protocol.WireCodes;
import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.SolanaUserEntry;
public class Net_CallSignalToSession_Handler implements JsonMessageHandler {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) throws Exception {
Net_CallSignalToSession_Request req = (Net_CallSignalToSession_Request) baseRequest;
if (ctx == null || !ctx.isAuthenticatedUser()) {
return NetExceptionResponseFactory.error(req, WireCodes.Status.UNVERIFIED, "NOT_AUTHENTICATED", "Требуется авторизация");
}
String toRequest = req.getToLogin() == null ? "" : req.getToLogin().trim();
String targetSessionId = req.getTargetSessionId() == null ? "" : req.getTargetSessionId().trim();
String callId = req.getCallId() == null ? "" : req.getCallId().trim();
Integer type = req.getType();
String data = req.getData() == null ? "" : req.getData();
if (toRequest.isBlank() || targetSessionId.isBlank() || callId.isBlank() || type == null) {
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_FIELDS", "toLogin/targetSessionId/callId/type обязательны");
}
SolanaUserEntry targetUser = SolanaUsersDAO.getInstance().getByLogin(toRequest);
if (targetUser == null) {
return NetExceptionResponseFactory.error(req, 404, "USER_NOT_FOUND", "Пользователь не найден");
}
String to = targetUser.getLogin();
ConnectionContext targetCtx = ActiveConnectionsRegistry.getInstance().getBySessionId(targetSessionId);
if (targetCtx == null || !to.equalsIgnoreCase(targetCtx.getLogin())) {
return NetExceptionResponseFactory.error(req, 404, "SESSION_NOT_FOUND", "Целевая сессия не найдена");
}
String eventId = NetIdGenerator.eventId("evt");
ObjectNode payload = MAPPER.createObjectNode();
payload.put("eventId", eventId);
payload.put("fromLogin", ctx.getLogin());
payload.put("fromSessionId", ctx.getSessionId());
payload.put("toLogin", to);
payload.put("callId", callId);
payload.put("type", type);
payload.put("data", data);
payload.put("timeMs", System.currentTimeMillis());
boolean delivered = WsEventSender.sendEvent(targetCtx, "IncomingCallSignal", eventId, payload);
Net_CallSignalToSession_Response resp = new Net_CallSignalToSession_Response();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(delivered ? WireCodes.Status.OK : 404);
resp.setDelivered(delivered);
return resp;
}
}
@@ -0,0 +1,18 @@
package server.logic.ws_protocol.JSON.messages.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
public class Net_CallInviteBroadcast_Request extends Net_Request {
private String toLogin;
private String callId;
private Integer type;
public String getToLogin() { return toLogin; }
public void setToLogin(String toLogin) { this.toLogin = toLogin; }
public String getCallId() { return callId; }
public void setCallId(String callId) { this.callId = callId; }
public Integer getType() { return type; }
public void setType(Integer type) { this.type = type; }
}
@@ -0,0 +1,18 @@
package server.logic.ws_protocol.JSON.messages.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
public class Net_CallInviteBroadcast_Response extends Net_Response {
private String callId;
private int deliveredWsSessions;
private int deliveredFcmSessions;
public String getCallId() { return callId; }
public void setCallId(String callId) { this.callId = callId; }
public int getDeliveredWsSessions() { return deliveredWsSessions; }
public void setDeliveredWsSessions(int deliveredWsSessions) { this.deliveredWsSessions = deliveredWsSessions; }
public int getDeliveredFcmSessions() { return deliveredFcmSessions; }
public void setDeliveredFcmSessions(int deliveredFcmSessions) { this.deliveredFcmSessions = deliveredFcmSessions; }
}
@@ -0,0 +1,26 @@
package server.logic.ws_protocol.JSON.messages.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
public class Net_CallSignalToSession_Request extends Net_Request {
private String toLogin;
private String targetSessionId;
private String callId;
private Integer type;
private String data;
public String getToLogin() { return toLogin; }
public void setToLogin(String toLogin) { this.toLogin = toLogin; }
public String getTargetSessionId() { return targetSessionId; }
public void setTargetSessionId(String targetSessionId) { this.targetSessionId = targetSessionId; }
public String getCallId() { return callId; }
public void setCallId(String callId) { this.callId = callId; }
public Integer getType() { return type; }
public void setType(Integer type) { this.type = type; }
public String getData() { return data; }
public void setData(String data) { this.data = data; }
}
@@ -0,0 +1,10 @@
package server.logic.ws_protocol.JSON.messages.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
public class Net_CallSignalToSession_Response extends Net_Response {
private boolean delivered;
public boolean isDelivered() { return delivered; }
public void setDelivered(boolean delivered) { this.delivered = delivered; }
}