Skip to content

Commit 6e5326f

Browse files
Add custom notification handling for MSC3401 call events (#2720)
1 parent a1b046b commit 6e5326f

File tree

3 files changed

+142
-4
lines changed

3 files changed

+142
-4
lines changed

spec/test-utils/test-utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ interface IEventOpts {
7474
sender?: string;
7575
skey?: string;
7676
content: IContent;
77+
prev_content?: IContent;
7778
user?: string;
7879
unsigned?: IUnsigned;
7980
redacts?: string;
@@ -103,6 +104,7 @@ export function mkEvent(opts: IEventOpts & { event?: boolean }, client?: MatrixC
103104
room_id: opts.room,
104105
sender: opts.sender || opts.user, // opts.user for backwards-compat
105106
content: opts.content,
107+
prev_content: opts.prev_content,
106108
unsigned: opts.unsigned || {},
107109
event_id: "$" + testEventIndex++ + "-" + Math.random() + "-" + Math.random(),
108110
txn_id: "~" + Math.random(),

spec/unit/pushprocessor.spec.ts

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as utils from "../test-utils/test-utils";
2-
import { PushProcessor } from "../../src/pushprocessor";
3-
import { EventType, MatrixClient, MatrixEvent } from "../../src";
2+
import { IActionsObject, PushProcessor } from "../../src/pushprocessor";
3+
import { EventType, IContent, MatrixClient, MatrixEvent } from "../../src";
44

55
describe('NotificationService', function() {
66
const testUserId = "@ali:matrix.org";
@@ -336,4 +336,102 @@ describe('NotificationService', function() {
336336
enabled: true,
337337
}, testEvent)).toBe(true);
338338
});
339+
340+
describe("performCustomEventHandling()", () => {
341+
const getActionsForEvent = (prevContent: IContent, content: IContent): IActionsObject => {
342+
testEvent = utils.mkEvent({
343+
type: "org.matrix.msc3401.call",
344+
room: testRoomId,
345+
user: "@alice:foo",
346+
skey: "state_key",
347+
event: true,
348+
content: content,
349+
prev_content: prevContent,
350+
});
351+
352+
return pushProcessor.actionsForEvent(testEvent);
353+
};
354+
355+
const assertDoesNotify = (actions: IActionsObject): void => {
356+
expect(actions.notify).toBeTruthy();
357+
expect(actions.tweaks.sound).toBeTruthy();
358+
expect(actions.tweaks.highlight).toBeFalsy();
359+
};
360+
361+
const assertDoesNotNotify = (actions: IActionsObject): void => {
362+
expect(actions.notify).toBeFalsy();
363+
expect(actions.tweaks.sound).toBeFalsy();
364+
expect(actions.tweaks.highlight).toBeFalsy();
365+
};
366+
367+
it.each(
368+
["m.ring", "m.prompt"],
369+
)("should notify when new group call event appears with %s intent", (intent: string) => {
370+
assertDoesNotify(getActionsForEvent({}, {
371+
"m.intent": intent,
372+
"m.type": "m.voice",
373+
"m.name": "Call",
374+
}));
375+
});
376+
377+
it("should notify when a call is un-terminated", () => {
378+
assertDoesNotify(getActionsForEvent({
379+
"m.intent": "m.ring",
380+
"m.type": "m.voice",
381+
"m.name": "Call",
382+
"m.terminated": "All users left",
383+
}, {
384+
"m.intent": "m.ring",
385+
"m.type": "m.voice",
386+
"m.name": "Call",
387+
}));
388+
});
389+
390+
it("should not notify when call is terminated", () => {
391+
assertDoesNotNotify(getActionsForEvent({
392+
"m.intent": "m.ring",
393+
"m.type": "m.voice",
394+
"m.name": "Call",
395+
}, {
396+
"m.intent": "m.ring",
397+
"m.type": "m.voice",
398+
"m.name": "Call",
399+
"m.terminated": "All users left",
400+
}));
401+
});
402+
403+
it("should ignore with m.room intent", () => {
404+
assertDoesNotNotify(getActionsForEvent({}, {
405+
"m.intent": "m.room",
406+
"m.type": "m.voice",
407+
"m.name": "Call",
408+
}));
409+
});
410+
411+
describe("ignoring non-relevant state changes", () => {
412+
it("should ignore intent changes", () => {
413+
assertDoesNotNotify(getActionsForEvent({
414+
"m.intent": "m.ring",
415+
"m.type": "m.voice",
416+
"m.name": "Call",
417+
}, {
418+
"m.intent": "m.ring",
419+
"m.type": "m.video",
420+
"m.name": "Call",
421+
}));
422+
});
423+
424+
it("should ignore name changes", () => {
425+
assertDoesNotNotify(getActionsForEvent({
426+
"m.intent": "m.ring",
427+
"m.type": "m.voice",
428+
"m.name": "Call",
429+
}, {
430+
"m.intent": "m.ring",
431+
"m.type": "m.voice",
432+
"m.name": "New call",
433+
}));
434+
});
435+
});
436+
});
339437
});

src/pushprocessor.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { escapeRegExp, globToRegexp, isNullOrUndefined } from "./utils";
17+
import { deepCompare, escapeRegExp, globToRegexp, isNullOrUndefined } from "./utils";
1818
import { logger } from './logger';
1919
import { MatrixClient } from "./client";
2020
import { MatrixEvent } from "./models/event";
@@ -91,6 +91,20 @@ const DEFAULT_OVERRIDE_RULES: IPushRule[] = [
9191
],
9292
actions: [],
9393
},
94+
{
95+
// For homeservers which don't support MSC3401 yet
96+
rule_id: ".org.matrix.msc3401.rule.room.call",
97+
default: true,
98+
enabled: true,
99+
conditions: [
100+
{
101+
kind: ConditionKind.EventMatch,
102+
key: "type",
103+
pattern: "org.matrix.msc3401.call",
104+
},
105+
],
106+
actions: [PushRuleActionName.Notify, { set_tweak: TweakName.Sound, value: "default" }],
107+
},
94108
];
95109

96110
export interface IActionsObject {
@@ -424,7 +438,7 @@ export class PushProcessor {
424438
return {} as IActionsObject;
425439
}
426440

427-
const actionObj = PushProcessor.actionListToActionsObject(rule.actions);
441+
let actionObj = PushProcessor.actionListToActionsObject(rule.actions);
428442

429443
// Some actions are implicit in some situations: we add those here
430444
if (actionObj.tweaks.highlight === undefined) {
@@ -433,6 +447,30 @@ export class PushProcessor {
433447
actionObj.tweaks.highlight = (rule.kind == PushRuleKind.ContentSpecific);
434448
}
435449

450+
actionObj = this.performCustomEventHandling(ev, actionObj);
451+
452+
return actionObj;
453+
}
454+
455+
/**
456+
* Some events require custom event handling e.g. due to missing server support
457+
*/
458+
private performCustomEventHandling(ev: MatrixEvent, actionObj: IActionsObject): IActionsObject {
459+
switch (ev.getType()) {
460+
case "m.call":
461+
case "org.matrix.msc3401.call":
462+
// Since servers don't support properly sending push notification
463+
// about MSC3401 call events, we do the handling ourselves
464+
if (
465+
ev.getContent()["m.intent"] === "m.room"
466+
|| ("m.terminated" in ev.getContent())
467+
|| !("m.terminated" in ev.getPrevContent()) && !deepCompare(ev.getPrevContent(), {})
468+
) {
469+
actionObj.notify = false;
470+
actionObj.tweaks = {};
471+
}
472+
}
473+
436474
return actionObj;
437475
}
438476

0 commit comments

Comments
 (0)