summaryrefslogtreecommitdiff
path: root/includes/external/matrix/node_modules/matrix-widget-api/src/models/WidgetParser.ts
blob: f93c0773a6827e4da90e8f956f59e0c29527a742 (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
/*
 * 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 { Widget } from "./Widget";
import { IWidget } from "..";
import { isValidUrl } from "./validation/url";

export interface IStateEvent {
    event_id: string; // eslint-disable-line camelcase
    room_id: string; // eslint-disable-line camelcase
    type: string;
    sender: string;
    origin_server_ts: number; // eslint-disable-line camelcase
    unsigned?: unknown;
    content: unknown;
    state_key: string; // eslint-disable-line camelcase
}

export interface IAccountDataWidgets {
    [widgetId: string]: {
        type: "m.widget";
        // the state_key is also the widget's ID
        state_key: string; // eslint-disable-line camelcase
        sender: string; // current user's ID
        content: IWidget;
        id?: string; // off-spec, but possible
    };
}

export class WidgetParser {
    private constructor() {
        // private constructor because this is a util class
    }

    /**
     * Parses widgets from the "m.widgets" account data event. This will always
     * return an array, though may be empty if no valid widgets were found.
     * @param {IAccountDataWidgets} content The content of the "m.widgets" account data.
     * @returns {Widget[]} The widgets in account data, or an empty array.
     */
    public static parseAccountData(content: IAccountDataWidgets): Widget[] {
        if (!content) return [];

        const result: Widget[] = [];
        for (const widgetId of Object.keys(content)) {
            const roughWidget = content[widgetId];
            if (!roughWidget) continue;
            if (roughWidget.type !== "m.widget" && roughWidget.type !== "im.vector.modular.widgets") continue;
            if (!roughWidget.sender) continue;

            const probableWidgetId = roughWidget.state_key || roughWidget.id;
            if (probableWidgetId !== widgetId) continue;

            const asStateEvent: IStateEvent = {
                content: roughWidget.content,
                sender: roughWidget.sender,
                type: "m.widget",
                state_key: widgetId,
                event_id: "$example",
                room_id: "!example",
                origin_server_ts: 1,
            };

            const widget = WidgetParser.parseRoomWidget(asStateEvent);
            if (widget) result.push(widget);
        }

        return result;
    }

    /**
     * Parses all the widgets possible in the given array. This will always return
     * an array, though may be empty if no widgets could be parsed.
     * @param {IStateEvent[]} currentState The room state to parse.
     * @returns {Widget[]} The widgets in the state, or an empty array.
     */
    public static parseWidgetsFromRoomState(currentState: IStateEvent[]): Widget[] {
        if (!currentState) return [];
        const result: Widget[] = [];
        for (const state of currentState) {
            const widget = WidgetParser.parseRoomWidget(state);
            if (widget) result.push(widget);
        }
        return result;
    }

    /**
     * Parses a state event into a widget. If the state event does not represent
     * a widget (wrong event type, invalid widget, etc) then null is returned.
     * @param {IStateEvent} stateEvent The state event.
     * @returns {Widget|null} The widget, or null if invalid
     */
    public static parseRoomWidget(stateEvent: IStateEvent): Widget | null {
        if (!stateEvent) return null;

        // TODO: [Legacy] Remove legacy support
        if (stateEvent.type !== "m.widget" && stateEvent.type !== "im.vector.modular.widgets") {
            return null;
        }

        // Dev note: Throughout this function we have null safety to ensure that
        // if the caller did not supply something useful that we don't error. This
        // is done against the requirements of the interface because not everyone
        // will have an interface to validate against.

        const content = stateEvent.content as IWidget || {};

        // Form our best approximation of a widget with the information we have
        const estimatedWidget: IWidget = {
            id: stateEvent.state_key,
            creatorUserId: content['creatorUserId'] || stateEvent.sender,
            name: content['name'],
            type: content['type'],
            url: content['url'],
            waitForIframeLoad: content['waitForIframeLoad'],
            data: content['data'],
        };

        // Finally, process that widget
        return WidgetParser.processEstimatedWidget(estimatedWidget);
    }

    private static processEstimatedWidget(widget: IWidget): Widget | null {
        // Validate that the widget has the best chance of passing as a widget
        if (!widget.id || !widget.creatorUserId || !widget.type) {
            return null;
        }
        if (!isValidUrl(widget.url)) {
            return null;
        }
        // TODO: Validate data for known widget types
        return new Widget(widget);
    }
}