-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmessage.ts
291 lines (247 loc) · 8.98 KB
/
message.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
import { ErrorInfo } from 'ably';
import { ChatMessageActions } from './events.js';
import { Headers } from './headers.js';
import { Metadata } from './metadata.js';
import { OperationMetadata } from './operation-metadata.js';
/**
* {@link Headers} type for chat messages.
*/
export type MessageHeaders = Headers;
/**
* {@link Metadata} type for chat messages.
*/
export type MessageMetadata = Metadata;
/**
* {@link OperationMetadata} type for a chat messages {@link Operation}.
*/
export type MessageOperationMetadata = OperationMetadata;
/**
* Represents the detail of a message deletion or update.
*/
export interface Operation {
/**
* The optional clientId of the user who performed the update or deletion.
*/
clientId?: string;
/**
* The optional description for the update or deletion.
*/
description?: string;
/**
* The optional metadata associated with the update or deletion.
*/
metadata?: MessageOperationMetadata;
}
/**
* Represents a single message in a chat room.
*/
export interface Message {
/**
* The unique identifier of the message.
*/
readonly serial: string;
/**
* The clientId of the user who created the message.
*/
readonly clientId: string;
/**
* The roomId of the chat room to which the message belongs.
*/
readonly roomId: string;
/**
* The text of the message.
*/
readonly text: string;
/**
* The timestamp at which the message was created.
*/
readonly createdAt: Date;
/**
* The metadata of a chat message. Allows for attaching extra info to a message,
* which can be used for various features such as animations, effects, or simply
* to link it to other resources such as images, relative points in time, etc.
*
* Metadata is part of the Ably Pub/sub message content and is not read by Ably.
*
* This value is always set. If there is no metadata, this is an empty object.
*
* Do not use metadata for authoritative information. There is no server-side
* validation. When reading the metadata treat it like user input.
*/
readonly metadata: MessageMetadata;
/**
* The headers of a chat message. Headers enable attaching extra info to a message,
* which can be used for various features such as linking to a relative point in
* time of a livestream video or flagging this message as important or pinned.
*
* Headers are part of the Ably realtime message extras.headers and they can be used
* for Filtered Subscriptions and similar.
*
* This value is always set. If there are no headers, this is an empty object.
*
* Do not use the headers for authoritative information. There is no server-side
* validation. When reading the headers treat them like user input.
*/
readonly headers: MessageHeaders;
/**
* The action type of the message. This can be used to determine if the message was created, updated, or deleted.
*/
readonly action: ChatMessageActions;
/**
* A unique identifier for the latest version of this message.
*/
readonly version: string;
/**
* The timestamp at which this version was updated, deleted, or created.
*/
readonly timestamp: Date;
/**
* The details of the operation that updated the message. This is only set for update and delete actions. It contains
* information about the operation: the clientId of the user who performed the operation, a description, and metadata.
*/
readonly operation?: Operation;
/**
* Indicates if the message has been updated.
*/
get isUpdated(): boolean;
/**
* Indicates if the message has been deleted.
*/
get isDeleted(): boolean;
/**
* The clientId of the user who deleted the message.
*/
get deletedBy(): string | undefined;
/**
* The clientId of the user who updated the message.
*/
get updatedBy(): string | undefined;
/**
* The timestamp at which the message was deleted.
*/
get deletedAt(): Date | undefined;
/**
* The timestamp at which the message was updated.
*/
get updatedAt(): Date | undefined;
/**
* Determines if the version of this message is older than the version of the given message.
* @param message The message to compare against.
* @returns true if the version of this message is before the given message.
* @throws {@link ErrorInfo} if both message serials do not match.
*/
versionBefore(message: Message): boolean;
/**
* Determines if the version of this message is newer than the version of the given message.
* @param message The message to compare against.
* @returns true if the version of this message is after the given message.
* @throws {@link ErrorInfo} if both message serials do not match.
*/
versionAfter(message: Message): boolean;
/**
* Determines if the version of this message is the same as to the version of the given message.
* @param message The message to compare against.
* @returns true if the version of this message is equal to the given message.
* @throws {@link ErrorInfo} if both message serials do not match.
*/
versionEqual(message: Message): boolean;
/**
* Determines if this message was created before the given message. This comparison is based on
* global order, so does not necessarily represent the order that messages are received in realtime
* from the backend.
* @param message The message to compare against.
* @returns true if this message was created before the given message, in global order.
* @throws {@link ErrorInfo} if serials of either message is invalid.
*/
before(message: Message): boolean;
/**
* Determines if this message was created after the given message. This comparison is based on
* global order, so does not necessarily represent the order that messages are received in realtime
* from the backend.
* @param message The message to compare against.
* @returns true if this message was created after the given message, in global order.
* @throws {@link ErrorInfo} if serials of either message is invalid.
*/
after(message: Message): boolean;
/**
* Determines if this message is equal to the given message.
*
* Note that this method compares messages based on {@link Message.serial} alone. It returns true if the
* two messages represent different versions of the same message.
* @param message The message to compare against.
* @returns true if this message is equal to the given message.
* @throws {@link ErrorInfo} if serials of either message is invalid.
*/
equal(message: Message): boolean;
}
/**
* An implementation of the Message interface for chat messages.
*
* Allows for comparison of messages based on their serials.
*/
export class DefaultMessage implements Message {
constructor(
public readonly serial: string,
public readonly clientId: string,
public readonly roomId: string,
public readonly text: string,
public readonly metadata: MessageMetadata,
public readonly headers: MessageHeaders,
public readonly action: ChatMessageActions,
public readonly version: string,
public readonly createdAt: Date,
public readonly timestamp: Date,
public readonly operation?: Operation,
) {
// The object is frozen after constructing to enforce readonly at runtime too
Object.freeze(this);
}
get isUpdated(): boolean {
return this.action === ChatMessageActions.MessageUpdate;
}
get isDeleted(): boolean {
return this.action === ChatMessageActions.MessageDelete;
}
get updatedBy(): string | undefined {
return this.isUpdated ? this.operation?.clientId : undefined;
}
get deletedBy(): string | undefined {
return this.isDeleted ? this.operation?.clientId : undefined;
}
get updatedAt(): Date | undefined {
return this.isUpdated ? this.timestamp : undefined;
}
get deletedAt(): Date | undefined {
return this.isDeleted ? this.timestamp : undefined;
}
versionBefore(message: Message): boolean {
// Check to ensure the messages are the same before comparing operation order
if (!this.equal(message)) {
throw new ErrorInfo('versionBefore(): Cannot compare versions, message serials must be equal', 50000, 500);
}
return this.version < message.version;
}
versionAfter(message: Message): boolean {
// Check to ensure the messages are the same before comparing operation order
if (!this.equal(message)) {
throw new ErrorInfo('versionAfter(): Cannot compare versions, message serials must be equal', 50000, 500);
}
return this.version > message.version;
}
versionEqual(message: Message): boolean {
// Check to ensure the messages are the same before comparing operation order
if (!this.equal(message)) {
throw new ErrorInfo('versionEqual(): Cannot compare versions, message serials must be equal', 50000, 500);
}
return this.version === message.version;
}
before(message: Message): boolean {
return this.serial < message.serial;
}
after(message: Message): boolean {
return this.serial > message.serial;
}
equal(message: Message): boolean {
return this.serial === message.serial;
}
}