Group chat enhancements to combat SL bugs

This commit is contained in:
Casper Warden
2022-04-19 16:04:55 +01:00
parent 487907fb85
commit 2104e03b40
9 changed files with 237 additions and 82 deletions

View File

@@ -42,12 +42,13 @@ export class Agent
regionAccess: string;
agentAccess: string;
currentRegion: Region;
chatSessions: { [key: string]: {
[key: string]: {
hasVoice: boolean,
isModerator: boolean
}
} } = {};
chatSessions = new Map<string, {
agents: Map<string, {
hasVoice: boolean;
isModerator: boolean
}>,
timeout?: Timer
}>();
controlFlags: ControlFlags = 0;
openID: {
'token'?: string,
@@ -96,6 +97,8 @@ export class Agent
private clientEvents: ClientEvents;
private animSubscription?: Subscription;
public onGroupChatExpired = new Subject<UUID>();
constructor(clientEvents: ClientEvents)
{
this.inventory = new Inventory(clientEvents, this);
@@ -103,27 +106,51 @@ export class Agent
this.clientEvents.onGroupChatAgentListUpdate.subscribe((event: GroupChatSessionAgentListEvent) =>
{
const str = event.groupID.toString();
if (this.chatSessions[str] === undefined)
{
this.chatSessions[str] = {};
}
const agent = event.agentID.toString();
const session = this.chatSessions.get(str);
if (session === undefined)
{
return;
}
if (event.entered)
{
this.chatSessions[str][agent] = {
if (session.agents === undefined)
{
session.agents = new Map<string, {
hasVoice: boolean;
isModerator: boolean
}>();
}
session.agents.set(agent, {
hasVoice: event.canVoiceChat,
isModerator: event.isModerator
}
});
}
else
{
delete this.chatSessions[str][agent];
session.agents.delete(agent);
}
});
}
public updateLastMessage(groupID: UUID): void
{
const str = groupID.toString();
const entry = this.chatSessions.get(str);
if (entry === undefined)
{
return;
}
if (entry.timeout !== undefined)
{
clearInterval(entry.timeout);
entry.timeout = setTimeout(this.groupChatExpired.bind(this, groupID), 900000);
}
}
setIsEstateManager(is: boolean): void
{
this.estateManager = is;
@@ -132,29 +159,54 @@ export class Agent
getSessionAgentCount(uuid: UUID): number
{
const str = uuid.toString();
if (this.chatSessions[str] === undefined)
const session = this.chatSessions.get(str);
if (session === undefined)
{
return 0;
}
else
{
return Object.keys(this.chatSessions[str]).length;
return Object.keys(session.agents).length;
}
}
addChatSession(uuid: UUID): void
addChatSession(uuid: UUID, timeout: boolean): boolean
{
const str = uuid.toString();
if (this.chatSessions[str] === undefined)
if (this.chatSessions.has(str))
{
this.chatSessions[str] = {};
return false;
}
this.chatSessions.set(str, {
agents: new Map<string, {
hasVoice: boolean,
isModerator: boolean
}>(),
timeout: timeout ? setTimeout(this.groupChatExpired.bind(this, uuid), 900000) : undefined
});
return true;
}
private groupChatExpired(groupID: UUID): void
{
this.onGroupChatExpired.next(groupID);
}
hasChatSession(uuid: UUID): boolean
{
const str = uuid.toString();
return !(this.chatSessions[str] === undefined);
return this.chatSessions.has(str);
}
deleteChatSession(uuid: UUID): boolean
{
const str = uuid.toString();
if (!this.chatSessions.has(str))
{
return false;
}
this.chatSessions.delete(str);
return true;
}
setCurrentRegion(region: Region): void

View File

@@ -1,4 +1,5 @@
import { Subject } from 'rxjs';
import { GroupChatClosedEvent } from '../events/GroupChatClosedEvent';
import { NewObjectEvent } from '../events/NewObjectEvent';
import { ObjectUpdatedEvent } from '../events/ObjectUpdatedEvent';
import { ObjectKilledEvent } from '../events/ObjectKilledEvent';
@@ -43,6 +44,7 @@ export class ClientEvents
onDisconnected: Subject<DisconnectEvent> = new Subject<DisconnectEvent>();
onCircuitLatency: Subject<number> = new Subject<number>();
onGroupChat: Subject<GroupChatEvent> = new Subject<GroupChatEvent>();
onGroupChatClosed: Subject<GroupChatClosedEvent> = new Subject<GroupChatClosedEvent>();
onGroupNotice: Subject<GroupNoticeEvent> = new Subject<GroupNoticeEvent>();
onGroupChatSessionJoin: Subject<GroupChatSessionJoinEvent> = new Subject<GroupChatSessionJoinEvent>();
onGroupChatAgentListUpdate: Subject<GroupChatSessionAgentListEvent> = new Subject<GroupChatSessionAgentListEvent>();

View File

@@ -1,3 +1,6 @@
import { Subscription } from 'rxjs';
import { GroupChatClosedEvent } from '../events/GroupChatClosedEvent';
import { Agent } from './Agent';
import { Circuit } from './Circuit';
import { Packet } from './Packet';
import { Message } from '../enums/Message';
@@ -25,13 +28,11 @@ import { InventoryResponseEvent } from '../events/InventoryResponseEvent';
export class Comms
{
private circuit: Circuit;
private clientEvents: ClientEvents;
private groupChatExpiredSub?: Subscription;
constructor(circuit: Circuit, clientEvents: ClientEvents)
constructor(public readonly circuit: Circuit, public readonly agent: Agent, public readonly clientEvents: ClientEvents)
{
this.clientEvents = clientEvents;
this.circuit = circuit;
this.groupChatExpiredSub = this.agent.onGroupChatExpired.subscribe(this.groupChatExpired.bind(this));
this.circuit.subscribeToMessages([
Message.ImprovedInstantMessage,
@@ -263,6 +264,14 @@ export class Comms
this.clientEvents.onInstantMessage.next(imEvent);
break;
}
case InstantMessageDialog.SessionDrop:
{
const groupChatClosedEvent = new GroupChatClosedEvent();
groupChatClosedEvent.groupID = im.MessageBlock.ID;
this.clientEvents.onGroupChatClosed.next(groupChatClosedEvent);
this.agent.deleteChatSession(groupChatClosedEvent.groupID);
break;
}
case InstantMessageDialog.SessionSend:
{
const groupChatEvent = new GroupChatEvent();
@@ -270,6 +279,11 @@ export class Comms
groupChatEvent.fromName = Utils.BufferToStringSimple(im.MessageBlock.FromAgentName);
groupChatEvent.groupID = im.MessageBlock.ID;
groupChatEvent.message = Utils.BufferToStringSimple(im.MessageBlock.Message);
if (!this.agent.hasChatSession(groupChatEvent.groupID))
{
this.agent.addChatSession(groupChatEvent.groupID, true);
}
this.agent.updateLastMessage(groupChatEvent.groupID);
this.clientEvents.onGroupChat.next(groupChatEvent);
break;
}
@@ -335,6 +349,17 @@ export class Comms
shutdown(): void
{
if (this.groupChatExpiredSub !== undefined)
{
this.groupChatExpiredSub.unsubscribe();
delete this.groupChatExpiredSub;
}
}
private async groupChatExpired(groupID: UUID): Promise<void>
{
// Reconnect to group chat since it's been idle for 15 minutes
await this.agent.currentRegion.clientCommands.comms.endGroupChatSession(groupID, false);
await this.agent.currentRegion.clientCommands.comms.startGroupChatSession(groupID, '');
}
}

View File

@@ -337,7 +337,11 @@ export class EventQueueClient
if (gcsje.success)
{
gcsje.sessionID = new UUID(event['body']['session_id'].toString());
this.agent.addChatSession(gcsje.sessionID);
const added = this.agent.addChatSession(gcsje.sessionID, true);
if (!added)
{
return;
}
}
this.clientEvents.onGroupChatSessionJoin.next(gcsje);
}
@@ -363,13 +367,13 @@ export class EventQueueClient
};
this.caps.capsPostXML('ChatSessionRequest', requested).then((_ignore: unknown) =>
{
this.agent.addChatSession(groupChatEvent.groupID);
this.agent.addChatSession(groupChatEvent.groupID, true);
const gcsje = new GroupChatSessionJoinEvent();
gcsje.sessionID = groupChatEvent.groupID;
gcsje.success = true;
this.clientEvents.onGroupChatSessionJoin.next(gcsje);
this.clientEvents.onGroupChat.next(groupChatEvent);
this.agent.updateLastMessage(groupChatEvent.groupID);
}).catch((err) =>
{
console.error(err);

View File

@@ -338,7 +338,7 @@ export class Region
{
this.objects = new ObjectStoreFull(this.circuit, agent, clientEvents, options);
}
this.comms = new Comms(this.circuit, clientEvents);
this.comms = new Comms(this.circuit, agent, clientEvents);
this.parcelPropertiesSubscription = this.clientEvents.onParcelPropertiesEvent.subscribe(async(parcelProperties: ParcelPropertiesEvent) =>
{

View File

@@ -389,6 +389,48 @@ export class CommunicationsCommands extends CommandsBase
});
}
public async endGroupChatSession(groupID: UUID | string, removeEntry = true): Promise<void>
{
if (typeof groupID === 'string')
{
groupID = new UUID(groupID);
}
if (!this.agent.hasChatSession(groupID))
{
throw new Error('Group session does not exist');
}
const circuit = this.circuit;
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: groupID,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: InstantMessageDialog.SessionDrop,
ID: groupID,
Timestamp: Math.floor(new Date().getTime() / 1000),
FromAgentName: Utils.StringToBuffer(agentName),
Message: Buffer.allocUnsafe(0),
BinaryBucket: Buffer.allocUnsafe(0)
};
im.EstateBlock = {
EstateID: 0
};
if (removeEntry)
{
this.agent.deleteChatSession(groupID);
}
const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable);
return this.circuit.waitForAck(sequenceNo, 10000);
}
startGroupChatSession(groupID: UUID | string, message: string): Promise<void>
{
return new Promise<void>((resolve, reject) =>
@@ -397,54 +439,47 @@ export class CommunicationsCommands extends CommandsBase
{
groupID = new UUID(groupID);
}
if (this.agent.hasChatSession(groupID))
{
resolve();
}
else
{
const circuit = this.circuit;
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: groupID,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: InstantMessageDialog.SessionGroupStart,
ID: groupID,
Timestamp: Math.floor(new Date().getTime() / 1000),
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(message),
BinaryBucket: Utils.StringToBuffer('')
};
im.EstateBlock = {
EstateID: 0
};
const waitForJoin = this.currentRegion.clientEvents.onGroupChatSessionJoin.subscribe((event: GroupChatSessionJoinEvent) =>
{
if (event.sessionID.toString() === groupID.toString())
{
if (event.success)
{
waitForJoin.unsubscribe();
resolve();
}
else
{
reject();
}
const circuit = this.circuit;
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: groupID,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: InstantMessageDialog.SessionGroupStart,
ID: groupID,
Timestamp: Math.floor(new Date().getTime() / 1000),
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(message),
BinaryBucket: Utils.StringToBuffer('')
};
im.EstateBlock = {
EstateID: 0
};
const waitForJoin = this.currentRegion.clientEvents.onGroupChatSessionJoin.subscribe((event: GroupChatSessionJoinEvent) =>
{
if (event.sessionID.toString() === groupID.toString())
{
if (event.success)
{
waitForJoin.unsubscribe();
resolve();
}
});
circuit.sendMessage(im, PacketFlags.Reliable);
}
else
{
reject();
}
}
});
circuit.sendMessage(im, PacketFlags.Reliable);
});
}