You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
Poll model - validate end events (#3072)
* first cut poll model * process incoming poll relations * allow alt event types in relations model * allow alt event types in relations model * remove unneccesary checks on remove relation * comment * Revert "allow alt event types in relations model" This reverts commite578d84464
. * Revert "Revert "allow alt event types in relations model"" This reverts commit515db7a8bc
. * basic handling for new poll relations * tests * test room.processPollEvents * join processBeaconEvents and poll events in client * tidy and set 23 copyrights * use rooms instance of matrixClient * tidy * more copyright * simplify processPollEvent code * throw when poll start event has no roomId * updates for events-sdk move * more type changes for events-sdk changes * validate poll end event senders * reformatted copyright * undo more comment reformatting * fix poll end validation logic to allow poll creator to end poll regardless of redaction * Update src/models/poll.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * correct creator == sender validationin poll end --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
@@ -60,6 +60,7 @@ export const getMockClientWithEventEmitter = (
|
|||||||
*/
|
*/
|
||||||
export const mockClientMethodsUser = (userId = "@alice:domain") => ({
|
export const mockClientMethodsUser = (userId = "@alice:domain") => ({
|
||||||
getUserId: jest.fn().mockReturnValue(userId),
|
getUserId: jest.fn().mockReturnValue(userId),
|
||||||
|
getSafeUserId: jest.fn().mockReturnValue(userId),
|
||||||
getUser: jest.fn().mockReturnValue(new User(userId)),
|
getUser: jest.fn().mockReturnValue(new User(userId)),
|
||||||
isGuest: jest.fn().mockReturnValue(false),
|
isGuest: jest.fn().mockReturnValue(false),
|
||||||
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
|
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
|
||||||
|
@@ -14,26 +14,31 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IEvent, MatrixEvent, PollEvent } from "../../../src";
|
import { IEvent, MatrixEvent, PollEvent, Room } from "../../../src";
|
||||||
import { REFERENCE_RELATION } from "../../../src/@types/extensible_events";
|
import { REFERENCE_RELATION } from "../../../src/@types/extensible_events";
|
||||||
import { M_POLL_END, M_POLL_KIND_DISCLOSED, M_POLL_RESPONSE } from "../../../src/@types/polls";
|
import { M_POLL_END, M_POLL_KIND_DISCLOSED, M_POLL_RESPONSE } from "../../../src/@types/polls";
|
||||||
import { PollStartEvent } from "../../../src/extensible_events_v1/PollStartEvent";
|
import { PollStartEvent } from "../../../src/extensible_events_v1/PollStartEvent";
|
||||||
import { Poll } from "../../../src/models/poll";
|
import { Poll } from "../../../src/models/poll";
|
||||||
import { getMockClientWithEventEmitter } from "../../test-utils/client";
|
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../test-utils/client";
|
||||||
|
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
describe("Poll", () => {
|
describe("Poll", () => {
|
||||||
|
const userId = "@alice:server.org";
|
||||||
const mockClient = getMockClientWithEventEmitter({
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
|
...mockClientMethodsUser(userId),
|
||||||
relations: jest.fn(),
|
relations: jest.fn(),
|
||||||
});
|
});
|
||||||
const roomId = "!room:server";
|
const roomId = "!room:server";
|
||||||
|
const room = new Room(roomId, mockClient, userId);
|
||||||
|
const maySendRedactionForEventSpy = jest.spyOn(room.currentState, "maySendRedactionForEvent");
|
||||||
// 14.03.2022 16:15
|
// 14.03.2022 16:15
|
||||||
const now = 1647270879403;
|
const now = 1647270879403;
|
||||||
|
|
||||||
const basePollStartEvent = new MatrixEvent({
|
const basePollStartEvent = new MatrixEvent({
|
||||||
...PollStartEvent.from("What?", ["a", "b"], M_POLL_KIND_DISCLOSED.name).serialize(),
|
...PollStartEvent.from("What?", ["a", "b"], M_POLL_KIND_DISCLOSED.name).serialize(),
|
||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
|
sender: userId,
|
||||||
});
|
});
|
||||||
basePollStartEvent.event.event_id = "$12345";
|
basePollStartEvent.event.event_id = "$12345";
|
||||||
|
|
||||||
@@ -42,6 +47,8 @@ describe("Poll", () => {
|
|||||||
jest.setSystemTime(now);
|
jest.setSystemTime(now);
|
||||||
|
|
||||||
mockClient.relations.mockResolvedValue({ events: [] });
|
mockClient.relations.mockResolvedValue({ events: [] });
|
||||||
|
|
||||||
|
maySendRedactionForEventSpy.mockClear().mockReturnValue(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
let eventId = 1;
|
let eventId = 1;
|
||||||
@@ -62,7 +69,7 @@ describe("Poll", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it("initialises with root event", () => {
|
it("initialises with root event", () => {
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
expect(poll.roomId).toEqual(roomId);
|
expect(poll.roomId).toEqual(roomId);
|
||||||
expect(poll.pollId).toEqual(basePollStartEvent.getId());
|
expect(poll.pollId).toEqual(basePollStartEvent.getId());
|
||||||
expect(poll.pollEvent).toEqual(basePollStartEvent.unstableExtensibleEvent);
|
expect(poll.pollEvent).toEqual(basePollStartEvent.unstableExtensibleEvent);
|
||||||
@@ -73,7 +80,7 @@ describe("Poll", () => {
|
|||||||
const pollStartEvent = new MatrixEvent(
|
const pollStartEvent = new MatrixEvent(
|
||||||
PollStartEvent.from("What?", ["a", "b"], M_POLL_KIND_DISCLOSED.name).serialize(),
|
PollStartEvent.from("What?", ["a", "b"], M_POLL_KIND_DISCLOSED.name).serialize(),
|
||||||
);
|
);
|
||||||
expect(() => new Poll(pollStartEvent, mockClient)).toThrowError("Invalid poll start event.");
|
expect(() => new Poll(pollStartEvent, mockClient, room)).toThrowError("Invalid poll start event.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws when poll start has no event id", () => {
|
it("throws when poll start has no event id", () => {
|
||||||
@@ -81,12 +88,12 @@ describe("Poll", () => {
|
|||||||
...PollStartEvent.from("What?", ["a", "b"], M_POLL_KIND_DISCLOSED.name).serialize(),
|
...PollStartEvent.from("What?", ["a", "b"], M_POLL_KIND_DISCLOSED.name).serialize(),
|
||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
});
|
});
|
||||||
expect(() => new Poll(pollStartEvent, mockClient)).toThrowError("Invalid poll start event.");
|
expect(() => new Poll(pollStartEvent, mockClient, room)).toThrowError("Invalid poll start event.");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("fetching responses", () => {
|
describe("fetching responses", () => {
|
||||||
it("calls relations api and emits", async () => {
|
it("calls relations api and emits", async () => {
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
const emitSpy = jest.spyOn(poll, "emit");
|
const emitSpy = jest.spyOn(poll, "emit");
|
||||||
const responses = await poll.getResponses();
|
const responses = await poll.getResponses();
|
||||||
expect(mockClient.relations).toHaveBeenCalledWith(roomId, basePollStartEvent.getId(), "m.reference");
|
expect(mockClient.relations).toHaveBeenCalledWith(roomId, basePollStartEvent.getId(), "m.reference");
|
||||||
@@ -94,7 +101,7 @@ describe("Poll", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns existing responses object after initial fetch", async () => {
|
it("returns existing responses object after initial fetch", async () => {
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
const responses = await poll.getResponses();
|
const responses = await poll.getResponses();
|
||||||
const responses2 = await poll.getResponses();
|
const responses2 = await poll.getResponses();
|
||||||
// only fetched relations once
|
// only fetched relations once
|
||||||
@@ -104,7 +111,7 @@ describe("Poll", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("waits for existing relations request to finish when getting responses", async () => {
|
it("waits for existing relations request to finish when getting responses", async () => {
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
const firstResponsePromise = poll.getResponses();
|
const firstResponsePromise = poll.getResponses();
|
||||||
const secondResponsePromise = poll.getResponses();
|
const secondResponsePromise = poll.getResponses();
|
||||||
await firstResponsePromise;
|
await firstResponsePromise;
|
||||||
@@ -121,14 +128,14 @@ describe("Poll", () => {
|
|||||||
mockClient.relations.mockResolvedValue({
|
mockClient.relations.mockResolvedValue({
|
||||||
events: [replyEvent, stableResponseEvent, unstableResponseEvent],
|
events: [replyEvent, stableResponseEvent, unstableResponseEvent],
|
||||||
});
|
});
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
const responses = await poll.getResponses();
|
const responses = await poll.getResponses();
|
||||||
expect(responses.getRelations()).toEqual([stableResponseEvent, unstableResponseEvent]);
|
expect(responses.getRelations()).toEqual([stableResponseEvent, unstableResponseEvent]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("with poll end event", () => {
|
describe("with poll end event", () => {
|
||||||
const stablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.stable! });
|
const stablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.stable!, sender: "@bob@server.org" });
|
||||||
const unstablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.unstable! });
|
const unstablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.unstable!, sender: "@bob@server.org" });
|
||||||
const responseEventBeforeEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now - 1000);
|
const responseEventBeforeEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now - 1000);
|
||||||
const responseEventAtEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now);
|
const responseEventAtEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now);
|
||||||
const responseEventAfterEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now + 1000);
|
const responseEventAfterEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now + 1000);
|
||||||
@@ -140,10 +147,43 @@ describe("Poll", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("sets poll end event with stable event type", async () => {
|
it("sets poll end event with stable event type", async () => {
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
jest.spyOn(poll, "emit");
|
jest.spyOn(poll, "emit");
|
||||||
await poll.getResponses();
|
await poll.getResponses();
|
||||||
|
|
||||||
|
expect(maySendRedactionForEventSpy).toHaveBeenCalledWith(basePollStartEvent, "@bob@server.org");
|
||||||
|
expect(poll.isEnded).toBe(true);
|
||||||
|
expect(poll.emit).toHaveBeenCalledWith(PollEvent.End);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not set poll end event when sent by a user without redaction rights", async () => {
|
||||||
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
|
maySendRedactionForEventSpy.mockReturnValue(false);
|
||||||
|
jest.spyOn(poll, "emit");
|
||||||
|
await poll.getResponses();
|
||||||
|
|
||||||
|
expect(maySendRedactionForEventSpy).toHaveBeenCalledWith(basePollStartEvent, "@bob@server.org");
|
||||||
|
expect(poll.isEnded).toBe(false);
|
||||||
|
expect(poll.emit).not.toHaveBeenCalledWith(PollEvent.End);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets poll end event when endevent sender also created the poll, but does not have redaction rights", async () => {
|
||||||
|
const pollStartEvent = new MatrixEvent({
|
||||||
|
...PollStartEvent.from("What?", ["a", "b"], M_POLL_KIND_DISCLOSED.name).serialize(),
|
||||||
|
room_id: roomId,
|
||||||
|
sender: "@bob:domain.org",
|
||||||
|
});
|
||||||
|
pollStartEvent.event.event_id = "$6789";
|
||||||
|
const poll = new Poll(pollStartEvent, mockClient, room);
|
||||||
|
const pollEndEvent = makeRelatedEvent({ type: M_POLL_END.stable!, sender: "@bob:domain.org" });
|
||||||
|
mockClient.relations.mockResolvedValue({
|
||||||
|
events: [pollEndEvent],
|
||||||
|
});
|
||||||
|
maySendRedactionForEventSpy.mockReturnValue(false);
|
||||||
|
jest.spyOn(poll, "emit");
|
||||||
|
await poll.getResponses();
|
||||||
|
|
||||||
|
expect(maySendRedactionForEventSpy).not.toHaveBeenCalled();
|
||||||
expect(poll.isEnded).toBe(true);
|
expect(poll.isEnded).toBe(true);
|
||||||
expect(poll.emit).toHaveBeenCalledWith(PollEvent.End);
|
expect(poll.emit).toHaveBeenCalledWith(PollEvent.End);
|
||||||
});
|
});
|
||||||
@@ -152,7 +192,7 @@ describe("Poll", () => {
|
|||||||
mockClient.relations.mockResolvedValue({
|
mockClient.relations.mockResolvedValue({
|
||||||
events: [unstablePollEndEvent],
|
events: [unstablePollEndEvent],
|
||||||
});
|
});
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
jest.spyOn(poll, "emit");
|
jest.spyOn(poll, "emit");
|
||||||
await poll.getResponses();
|
await poll.getResponses();
|
||||||
|
|
||||||
@@ -161,7 +201,7 @@ describe("Poll", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("filters out responses that were sent after poll end", async () => {
|
it("filters out responses that were sent after poll end", async () => {
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
const responses = await poll.getResponses();
|
const responses = await poll.getResponses();
|
||||||
|
|
||||||
// just response type events
|
// just response type events
|
||||||
@@ -173,7 +213,7 @@ describe("Poll", () => {
|
|||||||
|
|
||||||
describe("onNewRelation()", () => {
|
describe("onNewRelation()", () => {
|
||||||
it("discards response if poll responses have not been initialised", () => {
|
it("discards response if poll responses have not been initialised", () => {
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
jest.spyOn(poll, "emit");
|
jest.spyOn(poll, "emit");
|
||||||
const responseEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now);
|
const responseEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now);
|
||||||
|
|
||||||
@@ -184,24 +224,107 @@ describe("Poll", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("sets poll end event when responses are not initialised", () => {
|
it("sets poll end event when responses are not initialised", () => {
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
jest.spyOn(poll, "emit");
|
jest.spyOn(poll, "emit");
|
||||||
const stablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.stable! });
|
const stablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.stable!, sender: userId });
|
||||||
|
|
||||||
poll.onNewRelation(stablePollEndEvent);
|
poll.onNewRelation(stablePollEndEvent);
|
||||||
|
|
||||||
expect(poll.emit).toHaveBeenCalledWith(PollEvent.End);
|
expect(poll.emit).toHaveBeenCalledWith(PollEvent.End);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not set poll end event when sent by invalid user", async () => {
|
||||||
|
maySendRedactionForEventSpy.mockReturnValue(false);
|
||||||
|
const stablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.stable!, sender: "@charlie:server.org" });
|
||||||
|
const responseEventAfterEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now + 1000);
|
||||||
|
mockClient.relations.mockResolvedValue({
|
||||||
|
events: [responseEventAfterEnd],
|
||||||
|
});
|
||||||
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
|
await poll.getResponses();
|
||||||
|
jest.spyOn(poll, "emit");
|
||||||
|
|
||||||
|
poll.onNewRelation(stablePollEndEvent);
|
||||||
|
|
||||||
|
// didn't end, didn't refilter responses
|
||||||
|
expect(poll.emit).not.toHaveBeenCalled();
|
||||||
|
expect(poll.isEnded).toBeFalsy();
|
||||||
|
expect(maySendRedactionForEventSpy).toHaveBeenCalledWith(basePollStartEvent, "@charlie:server.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not set poll end event when an earlier end event already exists", async () => {
|
||||||
|
const earlierPollEndEvent = makeRelatedEvent(
|
||||||
|
{ type: M_POLL_END.stable!, sender: "@valid:server.org" },
|
||||||
|
now,
|
||||||
|
);
|
||||||
|
const laterPollEndEvent = makeRelatedEvent(
|
||||||
|
{ type: M_POLL_END.stable!, sender: "@valid:server.org" },
|
||||||
|
now + 2000,
|
||||||
|
);
|
||||||
|
|
||||||
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
|
await poll.getResponses();
|
||||||
|
|
||||||
|
poll.onNewRelation(earlierPollEndEvent);
|
||||||
|
|
||||||
|
// first end event set correctly
|
||||||
|
expect(poll.isEnded).toBeTruthy();
|
||||||
|
|
||||||
|
// reset spy count
|
||||||
|
jest.spyOn(poll, "emit").mockClear();
|
||||||
|
|
||||||
|
poll.onNewRelation(laterPollEndEvent);
|
||||||
|
// didn't set new end event, didn't refilter responses
|
||||||
|
expect(poll.emit).not.toHaveBeenCalled();
|
||||||
|
expect(poll.isEnded).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaces poll end event and refilters when an older end event already exists", async () => {
|
||||||
|
const earlierPollEndEvent = makeRelatedEvent(
|
||||||
|
{ type: M_POLL_END.stable!, sender: "@valid:server.org" },
|
||||||
|
now,
|
||||||
|
);
|
||||||
|
const laterPollEndEvent = makeRelatedEvent(
|
||||||
|
{ type: M_POLL_END.stable!, sender: "@valid:server.org" },
|
||||||
|
now + 2000,
|
||||||
|
);
|
||||||
|
const responseEventBeforeEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now - 1000);
|
||||||
|
const responseEventAtEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now);
|
||||||
|
const responseEventAfterEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now + 1000);
|
||||||
|
mockClient.relations.mockResolvedValue({
|
||||||
|
events: [responseEventAfterEnd, responseEventAtEnd, responseEventBeforeEnd, laterPollEndEvent],
|
||||||
|
});
|
||||||
|
|
||||||
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
|
const responses = await poll.getResponses();
|
||||||
|
|
||||||
|
// all responses have a timestamp < laterPollEndEvent
|
||||||
|
expect(responses.getRelations().length).toEqual(3);
|
||||||
|
// first end event set correctly
|
||||||
|
expect(poll.isEnded).toBeTruthy();
|
||||||
|
|
||||||
|
// reset spy count
|
||||||
|
jest.spyOn(poll, "emit").mockClear();
|
||||||
|
|
||||||
|
// add a valid end event with earlier timestamp
|
||||||
|
poll.onNewRelation(earlierPollEndEvent);
|
||||||
|
|
||||||
|
// emitted new end event
|
||||||
|
expect(poll.emit).toHaveBeenCalledWith(PollEvent.End);
|
||||||
|
// filtered responses and emitted
|
||||||
|
expect(poll.emit).toHaveBeenCalledWith(PollEvent.Responses, responses);
|
||||||
|
expect(responses.getRelations()).toEqual([responseEventAtEnd, responseEventBeforeEnd]);
|
||||||
|
});
|
||||||
|
|
||||||
it("sets poll end event and refilters responses based on timestamp", async () => {
|
it("sets poll end event and refilters responses based on timestamp", async () => {
|
||||||
const stablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.stable! });
|
const stablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.stable!, sender: userId });
|
||||||
const responseEventBeforeEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now - 1000);
|
const responseEventBeforeEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now - 1000);
|
||||||
const responseEventAtEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now);
|
const responseEventAtEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now);
|
||||||
const responseEventAfterEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now + 1000);
|
const responseEventAfterEnd = makeRelatedEvent({ type: M_POLL_RESPONSE.name }, now + 1000);
|
||||||
mockClient.relations.mockResolvedValue({
|
mockClient.relations.mockResolvedValue({
|
||||||
events: [responseEventAfterEnd, responseEventAtEnd, responseEventBeforeEnd],
|
events: [responseEventAfterEnd, responseEventAtEnd, responseEventBeforeEnd],
|
||||||
});
|
});
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
const responses = await poll.getResponses();
|
const responses = await poll.getResponses();
|
||||||
jest.spyOn(poll, "emit");
|
jest.spyOn(poll, "emit");
|
||||||
|
|
||||||
@@ -216,7 +339,7 @@ describe("Poll", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("filters out irrelevant relations", async () => {
|
it("filters out irrelevant relations", async () => {
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
// init responses
|
// init responses
|
||||||
const responses = await poll.getResponses();
|
const responses = await poll.getResponses();
|
||||||
jest.spyOn(poll, "emit");
|
jest.spyOn(poll, "emit");
|
||||||
@@ -230,7 +353,7 @@ describe("Poll", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("adds poll response relations to responses", async () => {
|
it("adds poll response relations to responses", async () => {
|
||||||
const poll = new Poll(basePollStartEvent, mockClient);
|
const poll = new Poll(basePollStartEvent, mockClient, room);
|
||||||
// init responses
|
// init responses
|
||||||
const responses = await poll.getResponses();
|
const responses = await poll.getResponses();
|
||||||
jest.spyOn(poll, "emit");
|
jest.spyOn(poll, "emit");
|
||||||
|
@@ -18,6 +18,7 @@ import { M_POLL_END, M_POLL_RESPONSE, PollStartEvent } from "../@types/polls";
|
|||||||
import { MatrixClient } from "../client";
|
import { MatrixClient } from "../client";
|
||||||
import { MatrixEvent } from "./event";
|
import { MatrixEvent } from "./event";
|
||||||
import { Relations } from "./relations";
|
import { Relations } from "./relations";
|
||||||
|
import { Room } from "./room";
|
||||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||||
|
|
||||||
export enum PollEvent {
|
export enum PollEvent {
|
||||||
@@ -64,13 +65,12 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
|
|||||||
private responses: null | Relations = null;
|
private responses: null | Relations = null;
|
||||||
private endEvent: MatrixEvent | undefined;
|
private endEvent: MatrixEvent | undefined;
|
||||||
|
|
||||||
public constructor(private rootEvent: MatrixEvent, private matrixClient: MatrixClient) {
|
public constructor(private rootEvent: MatrixEvent, private matrixClient: MatrixClient, private room: Room) {
|
||||||
super();
|
super();
|
||||||
if (!this.rootEvent.getRoomId() || !this.rootEvent.getId()) {
|
if (!this.rootEvent.getRoomId() || !this.rootEvent.getId()) {
|
||||||
throw new Error("Invalid poll start event.");
|
throw new Error("Invalid poll start event.");
|
||||||
}
|
}
|
||||||
this.roomId = this.rootEvent.getRoomId()!;
|
this.roomId = this.rootEvent.getRoomId()!;
|
||||||
// @TODO(kerrya) proper way to do this?
|
|
||||||
this.pollEvent = this.rootEvent.unstableExtensibleEvent as unknown as PollStartEvent;
|
this.pollEvent = this.rootEvent.unstableExtensibleEvent as unknown as PollStartEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
|
|||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
public onNewRelation(event: MatrixEvent): void {
|
public onNewRelation(event: MatrixEvent): void {
|
||||||
if (M_POLL_END.matches(event.getType())) {
|
if (M_POLL_END.matches(event.getType()) && this.validateEndEvent(event)) {
|
||||||
this.endEvent = event;
|
this.endEvent = event;
|
||||||
this.refilterResponsesOnEnd();
|
this.refilterResponsesOnEnd();
|
||||||
this.emit(PollEvent.End);
|
this.emit(PollEvent.End);
|
||||||
@@ -136,7 +136,8 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
|
|||||||
M_POLL_RESPONSE.altName!,
|
M_POLL_RESPONSE.altName!,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const pollEndEvent = allRelations.events.find((event) => M_POLL_END.matches(event.getType()));
|
const potentialEndEvent = allRelations.events.find((event) => M_POLL_END.matches(event.getType()));
|
||||||
|
const pollEndEvent = this.validateEndEvent(potentialEndEvent) ? potentialEndEvent : undefined;
|
||||||
const pollCloseTimestamp = pollEndEvent?.getTs() || Number.MAX_SAFE_INTEGER;
|
const pollCloseTimestamp = pollEndEvent?.getTs() || Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
const { responseEvents } = filterResponseRelations(allRelations.events, pollCloseTimestamp);
|
const { responseEvents } = filterResponseRelations(allRelations.events, pollCloseTimestamp);
|
||||||
@@ -172,4 +173,30 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
|
|||||||
|
|
||||||
this.emit(PollEvent.Responses, this.responses);
|
this.emit(PollEvent.Responses, this.responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private validateEndEvent(endEvent?: MatrixEvent): boolean {
|
||||||
|
if (!endEvent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Repeated end events are ignored -
|
||||||
|
* only the first (valid) closure event by origin_server_ts is counted.
|
||||||
|
*/
|
||||||
|
if (this.endEvent && this.endEvent.getTs() < endEvent.getTs()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MSC3381
|
||||||
|
* If a m.poll.end event is received from someone other than the poll creator or user with permission to redact
|
||||||
|
* others' messages in the room, the event must be ignored by clients due to being invalid.
|
||||||
|
*/
|
||||||
|
const roomCurrentState = this.room.currentState;
|
||||||
|
const endEventSender = endEvent.getSender();
|
||||||
|
return (
|
||||||
|
!!endEventSender &&
|
||||||
|
(endEventSender === this.rootEvent.getSender() ||
|
||||||
|
roomCurrentState.maySendRedactionForEvent(this.rootEvent, endEventSender))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1897,7 +1897,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
|||||||
const processPollStartEvent = (event: MatrixEvent): void => {
|
const processPollStartEvent = (event: MatrixEvent): void => {
|
||||||
if (!M_POLL_START.matches(event.getType())) return;
|
if (!M_POLL_START.matches(event.getType())) return;
|
||||||
try {
|
try {
|
||||||
const poll = new Poll(event, this.client);
|
const poll = new Poll(event, this.client, this);
|
||||||
this.polls.set(event.getId()!, poll);
|
this.polls.set(event.getId()!, poll);
|
||||||
this.emit(PollEvent.New, poll);
|
this.emit(PollEvent.New, poll);
|
||||||
} catch {}
|
} catch {}
|
||||||
|
Reference in New Issue
Block a user