summaryrefslogtreecommitdiff
path: root/includes/external/matrix/node_modules/matrix-widget-api/src/models/WidgetEventCapability.ts
blob: 16d933eabf123a3db01de7e3742eeaeb05ac6111 (plain)
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
/*
 * Copyright 2020 The Matrix.org Foundation C.I.C.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Capability } from "..";

export enum EventKind {
    Event = "event",
    State = "state_event",
    ToDevice = "to_device",
}

export enum EventDirection {
    Send = "send",
    Receive = "receive",
}

export class WidgetEventCapability {
    private constructor(
        public readonly direction: EventDirection,
        public readonly eventType: string,
        public readonly kind: EventKind,
        public readonly keyStr: string | null,
        public readonly raw: string,
    ) {
    }

    public matchesAsStateEvent(direction: EventDirection, eventType: string, stateKey: string | null): boolean {
        if (this.kind !== EventKind.State) return false; // not a state event
        if (this.direction !== direction) return false; // direction mismatch
        if (this.eventType !== eventType) return false; // event type mismatch
        if (this.keyStr === null) return true; // all state keys are allowed
        if (this.keyStr === stateKey) return true; // this state key is allowed

        // Default not allowed
        return false;
    }

    public matchesAsToDeviceEvent(direction: EventDirection, eventType: string): boolean {
        if (this.kind !== EventKind.ToDevice) return false; // not a to-device event
        if (this.direction !== direction) return false; // direction mismatch
        if (this.eventType !== eventType) return false; // event type mismatch

        // Checks passed, the event is allowed
        return true;
    }

    public matchesAsRoomEvent(direction: EventDirection, eventType: string, msgtype: string | null = null): boolean {
        if (this.kind !== EventKind.Event) return false; // not a room event
        if (this.direction !== direction) return false; // direction mismatch
        if (this.eventType !== eventType) return false; // event type mismatch

        if (this.eventType === "m.room.message") {
            if (this.keyStr === null) return true; // all message types are allowed
            if (this.keyStr === msgtype) return true; // this message type is allowed
        } else {
            return true; // already passed the check for if the event is allowed
        }

        // Default not allowed
        return false;
    }

    public static forStateEvent(
        direction: EventDirection,
        eventType: string,
        stateKey?: string,
    ): WidgetEventCapability {
        // TODO: Enable support for m.* namespace once the MSC lands.
        // https://github.com/matrix-org/matrix-widget-api/issues/22
        eventType = eventType.replace(/#/g, '\\#');
        stateKey = stateKey !== null && stateKey !== undefined ? `#${stateKey}` : '';
        const str = `org.matrix.msc2762.${direction}.state_event:${eventType}${stateKey}`;

        // cheat by sending it through the processor
        return WidgetEventCapability.findEventCapabilities([str])[0];
    }

    public static forToDeviceEvent(direction: EventDirection, eventType: string): WidgetEventCapability {
        // TODO: Enable support for m.* namespace once the MSC lands.
        // https://github.com/matrix-org/matrix-widget-api/issues/56
        const str = `org.matrix.msc3819.${direction}.to_device:${eventType}`;

        // cheat by sending it through the processor
        return WidgetEventCapability.findEventCapabilities([str])[0];
    }

    public static forRoomEvent(direction: EventDirection, eventType: string): WidgetEventCapability {
        // TODO: Enable support for m.* namespace once the MSC lands.
        // https://github.com/matrix-org/matrix-widget-api/issues/22
        const str = `org.matrix.msc2762.${direction}.event:${eventType}`;

        // cheat by sending it through the processor
        return WidgetEventCapability.findEventCapabilities([str])[0];
    }

    public static forRoomMessageEvent(direction: EventDirection, msgtype?: string): WidgetEventCapability {
        // TODO: Enable support for m.* namespace once the MSC lands.
        // https://github.com/matrix-org/matrix-widget-api/issues/22
        msgtype = msgtype === null || msgtype === undefined ? '' : msgtype;
        const str = `org.matrix.msc2762.${direction}.event:m.room.message#${msgtype}`;

        // cheat by sending it through the processor
        return WidgetEventCapability.findEventCapabilities([str])[0];
    }

    /**
     * Parses a capabilities request to find all the event capability requests.
     * @param {Iterable<Capability>} capabilities The capabilities requested/to parse.
     * @returns {WidgetEventCapability[]} An array of event capability requests. May be empty, but never null.
     */
    public static findEventCapabilities(capabilities: Iterable<Capability>): WidgetEventCapability[] {
        const parsed: WidgetEventCapability[] = [];
        for (const cap of capabilities) {
            let direction: EventDirection | null = null;
            let eventSegment: string | undefined;
            let kind: EventKind | null = null;

            // TODO: Enable support for m.* namespace once the MSCs land.
            // https://github.com/matrix-org/matrix-widget-api/issues/22
            // https://github.com/matrix-org/matrix-widget-api/issues/56

            if (cap.startsWith("org.matrix.msc2762.send.event:")) {
                direction = EventDirection.Send;
                kind = EventKind.Event;
                eventSegment = cap.substring("org.matrix.msc2762.send.event:".length);
            } else if (cap.startsWith("org.matrix.msc2762.send.state_event:")) {
                direction = EventDirection.Send;
                kind = EventKind.State;
                eventSegment = cap.substring("org.matrix.msc2762.send.state_event:".length);
            } else if (cap.startsWith("org.matrix.msc3819.send.to_device:")) {
                direction = EventDirection.Send;
                kind = EventKind.ToDevice;
                eventSegment = cap.substring("org.matrix.msc3819.send.to_device:".length);
            } else if (cap.startsWith("org.matrix.msc2762.receive.event:")) {
                direction = EventDirection.Receive;
                kind = EventKind.Event;
                eventSegment = cap.substring("org.matrix.msc2762.receive.event:".length);
            } else if (cap.startsWith("org.matrix.msc2762.receive.state_event:")) {
                direction = EventDirection.Receive;
                kind = EventKind.State;
                eventSegment = cap.substring("org.matrix.msc2762.receive.state_event:".length);
            } else if (cap.startsWith("org.matrix.msc3819.receive.to_device:")) {
                direction = EventDirection.Receive;
                kind = EventKind.ToDevice;
                eventSegment = cap.substring("org.matrix.msc3819.receive.to_device:".length);
            }

            if (direction === null || kind === null || eventSegment === undefined) continue;

            // The capability uses `#` as a separator between event type and state key/msgtype,
            // so we split on that. However, a # is also valid in either one of those so we
            // join accordingly.
            // Eg: `m.room.message##m.text` is "m.room.message" event with msgtype "#m.text".
            const expectingKeyStr = eventSegment.startsWith("m.room.message#") || kind === EventKind.State;
            let keyStr: string | null = null;
            if (eventSegment.includes('#') && expectingKeyStr) {
                // Dev note: regex is difficult to write, so instead the rules are manually written
                // out. This is probably just as understandable as a boring regex though, so win-win?

                // Test cases:
                // str                      eventSegment        keyStr
                // -------------------------------------------------------------
                // m.room.message#          m.room.message      <empty string>
                // m.room.message#test      m.room.message      test
                // m.room.message\#         m.room.message#     test
                // m.room.message##test     m.room.message      #test
                // m.room.message\##test    m.room.message#     test
                // m.room.message\\##test   m.room.message\#    test
                // m.room.message\\###test  m.room.message\#    #test

                // First step: explode the string
                const parts = eventSegment.split('#');

                // To form the eventSegment, we'll keep finding parts of the exploded string until
                // there's one that doesn't end with the escape character (\). We'll then join those
                // segments together with the exploding character. We have to remember to consume the
                // escape character as well.
                const idx = parts.findIndex(p => !p.endsWith("\\"));
                eventSegment = parts.slice(0, idx + 1)
                    .map(p => p.endsWith('\\') ? p.substring(0, p.length - 1) : p)
                    .join('#');

                // The keyStr is whatever is left over.
                keyStr = parts.slice(idx + 1).join('#');
            }

            parsed.push(new WidgetEventCapability(direction, eventSegment, kind, keyStr, cap));
        }
        return parsed;
    }
}