Skip to content

Commit 1b96056

Browse files
authored
Support sending sticky events (MSC4354) (#152)
* Add initial implementation * Prettier * lint * remove state key * fix tests * Add unstable sticky
1 parent 556f19f commit 1b96056

File tree

9 files changed

+344
-9
lines changed

9 files changed

+344
-9
lines changed

src/ClientWidgetApi.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,14 @@ export class ClientWidgetApi extends EventEmitter {
601601
const isDelayedEvent = request.data.delay !== undefined || request.data.parent_delay_id !== undefined;
602602
if (isDelayedEvent && !this.hasCapability(MatrixCapabilities.MSC4157SendDelayedEvent)) {
603603
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
604-
error: { message: "Missing capability" },
604+
error: { message: `Missing capability for ${MatrixCapabilities.MSC4157SendDelayedEvent}` },
605+
});
606+
}
607+
608+
const isStickyEvent = request.data.sticky_duration_ms !== undefined;
609+
if (isStickyEvent && !this.hasCapability(MatrixCapabilities.MSC4354SendStickyEvent)) {
610+
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
611+
error: { message: `Missing capability for ${MatrixCapabilities.MSC4354SendStickyEvent}` },
605612
});
606613
}
607614

@@ -612,6 +619,11 @@ export class ClientWidgetApi extends EventEmitter {
612619
error: { message: "Cannot send state events of this type" },
613620
});
614621
}
622+
if (isStickyEvent) {
623+
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
624+
error: { message: "Cannot send a state event with a sticky duration" },
625+
});
626+
}
615627

616628
if (isDelayedEvent) {
617629
sendEventPromise = this.driver.sendDelayedEvent(
@@ -639,22 +651,41 @@ export class ClientWidgetApi extends EventEmitter {
639651
});
640652
}
641653

642-
if (isDelayedEvent) {
643-
sendEventPromise = this.driver.sendDelayedEvent(
654+
// Events can be sticky, delayed, both, or neither. The following
655+
// section of code takes the common parameters and uses the correct
656+
// function depending on the request type.
657+
658+
const params: Parameters<WidgetDriver["sendEvent"]> = [
659+
request.data.type,
660+
content,
661+
null, // not sending a state event
662+
request.data.room_id,
663+
];
664+
665+
if (isDelayedEvent && request.data.sticky_duration_ms) {
666+
sendEventPromise = this.driver.sendDelayedStickyEvent(
644667
request.data.delay ?? null,
645668
request.data.parent_delay_id ?? null,
669+
request.data.sticky_duration_ms,
646670
request.data.type,
647671
content,
648-
null, // not sending a state event
649672
request.data.room_id,
650673
);
651-
} else {
652-
sendEventPromise = this.driver.sendEvent(
674+
} else if (isDelayedEvent) {
675+
sendEventPromise = this.driver.sendDelayedEvent(
676+
request.data.delay ?? null,
677+
request.data.parent_delay_id ?? null,
678+
...params,
679+
);
680+
} else if (request.data.sticky_duration_ms) {
681+
sendEventPromise = this.driver.sendStickyEvent(
682+
request.data.sticky_duration_ms,
653683
request.data.type,
654684
content,
655-
null, // not sending a state event
656685
request.data.room_id,
657686
);
687+
} else {
688+
sendEventPromise = this.driver.sendEvent(...params);
658689
}
659690
}
660691

src/WidgetApi.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,9 @@ export class WidgetApi extends EventEmitter {
441441
roomId?: string,
442442
delay?: number,
443443
parentDelayId?: string,
444+
stickyDurationMs?: number,
444445
): Promise<ISendEventFromWidgetResponseData> {
445-
return this.sendEvent(eventType, undefined, content, roomId, delay, parentDelayId);
446+
return this.sendEvent(eventType, undefined, content, roomId, delay, parentDelayId, stickyDurationMs);
446447
}
447448

448449
public sendStateEvent(
@@ -463,6 +464,7 @@ export class WidgetApi extends EventEmitter {
463464
roomId?: string,
464465
delay?: number,
465466
parentDelayId?: string,
467+
stickyDurationMs?: number,
466468
): Promise<ISendEventFromWidgetResponseData> {
467469
return this.transport.send<ISendEventFromWidgetRequestData, ISendEventFromWidgetResponseData>(
468470
WidgetApiFromWidgetAction.SendEvent,
@@ -473,6 +475,7 @@ export class WidgetApi extends EventEmitter {
473475
...(roomId !== undefined && { room_id: roomId }),
474476
...(delay !== undefined && { delay }),
475477
...(parentDelayId !== undefined && { parent_delay_id: parentDelayId }),
478+
...(stickyDurationMs !== undefined && { sticky_duration_ms: stickyDurationMs }),
476479
},
477480
);
478481
}

src/driver/WidgetDriver.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,29 @@ export abstract class WidgetDriver {
109109
return Promise.reject(new Error("Failed to override function"));
110110
}
111111

112+
/**
113+
* @experimental Part of MSC4354
114+
* Sends a sticky event into a room. If `roomId` is falsy, the client should send the event
115+
* into the room the user is currently looking at. The widget API will have already
116+
* verified that the widget is capable of sending the event to that room.
117+
* @param {number} stickyDurationMs The length of time a sticky event may remain sticky, in milliseconds.
118+
* @param {string} eventType The event type to be sent.
119+
* @param {*} content The content for the event.
120+
* @param {string|null} roomId The room ID to send the event to. If falsy, the room the
121+
* user is currently looking at.
122+
* @returns {Promise<ISendEventDetails>} Resolves when the event has been sent with
123+
* details of that event.
124+
* @throws Rejected when the event could not be sent.
125+
*/
126+
public sendStickyEvent(
127+
stickyDurationMs: number,
128+
eventType: string,
129+
content: unknown,
130+
roomId: string | null = null,
131+
): Promise<ISendEventDetails> {
132+
throw new Error("Method not implemented.");
133+
}
134+
112135
/**
113136
* @experimental Part of MSC4140 & MSC4157
114137
* Sends a delayed event into a room. If `roomId` is falsy, the client should send it
@@ -139,6 +162,35 @@ export abstract class WidgetDriver {
139162
return Promise.reject(new Error("Failed to override function"));
140163
}
141164

165+
/**
166+
* @experimental Part of MSC4140, MSC4157 and MSC4354
167+
* Sends a delayed sticky event into a room. If `roomId` is falsy, the client should send the event
168+
* into the room the user is currently looking at. The widget API will have already
169+
* verified that the widget is capable of sending the event to that room.
170+
* @param {number} stickyDurationMs The length of time a sticky event may remain sticky, in milliseconds.
171+
* @param {number|null} delay How much later to send the event, or null to not send the
172+
* event automatically. May not be null if {@link parentDelayId} is null.
173+
* @param {string|null} parentDelayId The ID of the delayed event this one is grouped with,
174+
* or null if it will be put in a new group. May not be null if {@link delay} is null.
175+
* @param {string} eventType The event type to be sent.
176+
* @param {*} content The content for the event.
177+
* @param {string|null} roomId The room ID to send the event to. If falsy, the room the
178+
* user is currently looking at.
179+
* @returns {Promise<ISendDelayedEventDetails>} Resolves when the event has been sent with
180+
* details of that event.
181+
* @throws Rejected when the event could not be sent.
182+
*/
183+
public sendDelayedStickyEvent(
184+
delay: number | null,
185+
parentDelayId: string | null,
186+
stickyDurationMs: number,
187+
eventType: string,
188+
content: unknown,
189+
roomId: string | null = null,
190+
): Promise<ISendDelayedEventDetails> {
191+
throw new Error("Method not implemented.");
192+
}
193+
142194
/**
143195
* @experimental Part of MSC4140 & MSC4157
144196
* Cancel the scheduled delivery of the delayed event matching the provided {@link delayId}.

src/interfaces/Capabilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ export enum MatrixCapabilities {
5353
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
5454
*/
5555
MSC4157UpdateDelayedEvent = "org.matrix.msc4157.update_delayed_event",
56+
/**
57+
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
58+
*/
59+
MSC4354SendStickyEvent = "org.matrix.msc4354.send_sticky_event",
5660
}
5761

5862
export type Capability = MatrixCapabilities | string;

src/interfaces/IRoomEvent.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,11 @@ export interface IRoomEvent {
2323
origin_server_ts: number; // eslint-disable-line camelcase
2424
content: unknown;
2525
unsigned: unknown;
26+
//MSC4354
27+
sticky?: {
28+
duration_ms: number;
29+
};
30+
msc4354_sticky?: {
31+
duration_ms: number;
32+
};
2633
}

src/interfaces/SendEventAction.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export interface ISendEventFromWidgetRequestData extends IWidgetApiRequestData {
2828
// MSC4157
2929
delay?: number; // eslint-disable-line camelcase
3030
parent_delay_id?: string; // eslint-disable-line camelcase
31+
32+
// MSC4354SendStickyEvent
33+
sticky_duration_ms?: number;
3134
}
3235

3336
export interface ISendEventFromWidgetActionRequest extends IWidgetApiRequest {

src/interfaces/WidgetApiAction.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ export enum WidgetApiFromWidgetAction {
9292
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
9393
*/
9494
MSC4157UpdateDelayedEvent = "org.matrix.msc4157.update_delayed_event",
95+
96+
/**
97+
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
98+
*/
99+
MSC4354SendStickyEvent = "org.matrix.msc4354.send_sticky_event",
95100
}
96101

97102
export type WidgetApiAction = WidgetApiToWidgetAction | WidgetApiFromWidgetAction | string;

0 commit comments

Comments
 (0)