From 12132cabf7baf8b244181dd2e801605fe881c0e3 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Thu, 21 Nov 2024 18:47:55 +0000 Subject: [PATCH] Implement TM2p,k,o (new mutable-message fields) Tests for TM2p pending serverside implementation of field name changes being deployed --- ably.d.ts | 22 +++++++++++------- src/common/lib/client/realtimechannel.ts | 15 ++++++++++-- src/common/lib/types/message.ts | 29 ++++++++++++++++++------ test/realtime/message.test.js | 19 ++++++++++++++++ 4 files changed, 68 insertions(+), 17 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index be483f1c5..228f90b78 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2332,6 +2332,7 @@ export interface Message { name?: string; /** * Timestamp of when the message was received by Ably, as milliseconds since the Unix epoch. + * (This is the timestamp of the current version of the message) */ timestamp?: number; /** @@ -2339,27 +2340,32 @@ export interface Message { */ action?: MessageAction; /** - * This message's unique serial. + * This message's unique serial (an identifier that will be the same in all future + * updates of this message). */ serial?: string; /** - * The serial of the message that this message is a reference to. + * If this message references another, the serial of that message. */ refSerial?: string; /** - * The type of reference this message is, in relation to the message it references. + * If this message references another, the type of reference that is. */ refType?: string; /** - * If an `update` operation was applied to this message, this will be the timestamp the update occurred. + * The timestamp of the very first version of a given message (will differ from + * createdAt only if the message has been updated or deleted). */ - updatedAt?: number; + createdAt?: number; /** - * The serial of the operation that updated this message. + * The version of the message, lexicographically-comparable with other versions (that + * share the same serial) Will differ from the serial only if the message has been + * updated or deleted. */ - updateSerial?: string; + version?: string; /** - * If this message resulted from an operation, this will contain the operation details. + * In the case of an updated or deleted message, this will contain metadata about the + * update or delete operation. */ operation?: Operation; } diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 4929295ec..89e807033 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -603,7 +603,8 @@ class RealtimeChannel extends EventEmitter { const messages = message.messages as Array, firstMessage = messages[0], - lastMessage = messages[messages.length - 1]; + lastMessage = messages[messages.length - 1], + channelSerial = message.channelSerial; if ( firstMessage.extras && @@ -652,6 +653,16 @@ class RealtimeChannel extends EventEmitter { return; } + for (let i = 0; i < messages.length; i++) { + const msg = messages[i]; + if (channelSerial && !msg.version) { + msg.version = channelSerial + ':' + i.toString().padStart(3, '0'); + // already done in fromWireProtocol -- but for realtime messages the source + // fields might be copied from the protocolmessage, so need to do it again + msg.expandFields(); + } + } + this._lastPayload.messageId = lastMessage.id; this._lastPayload.protocolMessageChannelSerial = message.channelSerial; this.onEvent(messages); @@ -720,7 +731,7 @@ class RealtimeChannel extends EventEmitter { if (!msg.connectionId) msg.connectionId = connectionId; if (!msg.timestamp) msg.timestamp = timestamp; - if (!msg.id) msg.id = id + ':' + i; + if (id && !msg.id) msg.id = id + ':' + i; } return { unrecoverableError: false }; diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index f5c406e63..d0ac41d62 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -304,7 +304,9 @@ export function fromValues(values: Properties): Message { export function fromWireProtocol(values: WireProtocolMessage): Message { const action = toMessageActionString(values.action as number) || values.action; - return Object.assign(new Message(), { ...values, action }); + const res = Object.assign(new Message(), { ...values, action }); + res.expandFields(); + return res; } export function fromValuesArray(values: Properties[]): Message[] { @@ -338,8 +340,8 @@ class Message { serial?: string; refSerial?: string; refType?: string; - updatedAt?: number; - updateSerial?: string; + createdAt?: number; + version?: string; operation?: API.Operation; /** @@ -375,14 +377,27 @@ class Message { action: toMessageActionNumber(this.action as API.MessageAction) || this.action, refSerial: this.refSerial, refType: this.refType, - updatedAt: this.updatedAt, - updateSerial: this.updateSerial, + createdAt: this.createdAt, + version: this.version, operation: this.operation, encoding, data, }; } + expandFields() { + if (this.action === 'message.create') { + // TM2k + if (this.version && !this.serial) { + this.serial = this.version; + } + // TM2o + if (this.timestamp && !this.createdAt) { + this.createdAt = this.timestamp; + } + } + } + toString(): string { let result = '[Message'; if (this.name) result += '; name=' + this.name; @@ -402,10 +417,10 @@ class Message { if (this.action) result += '; action=' + this.action; if (this.serial) result += '; serial=' + this.serial; + if (this.version) result += '; version=' + this.version; if (this.refSerial) result += '; refSerial=' + this.refSerial; if (this.refType) result += '; refType=' + this.refType; - if (this.updatedAt) result += '; updatedAt=' + this.updatedAt; - if (this.updateSerial) result += '; updateSerial=' + this.updateSerial; + if (this.createdAt) result += '; createdAt=' + this.createdAt; if (this.operation) result += '; operation=' + JSON.stringify(this.operation); result += ']'; return result; diff --git a/test/realtime/message.test.js b/test/realtime/message.test.js index 1490bdd7b..9f54cce7d 100644 --- a/test/realtime/message.test.js +++ b/test/realtime/message.test.js @@ -1310,6 +1310,25 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async expect(message.toJSON()).to.deep.contains(expectedJSON); }); }); + + /** + * @spec TM2k + * @spec TM2o + */ + it('create message should fill out serial and createdAt from version/timestamp', function () { + const values = { action: 1, timestamp: 12345, version: 'foo' }; + const message = Message.fromWireProtocol(values); + expect(message.timestamp).to.equal(12345); + expect(message.createdAt).to.equal(12345); + expect(message.version).to.equal('foo'); + expect(message.serial).to.equal('foo'); + + // should only apply to creates + const update = { action: 2, timestamp: 12345, version: 'foo' }; + const updateMessage = Message.fromWireProtocol(update); + expect(updateMessage.createdAt).to.equal(undefined); + expect(updateMessage.serial).to.equal(undefined); + }); }); /**