Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(client-presence): ISessionClient naming consistency #22973

Merged
merged 10 commits into from
Nov 4, 2024
12 changes: 12 additions & 0 deletions .changeset/ninety-zoos-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@fluid-experimental/presence": minor
---
---
"section": feature
---

ISessionClient now exposes connectivity information

1. `ISessionClient` has a new method, `getConnectionStatus()`, with two possible states: `Connected` and `Disconnected`.
2. `ISessionClient`'s `connectionId()` member has been renamed to `getConnectionId()` for consistency.
3. `IPresence` event `attendeeDisconnected` is now implemented.
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ function makePresenceView(
logContentDiv.style.border = "1px solid black";
if (audience !== undefined) {
presenceConfig.presence.events.on("attendeeJoined", (attendee) => {
const name = audience.getMembers().get(attendee.connectionId())?.name;
const name = audience.getMembers().get(attendee.getConnectionId())?.name;
const update = `client ${name === undefined ? "(unnamed)" : `named ${name}`} with id ${attendee.sessionId} joined`;
addLogEntry(logContentDiv, update);
});
Expand Down
5 changes: 2 additions & 3 deletions packages/framework/presence/api-report/presence.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ export interface IPresence {

// @alpha @sealed
export interface ISessionClient<SpecificSessionClientId extends ClientSessionId = ClientSessionId> {
connectionId(): ClientConnectionId;
getStatus(): SessionClientStatus;
// (undocumented)
getConnectionId(): ClientConnectionId;
getConnectionStatus(): SessionClientStatus;
readonly sessionId: SpecificSessionClientId;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/framework/presence/src/baseTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* Each client connection is given a unique identifier for the duration of the
* connection. If a client disconnects and reconnects, it will be given a new
* identifier. Prefer use of {@link ISessionClient} as a way to identify clients
* in a session. {@link ISessionClient.connectionId} will provide the current
* in a session. {@link ISessionClient.getConnectionId} will provide the current
* connection identifier for a logical session client.
*
* @privateRemarks
Expand Down
29 changes: 16 additions & 13 deletions packages/framework/presence/src/presence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type { ISubscribable } from "@fluid-experimental/presence/internal/events
* duration of the session. If a client disconnects and reconnects, it will
* retain its identifier. Prefer use of {@link ISessionClient} as a way to
* identify clients in a session. {@link ISessionClient.sessionId} will provide
* the session id.
* the session ID.
*
* @alpha
*/
Expand Down Expand Up @@ -56,8 +56,8 @@ export type SessionClientStatus =
*
* `ISessionClient` should be used as key to distinguish between different
* clients as they join, rejoin, and disconnect from a session. While a
* client's {@link ClientConnectionId} may change over time `ISessionClient`
* will be fixed.
* client's {@link ClientConnectionId} from {@link ISessionClient.getConnectionStatus}
* may change over time, `ISessionClient` will be fixed.
*
* @privateRemarks
* As this is evolved, pay attention to how this relates to Audience, Service
Expand All @@ -69,32 +69,35 @@ export type SessionClientStatus =
export interface ISessionClient<
SpecificSessionClientId extends ClientSessionId = ClientSessionId,
> {
/**
* The session ID of the client that is stable over all connections.
*/
readonly sessionId: SpecificSessionClientId;

/**
* Get current client connection id.
* Get current client connection ID.
*
* @returns Current client connection id.
* @returns Current client connection ID.
*
* @remarks
* Connection id will change on reconnect.
* Connection ID will change on reconnect.
*
* If {@link ISessionClient.getStatus} is {@link (SessionClientStatus:variable).Disconnected}, this will represent the last known connection id.
* If {@link ISessionClient.getConnectionStatus} is {@link (SessionClientStatus:variable).Disconnected}, this will represent the last known connection ID.
*/
connectionId(): ClientConnectionId;
getConnectionId(): ClientConnectionId;

/**
* Get status of session client.
* Get connection status of session client.
*
* @returns Status of session client.
* @returns Connection status of session client.
*
*/
getStatus(): SessionClientStatus;
getConnectionStatus(): SessionClientStatus;
}

/**
* Utility type limiting to a specific session client. (A session client with
* a specific session id - not just any session id.)
* a specific session ID - not just any session ID.)
*
* @internal
*/
Expand Down Expand Up @@ -162,7 +165,7 @@ export interface IPresence {
/**
* Lookup a specific attendee in the session.
*
* @param clientId - Client connection or session id
* @param clientId - Client connection or session ID
*/
getAttendee(clientId: ClientConnectionId | ClientSessionId): ISessionClient;

Expand Down
4 changes: 2 additions & 2 deletions packages/framework/presence/src/presenceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ export type PresenceExtensionInterface = Required<
*/
export interface ClientConnectionManager {
/**
* Remove the current client connection id from the corresponding disconnected attendee.
* Remove the current client connection ID from the corresponding disconnected attendee.
*
* @param clientConnectionId - The current client connection id to be removed.
* @param clientConnectionId - The current client connection ID to be removed.
*/
removeClientConnectionId(clientConnectionId: ClientConnectionId): void;
}
Expand Down
98 changes: 58 additions & 40 deletions packages/framework/presence/src/systemWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,49 @@ export interface SystemWorkspaceDatastore {
};
}

/**
* There is no implementation class for this interface.
* It is a simple structure. Most complicated aspect is that
* `connectionId()` member is replaced with a new
* function when a more recent connection is added.
*
* See {@link SystemWorkspaceImpl.ensureAttendee}.
*/
interface SessionClient extends ISessionClient {
class SessionClient implements ISessionClient {
/**
* Order is used to track the most recent client connection
* during a session.
*/
order: number;
public order: number = 0;

private connectionStatus: SessionClientStatus;

public constructor(
public readonly sessionId: ClientSessionId,
private connectionId: ClientConnectionId | undefined = undefined,
) {
this.connectionStatus =
connectionId === undefined
? SessionClientStatus.Disconnected
: SessionClientStatus.Connected;
}

public getConnectionId(): ClientConnectionId {
if (this.connectionId === undefined) {
throw new Error("Client has never been connected");
}
return this.connectionId;
}

public getConnectionStatus(): SessionClientStatus {
return this.connectionStatus;
}

public setConnectionId(
connectionId: ClientConnectionId,
updateStatus: boolean = true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When might you set updateStatus to false?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We think the management of connectivity is funky at the moment. I expect this will change when Task 22616: "Address ISessionClient connectivity consistency" is done. I hope this parameter just goes away. We'll see. I am using it now to preserve the logic as-is.

): void {
this.connectionId = connectionId;
if (updateStatus) {
this.connectionStatus = SessionClientStatus.Connected;
}
}

public setDisconnected(): void {
this.connectionStatus = SessionClientStatus.Disconnected;
}
}

/**
Expand All @@ -56,14 +85,14 @@ export interface SystemWorkspace
/**
* Must be called when the current client acquires a new connection.
*
* @param clientConnectionId - The new client connection id.
* @param clientConnectionId - The new client connection ID.
*/
onConnectionAdded(clientConnectionId: ClientConnectionId): void;

/**
* Removes the client connection id from the system workspace.
* Removes the client connection ID from the system workspace.
*
* @param clientConnectionId - The client connection id to remove.
* @param clientConnectionId - The client connection ID to remove.
*/
removeClientConnectionId(clientConnectionId: ClientConnectionId): void;
}
Expand All @@ -75,7 +104,7 @@ class SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace {
* session. The map covers entries for both session ids and connection
* ids, which are never expected to collide, but if they did for same
* client that would be fine.
* An entry is for session id if the value's `sessionId` matches the key.
* An entry is for session ID if the value's `sessionId` matches the key.
*/
private readonly attendees = new Map<ClientConnectionId | ClientSessionId, SessionClient>();

Expand All @@ -86,14 +115,7 @@ class SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace {
Pick<PresenceEvents, "attendeeJoined" | "attendeeDisconnected">
>,
) {
this.selfAttendee = {
sessionId: clientSessionId,
order: 0,
connectionId: () => {
throw new Error("Client has never been connected");
},
getStatus: () => SessionClientStatus.Disconnected,
};
this.selfAttendee = new SessionClient(clientSessionId);
this.attendees.set(clientSessionId, this.selfAttendee);
}

Expand Down Expand Up @@ -150,8 +172,7 @@ class SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace {
value: this.selfAttendee.sessionId,
};

this.selfAttendee.connectionId = () => clientConnectionId;
this.selfAttendee.getStatus = () => SessionClientStatus.Connected;
this.selfAttendee.setConnectionId(clientConnectionId);
this.attendees.set(clientConnectionId, this.selfAttendee);
}

Expand All @@ -161,11 +182,11 @@ class SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace {
return;
}

// If the last known connectionID is different from the connection id being removed, the attendee has reconnected,
// If the last known connectionID is different from the connection ID being removed, the attendee has reconnected,
// therefore we should not change the attendee connection status or emit a disconnect event.
const attendeeReconnected = attendee.connectionId() !== clientConnectionId;
const attendeeReconnected = attendee.getConnectionId() !== clientConnectionId;
if (!attendeeReconnected) {
attendee.getStatus = () => SessionClientStatus.Disconnected;
attendee.setDisconnected();
this.events.emit("attendeeDisconnected", attendee);
}
}
Expand All @@ -192,36 +213,33 @@ class SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace {
}

/**
* Make sure the given client session and connection id pair are represented
* Make sure the given client session and connection ID pair are represented
* in the attendee map. If not present, SessionClient is created and added
* to map. If present, make sure the current connection id is updated.
* to map. If present, make sure the current connection ID is updated.
*/
private ensureAttendee(
clientSessionId: ClientSessionId,
clientConnectionId: ClientConnectionId,
order: number,
): { attendee: SessionClient; isNew: boolean } {
const connectionId = (): ClientConnectionId => clientConnectionId;
let attendee = this.attendees.get(clientSessionId);
let isNew = false;
// TODO #22616: Check for a current connection to determine best status.
// For now, always leave existing state as was last determined and
// assume new client is connected.
if (attendee === undefined) {
// New attendee. Create SessionClient and add session id based
// New attendee. Create SessionClient and add session ID based
// entry to map.
attendee = {
sessionId: clientSessionId,
order,
connectionId,
getStatus: () => SessionClientStatus.Connected,
};
attendee = new SessionClient(clientSessionId, clientConnectionId);
this.attendees.set(clientSessionId, attendee);
isNew = true;
} else if (order > attendee.order) {
// The given association is newer than the one we have.
// Update the order and current connection id.
// Update the order and current connection ID.
attendee.order = order;
attendee.connectionId = connectionId;
attendee.setConnectionId(clientConnectionId, /* updateStatus */ false);
}
// Always update entry for the connection id. (Okay if already set.)
// Always update entry for the connection ID. (Okay if already set.)
this.attendees.set(clientConnectionId, attendee);
return { attendee, isNew };
}
Expand Down
8 changes: 4 additions & 4 deletions packages/framework/presence/src/test/presenceManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ describe("Presence", () => {
"Attendee has wrong session id",
);
assert.equal(
newAttendee.connectionId(),
newAttendee.getConnectionId(),
initialAttendeeConnectionId,
"Attendee has wrong client connection id",
);
Expand Down Expand Up @@ -163,7 +163,7 @@ describe("Presence", () => {
"Disconnected attendee has wrong session id",
);
assert.equal(
disconnectedAttendee.connectionId(),
disconnectedAttendee.getConnectionId(),
initialAttendeeConnectionId,
"Disconnected attendee has wrong client connection id",
);
Expand All @@ -176,7 +176,7 @@ describe("Presence", () => {
"No attendee was disconnected in beforeEach",
);
assert.equal(
disconnectedAttendee.getStatus(),
disconnectedAttendee.getConnectionStatus(),
SessionClientStatus.Disconnected,
"Disconnected attendee has wrong status",
);
Expand Down Expand Up @@ -251,7 +251,7 @@ describe("Presence", () => {
);
// Current connection id is updated
assert(
newAttendee.connectionId() === updatedClientConnectionId,
newAttendee.getConnectionId() === updatedClientConnectionId,
"Attendee does not have updated client connection id",
);
// Attendee is available via new connection id
Expand Down
Loading