summaryrefslogtreecommitdiff
path: root/includes/external/matrix/node_modules/matrix-js-sdk/lib/models/MSC3089Branch.js
blob: ff45c173b9d7e9a90fffa4f1f2c1e02c5471ed47 (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
205
206
207
208
209
210
211
212
213
214
"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.MSC3089Branch = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _event = require("../@types/event");
var _eventTimeline = require("./event-timeline");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
/**
 * Represents a [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) branch - a reference
 * to a file (leaf) in the tree. Note that this is UNSTABLE and subject to breaking changes
 * without notice.
 */
class MSC3089Branch {
  constructor(client, indexEvent, directory) {
    this.client = client;
    this.indexEvent = indexEvent;
    this.directory = directory;
  } // Nothing to do

  /**
   * The file ID.
   */
  get id() {
    const stateKey = this.indexEvent.getStateKey();
    if (!stateKey) {
      throw new Error("State key not found for branch");
    }
    return stateKey;
  }

  /**
   * Whether this branch is active/valid.
   */
  get isActive() {
    return this.indexEvent.getContent()["active"] === true;
  }

  /**
   * Version for the file, one-indexed.
   */
  get version() {
    var _this$indexEvent$getC;
    return (_this$indexEvent$getC = this.indexEvent.getContent()["version"]) !== null && _this$indexEvent$getC !== void 0 ? _this$indexEvent$getC : 1;
  }
  get roomId() {
    return this.indexEvent.getRoomId();
  }

  /**
   * Deletes the file from the tree, including all prior edits/versions.
   * @returns Promise which resolves when complete.
   */
  async delete() {
    await this.client.sendStateEvent(this.roomId, _event.UNSTABLE_MSC3089_BRANCH.name, {}, this.id);
    await this.client.redactEvent(this.roomId, this.id);
    const nextVersion = (await this.getVersionHistory())[1]; // [0] will be us
    if (nextVersion) await nextVersion.delete(); // implicit recursion
  }

  /**
   * Gets the name for this file.
   * @returns The name, or "Unnamed File" if unknown.
   */
  getName() {
    return this.indexEvent.getContent()["name"] || "Unnamed File";
  }

  /**
   * Sets the name for this file.
   * @param name - The new name for this file.
   * @returns Promise which resolves when complete.
   */
  async setName(name) {
    await this.client.sendStateEvent(this.roomId, _event.UNSTABLE_MSC3089_BRANCH.name, _objectSpread(_objectSpread({}, this.indexEvent.getContent()), {}, {
      name: name
    }), this.id);
  }

  /**
   * Gets whether or not a file is locked.
   * @returns True if locked, false otherwise.
   */
  isLocked() {
    return this.indexEvent.getContent()["locked"] || false;
  }

  /**
   * Sets a file as locked or unlocked.
   * @param locked - True to lock the file, false otherwise.
   * @returns Promise which resolves when complete.
   */
  async setLocked(locked) {
    await this.client.sendStateEvent(this.roomId, _event.UNSTABLE_MSC3089_BRANCH.name, _objectSpread(_objectSpread({}, this.indexEvent.getContent()), {}, {
      locked: locked
    }), this.id);
  }

  /**
   * Gets information about the file needed to download it.
   * @returns Information about the file.
   */
  async getFileInfo() {
    const event = await this.getFileEvent();
    const file = event.getOriginalContent()["file"];
    const httpUrl = this.client.mxcUrlToHttp(file["url"]);
    if (!httpUrl) {
      throw new Error(`No HTTP URL available for ${file["url"]}`);
    }
    return {
      info: file,
      httpUrl: httpUrl
    };
  }

  /**
   * Gets the event the file points to.
   * @returns Promise which resolves to the file's event.
   */
  async getFileEvent() {
    const room = this.client.getRoom(this.roomId);
    if (!room) throw new Error("Unknown room");
    let event = room.getUnfilteredTimelineSet().findEventById(this.id);

    // keep scrolling back if needed until we find the event or reach the start of the room:
    while (!event && room.getLiveTimeline().getState(_eventTimeline.EventTimeline.BACKWARDS).paginationToken) {
      await this.client.scrollback(room, 100);
      event = room.getUnfilteredTimelineSet().findEventById(this.id);
    }
    if (!event) throw new Error("Failed to find event");

    // Sometimes the event isn't decrypted for us, so do that. We specifically set `emit: true`
    // to ensure that the relations system in the sdk will function.
    await this.client.decryptEventIfNeeded(event, {
      emit: true,
      isRetry: true
    });
    return event;
  }

  /**
   * Creates a new version of this file with contents in a type that is compatible with MatrixClient.uploadContent().
   * @param name - The name of the file.
   * @param encryptedContents - The encrypted contents.
   * @param info - The encrypted file information.
   * @param additionalContent - Optional event content fields to include in the message.
   * @returns Promise which resolves to the file event's sent response.
   */
  async createNewVersion(name, encryptedContents, info, additionalContent) {
    const fileEventResponse = await this.directory.createFile(name, encryptedContents, info, _objectSpread(_objectSpread({}, additionalContent !== null && additionalContent !== void 0 ? additionalContent : {}), {}, {
      "m.new_content": true,
      "m.relates_to": {
        rel_type: _event.RelationType.Replace,
        event_id: this.id
      }
    }));

    // Update the version of the new event
    await this.client.sendStateEvent(this.roomId, _event.UNSTABLE_MSC3089_BRANCH.name, {
      active: true,
      name: name,
      version: this.version + 1
    }, fileEventResponse["event_id"]);

    // Deprecate ourselves
    await this.client.sendStateEvent(this.roomId, _event.UNSTABLE_MSC3089_BRANCH.name, _objectSpread(_objectSpread({}, this.indexEvent.getContent()), {}, {
      active: false
    }), this.id);
    return fileEventResponse;
  }

  /**
   * Gets the file's version history, starting at this file.
   * @returns Promise which resolves to the file's version history, with the
   * first element being the current version and the last element being the first version.
   */
  async getVersionHistory() {
    const fileHistory = [];
    fileHistory.push(this); // start with ourselves

    const room = this.client.getRoom(this.roomId);
    if (!room) throw new Error("Invalid or unknown room");

    // Clone the timeline to reverse it, getting most-recent-first ordering, hopefully
    // shortening the awful loop below. Without the clone, we can unintentionally mutate
    // the timeline.
    const timelineEvents = [...room.getLiveTimeline().getEvents()].reverse();

    // XXX: This is a very inefficient search, but it's the best we can do with the
    // relations structure we have in the SDK. As of writing, it is not worth the
    // investment in improving the structure.
    let childEvent;
    let parentEvent = await this.getFileEvent();
    do {
      childEvent = timelineEvents.find(e => e.replacingEventId() === parentEvent.getId());
      if (childEvent) {
        const branch = this.directory.getFile(childEvent.getId());
        if (branch) {
          fileHistory.push(branch);
          parentEvent = childEvent;
        } else {
          break; // prevent infinite loop
        }
      }
    } while (childEvent);
    return fileHistory;
  }
}
exports.MSC3089Branch = MSC3089Branch;
//# sourceMappingURL=MSC3089Branch.js.map