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

Support presence message extras #1418

Merged
merged 2 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions ably.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2454,7 +2454,7 @@ declare namespace Types {
/**
* Enters the presence set for the channel, passing a `data` payload. A `clientId` is required to be present on a channel.
*
* @param data - The payload associated with the presence member.
* @param data - The data payload or {@link PresenceMessage} associated with the presence member.
* @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
*/
enter(data?: any, callback?: errorCallback): void;
Expand All @@ -2467,14 +2467,14 @@ declare namespace Types {
/**
* Updates the `data` payload for a presence member. If called before entering the presence set, this is treated as an {@link PresenceAction.ENTER} event.
*
* @param data - The payload to update for the presence member.
* @param data - The data payload or {@link PresenceMessage} object to update for the presence member.
* @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
*/
update(data?: any, callback?: errorCallback): void;
/**
* Leaves the presence set for the channel. A client must have previously entered the presence set before they can leave it.
*
* @param data - The payload associated with the presence member.
* @param data - The data payload or {@link PresenceMessage} associated with the presence member.
* @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
*/
leave(data?: any, callback?: errorCallback): void;
Expand All @@ -2488,7 +2488,7 @@ declare namespace Types {
* Enters the presence set of the channel for a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`.
*
* @param clientId - The ID of the client to enter into the presence set.
* @param data - The payload associated with the presence member.
* @param data - The data payload or {@link PresenceMessage} associated with the presence member.
* @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
*/
enterClient(clientId: string, data?: any, callback?: errorCallback): void;
Expand All @@ -2503,15 +2503,15 @@ declare namespace Types {
* Updates the `data` payload for a presence member using a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`.
*
* @param clientId - The ID of the client to update in the presence set.
* @param data - The payload to update for the presence member.
* @param data - The data payload or {@link PresenceMessage} object to update for the presence member.
* @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
*/
updateClient(clientId: string, data?: any, callback?: errorCallback): void;
/**
* Leaves the presence set of the channel for a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`.
*
* @param clientId - The ID of the client to leave the presence set for.
* @param data - The payload associated with the presence member.
* @param data - The data payload or {@link PresenceMessage} associated with the presence member.
* @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
*/
leaveClient(clientId: string, data?: any, callback?: errorCallback): void;
Expand Down Expand Up @@ -3208,6 +3208,10 @@ declare namespace Types {
* This will typically be empty as all presence messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute will contain the remaining transformations not applied to the data payload.
*/
encoding: string;
/**
* A JSON object of arbitrary key-value pairs that may contain metadata, and/or ancillary payloads. Valid payloads include `headers`.
*/
extras: any;
/**
* A unique ID assigned to each `PresenceMessage` by Ably.
*/
Expand Down Expand Up @@ -3236,6 +3240,14 @@ declare namespace Types {
* @param channelOptions - A {@link ChannelOptions} object containing the cipher.
*/
fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => PresenceMessage[];

/**
* Initialises a `PresenceMessage` from a `PresenceMessage`-like object.
*
* @param values - The values to intialise the `PresenceMessage` from.
* @param stringifyAction - Whether to convert the `action` field from a number to a string.
Copy link
Member

Choose a reason for hiding this comment

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

We should not be exposing things like a stringifyAction param in the public api. There is no reason a user of the lib should have to know or care that we use numerical encoding in our internal wire protocol, and this doc string is meaningless to such a user.

I'm not a fan of having the one method to do conceptually quite different actions controlled by that param anyway, so in #1923 I've split it into two different methods, fromValues(values: Properties<PresenceMessage>): Message suitable for public use, and fromWireProtocol(values: WireProtocolPresenceMessage): PresenceMessage as an internal method.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I didn't introduce the argument, I just needed the function to be included here to be able to call it from Spaces, I paid very little attention to whether or not the argument is suitable, what you suggest sounds good to me 👍

*/
fromValues(values: PresenceMessage | Record<string, unknown>, stringifyAction?: boolean): PresenceMessage;
}

/**
Expand Down
6 changes: 2 additions & 4 deletions src/common/lib/client/realtimepresence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,8 @@ class RealtimePresence extends Presence {
'channel = ' + channel.name + ', id = ' + id + ', client = ' + (clientId || '(implicit) ' + getClientId(this))
);

const presence = PresenceMessage.fromValues({
action: action,
data: data,
});
const presence = PresenceMessage.fromData(data);
presence.action = action;
if (id) {
presence.id = id;
}
Expand Down
15 changes: 15 additions & 0 deletions src/common/lib/types/presencemessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class PresenceMessage {
connectionId?: string;
data?: string | Buffer | Uint8Array;
encoding?: string;
extras?: any;
size?: number;

static Actions = ['absent', 'present', 'enter', 'leave', 'update'];
Expand Down Expand Up @@ -53,6 +54,7 @@ class PresenceMessage {
action: number;
data: string | Buffer | Uint8Array;
encoding?: string;
extras?: any;
} {
/* encode data to base64 if present and we're returning real JSON;
* although msgpack calls toJSON(), we know it is a stringify()
Expand All @@ -78,6 +80,7 @@ class PresenceMessage {
action: toActionValue(this.action as string),
data: data,
encoding: encoding,
extras: this.extras,
};
}

Expand All @@ -95,6 +98,9 @@ class PresenceMessage {
result += '; data (buffer)=' + Platform.BufferUtils.base64Encode(this.data);
else result += '; data (json)=' + JSON.stringify(this.data);
}
if (this.extras) {
result += '; extras=' + JSON.stringify(this.extras);
}
result += ']';
return result;
}
Expand Down Expand Up @@ -155,6 +161,15 @@ class PresenceMessage {
});
}

static fromData(data: unknown): PresenceMessage {
if (data instanceof PresenceMessage) {
return data;
}
return PresenceMessage.fromValues({
data,
});
}

static getMessagesSize = Message.getMessagesSize;
}

Expand Down
38 changes: 38 additions & 0 deletions test/realtime/presence.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async
var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized;
var closeAndFinish = helper.closeAndFinish;
var monitorConnection = helper.monitorConnection;
var PresenceMessage = Ably.Realtime.PresenceMessage;

function extractClientIds(presenceSet) {
return utils
Expand Down Expand Up @@ -370,6 +371,43 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async
monitorConnection(done, clientRealtime);
});

/*
* Attach to channel, enter presence channel with extras and check received
* PresenceMessage has extras.
*/
it('presenceMessageExtras', function (done) {
var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken });
var channelName = 'presenceMessageExtras';
var clientChannel = clientRealtime.channels.get(channelName);
var presence = clientChannel.presence;
presence.subscribe(
function (presenceMessage) {
try {
expect(presenceMessage.extras).to.deep.equal(
{ headers: { key: 'value' } },
'extras should have headers "key=value"'
);
} catch (err) {
closeAndFinish(done, clientRealtime, err);
return;
}
closeAndFinish(done, clientRealtime);
},
function onPresenceSubscribe(err) {
if (err) {
closeAndFinish(done, clientRealtime, err);
return;
}
clientChannel.presence.enter(
PresenceMessage.fromValues({
extras: { headers: { key: 'value' } },
})
);
}
);
monitorConnection(done, clientRealtime);
});

/*
* Enter presence channel (without attaching), detach, then enter again to reattach
*/
Expand Down