Звонки: WebPush incoming/stop, actions и TTL; обновлена логика

This commit is contained in:
AidarKC
2026-05-02 18:25:44 +03:00
parent 310863faec
commit c0c29b74ab
10 changed files with 711 additions and 208 deletions
@@ -9,14 +9,14 @@ 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.WebPushSender;
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.ActiveSessionsDAO;
import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.PushTokenEntry;
import shine.db.entities.ActiveSessionEntry;
import shine.db.entities.SolanaUserEntry;
import java.util.HashSet;
@@ -26,6 +26,7 @@ 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;
private static final long PUSH_CALL_TTL_MS = 10_000L;
@Override
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) throws Exception {
@@ -49,12 +50,13 @@ public class Net_CallInviteBroadcast_Handler implements JsonMessageHandler {
String from = ctx.getLogin();
String to = targetUser.getLogin();
long timeMs = System.currentTimeMillis();
long expiresAtMs = timeMs + PUSH_CALL_TTL_MS;
Set<ConnectionContext> activeSessions = ActiveConnectionsRegistry.getInstance().getByLogin(to);
List<PushTokenEntry> tokens = PushTokensDAO.getInstance().listByLogin(to);
List<ActiveSessionEntry> allTargetSessions = ActiveSessionsDAO.getInstance().getByLogin(to);
int wsDelivered = 0;
int fcmDelivered = 0;
int webPushDelivered = 0;
Set<String> activeSessionIds = new HashSet<>();
for (ConnectionContext targetCtx : activeSessions) {
@@ -74,14 +76,31 @@ public class Net_CallInviteBroadcast_Handler implements JsonMessageHandler {
if (sent) wsDelivered++;
}
for (PushTokenEntry token : tokens) {
boolean pushed = FcmPushSender.sendNotification(
token.getToken(),
"Входящий звонок",
from + " пытается дозвониться",
callId
for (ActiveSessionEntry session : allTargetSessions) {
String sessionId = String.valueOf(session.getSessionId() == null ? "" : session.getSessionId()).trim();
if (!sessionId.isBlank() && activeSessionIds.contains(sessionId)) {
continue;
}
if (isBlank(session.getPushEndpoint()) || isBlank(session.getPushP256dhKey()) || isBlank(session.getPushAuthKey())) {
continue;
}
String payload = "{\"kind\":\"incoming_call\""
+ ",\"title\":\"SHiNE: входящий звонок\""
+ ",\"text\":\"Вам звонит " + jsonEscape(from) + "\""
+ ",\"fromLogin\":\"" + jsonEscape(from) + "\""
+ ",\"fromSessionId\":\"" + jsonEscape(ctx.getSessionId()) + "\""
+ ",\"toLogin\":\"" + jsonEscape(to) + "\""
+ ",\"callId\":\"" + jsonEscape(callId) + "\""
+ ",\"sentAtMs\":" + timeMs
+ ",\"expiresAtMs\":" + expiresAtMs
+ "}";
boolean pushed = WebPushSender.sendBase64Payload(
session.getPushEndpoint(),
session.getPushP256dhKey(),
session.getPushAuthKey(),
payload
);
if (pushed) fcmDelivered++;
if (pushed) webPushDelivered++;
}
Net_CallInviteBroadcast_Response resp = new Net_CallInviteBroadcast_Response();
@@ -90,7 +109,27 @@ public class Net_CallInviteBroadcast_Handler implements JsonMessageHandler {
resp.setStatus(WireCodes.Status.OK);
resp.setCallId(callId);
resp.setDeliveredWsSessions(wsDelivered);
resp.setDeliveredFcmSessions(fcmDelivered);
resp.setDeliveredFcmSessions(webPushDelivered);
resp.setDeliveredWebPushSessions(webPushDelivered);
return resp;
}
private boolean isBlank(String s) {
return s == null || s.isBlank();
}
private static String jsonEscape(String s) {
if (s == null) return "";
StringBuilder out = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '\\') out.append("\\\\");
else if (c == '"') out.append("\\\"");
else if (c == '\n') out.append("\\n");
else if (c == '\r') out.append("\\r");
else if (c == '\t') out.append("\\t");
else out.append(c);
}
return out.toString();
}
}
@@ -9,18 +9,25 @@ 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.WebPushSender;
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.ActiveSessionsDAO;
import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.ActiveSessionEntry;
import shine.db.entities.SolanaUserEntry;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Net_CallSignalToSession_Handler implements JsonMessageHandler {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final int TYPE_ACCEPT = 120;
private static final int TYPE_DECLINE_BUSY = 130;
private static final int TYPE_TIMEOUT = 140;
private static final int TYPE_HANGUP = 150;
@Override
@@ -72,7 +79,34 @@ public class Net_CallSignalToSession_Handler implements JsonMessageHandler {
boolean delivered = WsEventSender.sendEvent(targetCtx, "IncomingCallSignal", eventId, payload);
if (type == TYPE_ACCEPT) {
notifyAcceptedOnOtherSessions(ctx, callId);
notifyStopOnOtherSessions(
ctx.getLogin(),
ctx.getSessionId(),
ctx.getLogin(),
ctx.getSessionId(),
callId,
"accepted_on_other_device"
);
}
if (type == TYPE_DECLINE_BUSY || type == TYPE_TIMEOUT || type == TYPE_HANGUP) {
String reason = "terminal_call_signal_" + type;
notifyStopOnOtherSessions(
ctx.getLogin(),
ctx.getSessionId(),
ctx.getLogin(),
ctx.getSessionId(),
callId,
reason
);
notifyStopOnOtherSessions(
to,
targetCtx.getSessionId(),
ctx.getLogin(),
ctx.getSessionId(),
callId,
reason
);
}
Net_CallSignalToSession_Response resp = new Net_CallSignalToSession_Response();
@@ -83,31 +117,81 @@ public class Net_CallSignalToSession_Handler implements JsonMessageHandler {
return resp;
}
private void notifyAcceptedOnOtherSessions(ConnectionContext accepterCtx, String callId) {
if (accepterCtx == null) return;
String login = accepterCtx.getLogin();
String acceptedSessionId = accepterCtx.getSessionId();
if (login == null || login.isBlank() || acceptedSessionId == null || acceptedSessionId.isBlank() || callId == null || callId.isBlank()) {
private void notifyStopOnOtherSessions(
String targetLogin,
String excludeSessionId,
String fromLogin,
String fromSessionId,
String callId,
String reason
) throws Exception {
if (isBlank(targetLogin) || isBlank(callId)) {
return;
}
Set<ConnectionContext> sameUserSessions = ActiveConnectionsRegistry.getInstance().getByLogin(login);
Set<String> onlineSessionIds = new HashSet<>();
Set<ConnectionContext> sameUserSessions = ActiveConnectionsRegistry.getInstance().getByLogin(targetLogin);
for (ConnectionContext siblingCtx : sameUserSessions) {
if (siblingCtx == null || siblingCtx.getWsSession() == null || !siblingCtx.getWsSession().isOpen()) continue;
if (acceptedSessionId.equals(siblingCtx.getSessionId())) continue;
onlineSessionIds.add(String.valueOf(siblingCtx.getSessionId() == null ? "" : siblingCtx.getSessionId()).trim());
if (!isBlank(excludeSessionId) && excludeSessionId.equals(siblingCtx.getSessionId())) continue;
String siblingEventId = NetIdGenerator.eventId("evt");
ObjectNode siblingPayload = MAPPER.createObjectNode();
siblingPayload.put("eventId", siblingEventId);
siblingPayload.put("fromLogin", login);
siblingPayload.put("fromSessionId", acceptedSessionId);
siblingPayload.put("toLogin", login);
siblingPayload.put("fromLogin", fromLogin);
siblingPayload.put("fromSessionId", fromSessionId);
siblingPayload.put("toLogin", targetLogin);
siblingPayload.put("callId", callId);
siblingPayload.put("type", TYPE_HANGUP);
siblingPayload.put("data", "accepted_on_other_device");
siblingPayload.put("data", reason);
siblingPayload.put("timeMs", System.currentTimeMillis());
WsEventSender.sendEvent(siblingCtx, "IncomingCallSignal", siblingEventId, siblingPayload);
}
List<ActiveSessionEntry> persistedSessions = ActiveSessionsDAO.getInstance().getByLogin(targetLogin);
long sentAtMs = System.currentTimeMillis();
for (ActiveSessionEntry session : persistedSessions) {
String sessionId = String.valueOf(session.getSessionId() == null ? "" : session.getSessionId()).trim();
if (!isBlank(excludeSessionId) && excludeSessionId.equals(sessionId)) continue;
if (!sessionId.isBlank() && onlineSessionIds.contains(sessionId)) continue;
if (isBlank(session.getPushEndpoint()) || isBlank(session.getPushP256dhKey()) || isBlank(session.getPushAuthKey())) {
continue;
}
String pushPayload = "{\"kind\":\"stop_call\""
+ ",\"callId\":\"" + jsonEscape(callId) + "\""
+ ",\"reason\":\"" + jsonEscape(reason) + "\""
+ ",\"fromLogin\":\"" + jsonEscape(fromLogin) + "\""
+ ",\"fromSessionId\":\"" + jsonEscape(fromSessionId) + "\""
+ ",\"toLogin\":\"" + jsonEscape(targetLogin) + "\""
+ ",\"sentAtMs\":" + sentAtMs
+ "}";
WebPushSender.sendBase64Payload(
session.getPushEndpoint(),
session.getPushP256dhKey(),
session.getPushAuthKey(),
pushPayload
);
}
}
private boolean isBlank(String s) {
return s == null || s.isBlank();
}
private static String jsonEscape(String s) {
if (s == null) return "";
StringBuilder out = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '\\') out.append("\\\\");
else if (c == '"') out.append("\\\"");
else if (c == '\n') out.append("\\n");
else if (c == '\r') out.append("\\r");
else if (c == '\t') out.append("\\t");
else out.append(c);
}
return out.toString();
}
}
@@ -6,6 +6,7 @@ public class Net_CallInviteBroadcast_Response extends Net_Response {
private String callId;
private int deliveredWsSessions;
private int deliveredFcmSessions;
private int deliveredWebPushSessions;
public String getCallId() { return callId; }
public void setCallId(String callId) { this.callId = callId; }
@@ -15,4 +16,7 @@ public class Net_CallInviteBroadcast_Response extends Net_Response {
public int getDeliveredFcmSessions() { return deliveredFcmSessions; }
public void setDeliveredFcmSessions(int deliveredFcmSessions) { this.deliveredFcmSessions = deliveredFcmSessions; }
public int getDeliveredWebPushSessions() { return deliveredWebPushSessions; }
public void setDeliveredWebPushSessions(int deliveredWebPushSessions) { this.deliveredWebPushSessions = deliveredWebPushSessions; }
}