Добавлены call-pushes
This commit is contained in:
4
.env
4
.env
@@ -14,6 +14,10 @@ SDU_SERVERS=http://10.211.55.2:7777
|
|||||||
|
|
||||||
#Firebase Credentials
|
#Firebase Credentials
|
||||||
FIREBASE_CREDENTIALS_PATH=serviceAccount.json
|
FIREBASE_CREDENTIALS_PATH=serviceAccount.json
|
||||||
|
#Apple APNS
|
||||||
|
APNS_KEY_PATH=voip.p12
|
||||||
|
APNS_P12_PASSWORD=rosetta1
|
||||||
|
IOS_BUNDLE_ID=com.rosetta.dev
|
||||||
|
|
||||||
#Каждые сколько дней будет очищаться буфер (максимальная дистанция синхронизации сообщений)
|
#Каждые сколько дней будет очищаться буфер (максимальная дистанция синхронизации сообщений)
|
||||||
BUFFER_CLEANUP_DAYS=7
|
BUFFER_CLEANUP_DAYS=7
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -34,6 +34,9 @@ target
|
|||||||
.settings
|
.settings
|
||||||
.project
|
.project
|
||||||
.classpath
|
.classpath
|
||||||
|
serviceAccount.json
|
||||||
|
build/*.p12
|
||||||
|
*.p12
|
||||||
|
|
||||||
build/.env
|
build/.env
|
||||||
build/.env*
|
build/.env*
|
||||||
|
|||||||
6
pom.xml
6
pom.xml
@@ -46,6 +46,12 @@
|
|||||||
<artifactId>jakarta.persistence-api</artifactId>
|
<artifactId>jakarta.persistence-api</artifactId>
|
||||||
<version>3.1.0</version>
|
<version>3.1.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.eatthepath</groupId>
|
||||||
|
<artifactId>pushy</artifactId>
|
||||||
|
<version>0.15.4</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package im.rosetta.executors;
|
package im.rosetta.executors;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import im.rosetta.Failures;
|
import im.rosetta.Failures;
|
||||||
import im.rosetta.client.ClientManager;
|
import im.rosetta.client.ClientManager;
|
||||||
import im.rosetta.client.tags.ECIAuthentificate;
|
import im.rosetta.client.tags.ECIAuthentificate;
|
||||||
import im.rosetta.packet.Packet26SignalPeer;
|
import im.rosetta.packet.Packet26SignalPeer;
|
||||||
import im.rosetta.packet.runtime.NetworkSignalType;
|
import im.rosetta.packet.runtime.NetworkSignalType;
|
||||||
|
import im.rosetta.service.dispatch.push.PushNotifyDispatcher;
|
||||||
|
import im.rosetta.service.dispatch.runtime.PushType;
|
||||||
import im.rosetta.service.services.ForwardUnitService;
|
import im.rosetta.service.services.ForwardUnitService;
|
||||||
import io.g365sfu.Room;
|
import io.g365sfu.Room;
|
||||||
import io.orprotocol.ProtocolException;
|
import io.orprotocol.ProtocolException;
|
||||||
@@ -18,6 +23,7 @@ public class Executor26SignalPeer extends PacketExecutor<Packet26SignalPeer> {
|
|||||||
|
|
||||||
private ClientManager clientManager;
|
private ClientManager clientManager;
|
||||||
private ForwardUnitService fus;
|
private ForwardUnitService fus;
|
||||||
|
private PushNotifyDispatcher pushNotifyDispatcher = new PushNotifyDispatcher();
|
||||||
|
|
||||||
public Executor26SignalPeer(ClientManager clientManager, ForwardUnitService fus) {
|
public Executor26SignalPeer(ClientManager clientManager, ForwardUnitService fus) {
|
||||||
this.clientManager = clientManager;
|
this.clientManager = clientManager;
|
||||||
@@ -26,6 +32,8 @@ public class Executor26SignalPeer extends PacketExecutor<Packet26SignalPeer> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPacketReceived(Packet26SignalPeer packet, Client client) throws Exception, ProtocolException {
|
public void onPacketReceived(Packet26SignalPeer packet, Client client) throws Exception, ProtocolException {
|
||||||
|
String src = packet.getSrc();
|
||||||
|
String dst = packet.getDst();
|
||||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||||
if (eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) {
|
if (eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) {
|
||||||
/**
|
/**
|
||||||
@@ -35,6 +43,14 @@ public class Executor26SignalPeer extends PacketExecutor<Packet26SignalPeer> {
|
|||||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(!src.equals(eciAuthentificate.getPublicKey())) {
|
||||||
|
/**
|
||||||
|
* Если src в пакете не совпадает с авторизованным PK клиента, то это может означать, что клиент пытается
|
||||||
|
* отправить сигнал от другого пользователя, отключаем его от сервера.
|
||||||
|
*/
|
||||||
|
client.disconnect(Failures.DATA_MISSMATCH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
NetworkSignalType type = packet.getSignalType();
|
NetworkSignalType type = packet.getSignalType();
|
||||||
if(type == NetworkSignalType.CALL) {
|
if(type == NetworkSignalType.CALL) {
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +66,14 @@ public class Executor26SignalPeer extends PacketExecutor<Packet26SignalPeer> {
|
|||||||
this.clientManager.sendPacketToAuthorizedPK(packet.getSrc(), responsePacket);
|
this.clientManager.sendPacketToAuthorizedPK(packet.getSrc(), responsePacket);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Получатель сигнала не занят, отправляем ему пуш уведомление о входящем звонке и сигнал CALL для инициализации звонка
|
||||||
|
*/
|
||||||
|
pushNotifyDispatcher.sendPush(dst, new HashMap<>(){{
|
||||||
|
put("type", PushType.CALL);
|
||||||
|
put("dialog", src);
|
||||||
|
put("callId", UUID.randomUUID().toString());
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
if(type == NetworkSignalType.CREATE_ROOM){
|
if(type == NetworkSignalType.CREATE_ROOM){
|
||||||
/**
|
/**
|
||||||
@@ -66,14 +90,8 @@ public class Executor26SignalPeer extends PacketExecutor<Packet26SignalPeer> {
|
|||||||
this.clientManager.sendPacketToAuthorizedPK(packet.getDst(), packet);
|
this.clientManager.sendPacketToAuthorizedPK(packet.getDst(), packet);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* TODO: Проверка на существование получателя
|
|
||||||
*/
|
|
||||||
this.clientManager.sendPacketToAuthorizedPK(packet.getDst(), packet);
|
this.clientManager.sendPacketToAuthorizedPK(packet.getDst(), packet);
|
||||||
/**
|
|
||||||
* TODO: Высокоприоритетный пуш для сигналов звонков, чтобы мобильные устройства могли показать
|
|
||||||
* интерфейс входящего звонка, даже если приложение находится в фоне
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,8 +82,7 @@ public class FCM extends Pusher {
|
|||||||
break;
|
break;
|
||||||
case PushType.CALL:
|
case PushType.CALL:
|
||||||
/**
|
/**
|
||||||
* Звонок для андроид используем high priority, чтобы уведомление доставлялось даже если устройство в режиме Doze,
|
* Это только для Android, для iOS используется VoIP APNs с отдельным сертификатом
|
||||||
* для iOS используем VoIP уведомление, которое доставляется даже если приложение убито
|
|
||||||
*/
|
*/
|
||||||
androidConfig.setPriority(AndroidConfig.Priority.HIGH);
|
androidConfig.setPriority(AndroidConfig.Priority.HIGH);
|
||||||
messageBuilder.setAndroidConfig(androidConfig.build());
|
messageBuilder.setAndroidConfig(androidConfig.build());
|
||||||
|
|||||||
@@ -1,15 +1,94 @@
|
|||||||
package im.rosetta.service.dispatch.push.dispatchers;
|
package im.rosetta.service.dispatch.push.dispatchers;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import com.eatthepath.pushy.apns.ApnsClient;
|
||||||
|
import com.eatthepath.pushy.apns.ApnsClientBuilder;
|
||||||
|
import com.eatthepath.pushy.apns.PushNotificationResponse;
|
||||||
|
import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification;
|
||||||
|
import com.eatthepath.pushy.apns.util.TokenUtil;
|
||||||
|
|
||||||
import im.rosetta.service.dispatch.push.Pusher;
|
import im.rosetta.service.dispatch.push.Pusher;
|
||||||
|
import im.rosetta.service.dispatch.runtime.PushType;
|
||||||
|
|
||||||
public class VoIPApns extends Pusher {
|
public class VoIPApns extends Pusher {
|
||||||
|
|
||||||
|
private ApnsClient client;
|
||||||
|
private String topic;
|
||||||
|
|
||||||
|
public VoIPApns(){
|
||||||
|
this.initializeApns();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeApns() {
|
||||||
|
try {
|
||||||
|
String p12Path = System.getenv("APNS_KEY_PATH");
|
||||||
|
String p12Password = System.getenv("APNS_P12_PASSWORD");
|
||||||
|
String bundleId = System.getenv("IOS_BUNDLE_ID");
|
||||||
|
|
||||||
|
if (p12Path == null || bundleId == null) {
|
||||||
|
throw new IllegalStateException("APNS_P12_PATH and IOS_BUNDLE_ID must be set");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.topic = bundleId + ".voip";
|
||||||
|
|
||||||
|
this.client = new ApnsClientBuilder()
|
||||||
|
.setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST)
|
||||||
|
.setClientCredentials(new File(p12Path), p12Password)
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to init VoIP APNs client", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendPush(String token, HashMap<String, String> data) {
|
public void sendPush(String token, HashMap<String, String> data) {
|
||||||
// TODO Auto-generated method stub
|
if(data.get("type") != PushType.CALL) {
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'sendPush'");
|
/**
|
||||||
|
* Для VoIP APNs отправляем уведомления только о входящих звонках
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String normalizedToken = TokenUtil.sanitizeTokenString(token);
|
||||||
|
|
||||||
|
String payload = """
|
||||||
|
{
|
||||||
|
"aps": { "content-available": 1 },
|
||||||
|
"type": "CALL",
|
||||||
|
"callId": "%s",
|
||||||
|
"from": "%s"
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
escape(data.getOrDefault("callId", "")),
|
||||||
|
escape(data.getOrDefault("dialog", ""))
|
||||||
|
);
|
||||||
|
|
||||||
|
SimpleApnsPushNotification push = new SimpleApnsPushNotification(
|
||||||
|
normalizedToken,
|
||||||
|
topic,
|
||||||
|
payload,
|
||||||
|
null, // invalidation time
|
||||||
|
com.eatthepath.pushy.apns.DeliveryPriority.IMMEDIATE,
|
||||||
|
com.eatthepath.pushy.apns.PushType.VOIP // apns-push-type: voip
|
||||||
|
);
|
||||||
|
|
||||||
|
PushNotificationResponse<SimpleApnsPushNotification> response = client.sendNotification(push).get();
|
||||||
|
|
||||||
|
if (!response.isAccepted()) {
|
||||||
|
System.err.println("VoIP push rejected: " + response.getRejectionReason());
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escape(String v) {
|
||||||
|
return v == null ? "" : v.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ public class Server extends WebSocketServer {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
client.disconnect(ServerFailures.BAD_PACKET);
|
client.disconnect(ServerFailures.BAD_PACKET);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user