You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +03:00
Early file management APIs
This commit is contained in:
27
spec/MockBlob.ts
Normal file
27
spec/MockBlob.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class MockBlob {
|
||||||
|
private contents: number[] = [];
|
||||||
|
|
||||||
|
public constructor(private parts: ArrayLike<number>[]) {
|
||||||
|
parts.forEach(p => Array.from(p).forEach(e => this.contents.push(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public get size(): number {
|
||||||
|
return this.contents.length;
|
||||||
|
}
|
||||||
|
}
|
153
spec/unit/models/MSC3089Branch.spec.ts
Normal file
153
spec/unit/models/MSC3089Branch.spec.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 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 { MatrixClient } from "../../../src";
|
||||||
|
import { Room } from "../../../src/models/room";
|
||||||
|
import { MatrixEvent } from "../../../src/models/event";
|
||||||
|
import { UNSTABLE_MSC3089_BRANCH } from "../../../src/@types/event";
|
||||||
|
import { EventTimelineSet } from "../../../src/models/event-timeline-set";
|
||||||
|
import { EventTimeline } from "../../../src/models/event-timeline";
|
||||||
|
import { MSC3089Branch } from "../../../src/models/MSC3089Branch";
|
||||||
|
|
||||||
|
describe("MSC3089Branch", () => {
|
||||||
|
let client: MatrixClient;
|
||||||
|
// @ts-ignore - TS doesn't know that this is a type
|
||||||
|
let indexEvent: MatrixEvent;
|
||||||
|
let branch: MSC3089Branch;
|
||||||
|
|
||||||
|
const branchRoomId = "!room:example.org";
|
||||||
|
const fileEventId = "$file";
|
||||||
|
|
||||||
|
const staticTimelineSets = {} as EventTimelineSet;
|
||||||
|
const staticRoom = {
|
||||||
|
getUnfilteredTimelineSet: () => staticTimelineSets,
|
||||||
|
} as any as Room; // partial
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// TODO: Use utility functions to create test rooms and clients
|
||||||
|
client = <MatrixClient>{
|
||||||
|
getRoom: (roomId: string) => {
|
||||||
|
if (roomId === branchRoomId) {
|
||||||
|
return staticRoom;
|
||||||
|
} else {
|
||||||
|
throw new Error("Unexpected fetch for unknown room");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
indexEvent = {
|
||||||
|
getRoomId: () => branchRoomId,
|
||||||
|
getStateKey: () => fileEventId,
|
||||||
|
};
|
||||||
|
branch = new MSC3089Branch(client, indexEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should know the file event ID', () => {
|
||||||
|
expect(branch.id).toEqual(fileEventId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should know if the file is active or not', () => {
|
||||||
|
indexEvent.getContent = () => ({});
|
||||||
|
expect(branch.isActive).toBe(false);
|
||||||
|
indexEvent.getContent = () => ({ active: false });
|
||||||
|
expect(branch.isActive).toBe(false);
|
||||||
|
indexEvent.getContent = () => ({ active: true });
|
||||||
|
expect(branch.isActive).toBe(true);
|
||||||
|
indexEvent.getContent = () => ({ active: "true" }); // invalid boolean, inactive
|
||||||
|
expect(branch.isActive).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to delete the file', async () => {
|
||||||
|
const stateFn = jest.fn().mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
|
||||||
|
expect(roomId).toEqual(branchRoomId);
|
||||||
|
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
|
||||||
|
expect(content).toMatchObject({});
|
||||||
|
expect(content['active']).toBeUndefined();
|
||||||
|
expect(stateKey).toEqual(fileEventId);
|
||||||
|
|
||||||
|
return Promise.resolve(); // return value not used
|
||||||
|
});
|
||||||
|
client.sendStateEvent = stateFn;
|
||||||
|
|
||||||
|
const redactFn = jest.fn().mockImplementation((roomId: string, eventId: string) => {
|
||||||
|
expect(roomId).toEqual(branchRoomId);
|
||||||
|
expect(eventId).toEqual(fileEventId);
|
||||||
|
|
||||||
|
return Promise.resolve(); // return value not used
|
||||||
|
});
|
||||||
|
client.redactEvent = redactFn;
|
||||||
|
|
||||||
|
await branch.delete();
|
||||||
|
|
||||||
|
expect(stateFn).toHaveBeenCalledTimes(1);
|
||||||
|
expect(redactFn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should know its name', async () => {
|
||||||
|
const name = "My File.txt";
|
||||||
|
indexEvent.getContent = () => ({ active: true, name: name });
|
||||||
|
|
||||||
|
const res = branch.getName();
|
||||||
|
|
||||||
|
expect(res).toEqual(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to change its name', async () => {
|
||||||
|
const name = "My File.txt";
|
||||||
|
indexEvent.getContent = () => ({ active: true, retained: true });
|
||||||
|
const stateFn = jest.fn().mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
|
||||||
|
expect(roomId).toEqual(branchRoomId);
|
||||||
|
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
|
||||||
|
expect(content).toMatchObject({
|
||||||
|
retained: true, // canary for copying state
|
||||||
|
active: true,
|
||||||
|
name: name,
|
||||||
|
});
|
||||||
|
expect(stateKey).toEqual(fileEventId);
|
||||||
|
|
||||||
|
return Promise.resolve(); // return value not used
|
||||||
|
});
|
||||||
|
client.sendStateEvent = stateFn;
|
||||||
|
|
||||||
|
await branch.setName(name);
|
||||||
|
|
||||||
|
expect(stateFn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to return event information', async () => {
|
||||||
|
const mxcLatter = "example.org/file";
|
||||||
|
const fileContent = {isFile: "not quite", url: "mxc://" + mxcLatter};
|
||||||
|
const eventsArr = [
|
||||||
|
{getId: () => "$not-file", getContent: () => ({})},
|
||||||
|
{getId: () => fileEventId, getContent: () => ({file: fileContent})},
|
||||||
|
];
|
||||||
|
client.getEventTimeline = () => Promise.resolve({
|
||||||
|
getEvents: () => eventsArr,
|
||||||
|
}) as any as Promise<EventTimeline>; // partial
|
||||||
|
client.mxcUrlToHttp = (mxc: string) => {
|
||||||
|
expect(mxc).toEqual("mxc://" + mxcLatter);
|
||||||
|
return `https://example.org/_matrix/media/v1/download/${mxcLatter}`;
|
||||||
|
};
|
||||||
|
client.decryptEventIfNeeded = () => Promise.resolve();
|
||||||
|
|
||||||
|
const res = await branch.getFileInfo();
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(res).toMatchObject({
|
||||||
|
info: fileContent,
|
||||||
|
// Escape regex from MDN guides: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
||||||
|
httpUrl: expect.stringMatching(`.+${mxcLatter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -17,13 +17,14 @@ limitations under the License.
|
|||||||
import { MatrixClient } from "../../../src";
|
import { MatrixClient } from "../../../src";
|
||||||
import { Room } from "../../../src/models/room";
|
import { Room } from "../../../src/models/room";
|
||||||
import { MatrixEvent } from "../../../src/models/event";
|
import { MatrixEvent } from "../../../src/models/event";
|
||||||
import { EventType } from "../../../src/@types/event";
|
import { EventType, MsgType, UNSTABLE_MSC3089_BRANCH, UNSTABLE_MSC3089_LEAF } from "../../../src/@types/event";
|
||||||
import {
|
import {
|
||||||
DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
|
DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
|
||||||
MSC3089TreeSpace,
|
MSC3089TreeSpace,
|
||||||
TreePermissions
|
TreePermissions
|
||||||
} from "../../../src/models/MSC3089TreeSpace";
|
} from "../../../src/models/MSC3089TreeSpace";
|
||||||
import { DEFAULT_ALPHABET } from "../../../src/utils";
|
import { DEFAULT_ALPHABET } from "../../../src/utils";
|
||||||
|
import { MockBlob } from "../../MockBlob";
|
||||||
|
|
||||||
describe("MSC3089TreeSpace", () => {
|
describe("MSC3089TreeSpace", () => {
|
||||||
let client: MatrixClient;
|
let client: MatrixClient;
|
||||||
@@ -37,8 +38,8 @@ describe("MSC3089TreeSpace", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// TODO: Use utility functions to create test rooms and clients
|
// TODO: Use utility functions to create test rooms and clients
|
||||||
client = <MatrixClient>{
|
client = <MatrixClient>{
|
||||||
getRoom: (roomId: string) => {
|
getRoom: (fetchRoomId: string) => {
|
||||||
if (roomId === roomId) {
|
if (fetchRoomId === roomId) {
|
||||||
return room;
|
return room;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Unexpected fetch for unknown room");
|
throw new Error("Unexpected fetch for unknown room");
|
||||||
@@ -735,4 +736,112 @@ describe("MSC3089TreeSpace", () => {
|
|||||||
expectOrder(d, 3);
|
expectOrder(d, 3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should upload files', async () => {
|
||||||
|
const mxc = "mxc://example.org/file";
|
||||||
|
const fileInfo = {
|
||||||
|
mimetype: "text/plain",
|
||||||
|
// other fields as required by encryption, but ignored here
|
||||||
|
};
|
||||||
|
const fileEventId = "$file";
|
||||||
|
const fileName = "My File.txt";
|
||||||
|
const fileContents = "This is a test file";
|
||||||
|
|
||||||
|
// Mock out Blob for the test environment
|
||||||
|
(<any>global).Blob = MockBlob;
|
||||||
|
|
||||||
|
const uploadFn = jest.fn().mockImplementation((contents: Blob, opts: any) => {
|
||||||
|
expect(contents).toBeInstanceOf(Blob);
|
||||||
|
expect(contents.size).toEqual(fileContents.length);
|
||||||
|
expect(opts).toMatchObject({
|
||||||
|
includeFilename: false,
|
||||||
|
onlyContentUri: true, // because the tests rely on this - we shouldn't really be testing for this.
|
||||||
|
});
|
||||||
|
return Promise.resolve(mxc);
|
||||||
|
});
|
||||||
|
client.uploadContent = uploadFn;
|
||||||
|
|
||||||
|
const sendMsgFn = jest.fn().mockImplementation((roomId: string, contents: any) => {
|
||||||
|
expect(roomId).toEqual(tree.roomId);
|
||||||
|
expect(contents).toMatchObject({
|
||||||
|
msgtype: MsgType.File,
|
||||||
|
body: fileName,
|
||||||
|
url: mxc,
|
||||||
|
file: fileInfo,
|
||||||
|
[UNSTABLE_MSC3089_LEAF.unstable]: {}, // test to ensure we're definitely using unstable
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.resolve({event_id: fileEventId}); // eslint-disable-line camelcase
|
||||||
|
});
|
||||||
|
client.sendMessage = sendMsgFn;
|
||||||
|
|
||||||
|
const sendStateFn = jest.fn().mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
|
||||||
|
expect(roomId).toEqual(tree.roomId);
|
||||||
|
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
|
||||||
|
expect(stateKey).toEqual(fileEventId);
|
||||||
|
expect(content).toMatchObject({
|
||||||
|
active: true,
|
||||||
|
name: fileName,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.resolve(); // return value not used.
|
||||||
|
});
|
||||||
|
client.sendStateEvent = sendStateFn;
|
||||||
|
|
||||||
|
const buf = Uint8Array.from(Array.from(fileContents).map((_, i) => fileContents.charCodeAt(i)));
|
||||||
|
|
||||||
|
// We clone the file info just to make sure it doesn't get mutated for the test.
|
||||||
|
await tree.createFile(fileName, buf, Object.assign({}, fileInfo));
|
||||||
|
|
||||||
|
expect(uploadFn).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sendMsgFn).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sendStateFn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support getting files', () => {
|
||||||
|
const fileEventId = "$file";
|
||||||
|
const fileEvent = {forTest: true}; // MatrixEvent mock
|
||||||
|
room.currentState = {
|
||||||
|
getStateEvents: (eventType: string, stateKey?: string) => {
|
||||||
|
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
|
||||||
|
expect(stateKey).toEqual(fileEventId);
|
||||||
|
return fileEvent;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const file = tree.getFile(fileEventId);
|
||||||
|
expect(file).toBeDefined();
|
||||||
|
expect(file.indexEvent).toBe(fileEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return falsy for unknown files', () => {
|
||||||
|
const fileEventId = "$file";
|
||||||
|
room.currentState = {
|
||||||
|
getStateEvents: (eventType: string, stateKey?: string) => {
|
||||||
|
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
|
||||||
|
expect(stateKey).toEqual(fileEventId);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const file = tree.getFile(fileEventId);
|
||||||
|
expect(file).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list files', () => {
|
||||||
|
const firstFile = {getContent: () => ({active: true})};
|
||||||
|
const secondFile = {getContent: () => ({active: false})}; // deliberately inactive
|
||||||
|
room.currentState = {
|
||||||
|
getStateEvents: (eventType: string, stateKey?: string) => {
|
||||||
|
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
|
||||||
|
expect(stateKey).toBeUndefined();
|
||||||
|
return [firstFile, secondFile];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = tree.listFiles();
|
||||||
|
expect(files).toBeDefined();
|
||||||
|
expect(files.length).toEqual(1);
|
||||||
|
expect(files[0].indexEvent).toBe(firstFile);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -130,3 +130,25 @@ export const UNSTABLE_MSC3089_TREE_SUBTYPE = new UnstableValue("m.data_tree", "o
|
|||||||
* eventual removal.
|
* eventual removal.
|
||||||
*/
|
*/
|
||||||
export const UNSTABLE_MSC3089_LEAF = new UnstableValue("m.leaf", "org.matrix.msc3089.leaf");
|
export const UNSTABLE_MSC3089_LEAF = new UnstableValue("m.leaf", "org.matrix.msc3089.leaf");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Branch (Leaf Reference) type for the index approach in a
|
||||||
|
* [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) space-room. Note that this reference is
|
||||||
|
* UNSTABLE and subject to breaking changes, including its eventual removal.
|
||||||
|
*/
|
||||||
|
export const UNSTABLE_MSC3089_BRANCH = new UnstableValue("m.branch", "org.matrix.msc3089.branch");
|
||||||
|
|
||||||
|
export interface IEncryptedFile {
|
||||||
|
url: string;
|
||||||
|
mimetype?: string;
|
||||||
|
key: {
|
||||||
|
alg: string;
|
||||||
|
key_ops: string[]; // eslint-disable-line camelcase
|
||||||
|
kty: string;
|
||||||
|
k: string;
|
||||||
|
ext: boolean;
|
||||||
|
};
|
||||||
|
iv: string;
|
||||||
|
hashes: {[alg: string]: string};
|
||||||
|
v: string;
|
||||||
|
}
|
||||||
|
@@ -4295,7 +4295,7 @@ export class MatrixClient extends EventEmitter {
|
|||||||
* {@link module:models/event-timeline~EventTimeline} including the given
|
* {@link module:models/event-timeline~EventTimeline} including the given
|
||||||
* event
|
* event
|
||||||
*/
|
*/
|
||||||
public getEventTimeline(timelineSet: EventTimelineSet, eventId: string): EventTimeline {
|
public getEventTimeline(timelineSet: EventTimelineSet, eventId: string): Promise<EventTimeline> {
|
||||||
// don't allow any timeline support unless it's been enabled.
|
// don't allow any timeline support unless it's been enabled.
|
||||||
if (!this.timelineSupport) {
|
if (!this.timelineSupport) {
|
||||||
throw new Error("timeline support is disabled. Set the 'timelineSupport'" +
|
throw new Error("timeline support is disabled. Set the 'timelineSupport'" +
|
||||||
|
102
src/models/MSC3089Branch.ts
Normal file
102
src/models/MSC3089Branch.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 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 { MatrixClient } from "../client";
|
||||||
|
import { IEncryptedFile, UNSTABLE_MSC3089_BRANCH } from "../@types/event";
|
||||||
|
import { MatrixEvent } from "./event";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
export class MSC3089Branch {
|
||||||
|
public constructor(private client: MatrixClient, public readonly indexEvent: MatrixEvent) {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file ID.
|
||||||
|
*/
|
||||||
|
public get id(): string {
|
||||||
|
return this.indexEvent.getStateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this branch is active/valid.
|
||||||
|
*/
|
||||||
|
public get isActive(): boolean {
|
||||||
|
return this.indexEvent.getContent()["active"] === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get roomId(): string {
|
||||||
|
return this.indexEvent.getRoomId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the file from the tree.
|
||||||
|
* @returns {Promise<void>} Resolves when complete.
|
||||||
|
*/
|
||||||
|
public async delete(): Promise<void> {
|
||||||
|
await this.client.sendStateEvent(this.roomId, UNSTABLE_MSC3089_BRANCH.name, {}, this.id);
|
||||||
|
await this.client.redactEvent(this.roomId, this.id);
|
||||||
|
|
||||||
|
// TODO: Delete edit history as well
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name for this file.
|
||||||
|
* @returns {string} The name, or "Unnamed File" if unknown.
|
||||||
|
*/
|
||||||
|
public getName(): string {
|
||||||
|
return this.indexEvent.getContent()['name'] || "Unnamed File";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name for this file.
|
||||||
|
* @param {string} name The new name for this file.
|
||||||
|
* @returns {Promise<void>} Resolves when complete.
|
||||||
|
*/
|
||||||
|
public setName(name: string): Promise<void> {
|
||||||
|
return this.client.sendStateEvent(this.roomId, UNSTABLE_MSC3089_BRANCH.name, {
|
||||||
|
...this.indexEvent.getContent(),
|
||||||
|
name: name,
|
||||||
|
}, this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets information about the file needed to download it.
|
||||||
|
* @returns {Promise<{info: IEncryptedFile, httpUrl: string}>} Information about the file.
|
||||||
|
*/
|
||||||
|
public async getFileInfo(): Promise<{ info: IEncryptedFile, httpUrl: string }> {
|
||||||
|
const room = this.client.getRoom(this.roomId);
|
||||||
|
if (!room) throw new Error("Unknown room");
|
||||||
|
|
||||||
|
const timeline = await this.client.getEventTimeline(room.getUnfilteredTimelineSet(), this.id);
|
||||||
|
if (!timeline) throw new Error("Failed to get timeline for room event");
|
||||||
|
|
||||||
|
const event = timeline.getEvents().find(e => e.getId() === this.id);
|
||||||
|
if (!event) throw new Error("Failed to find event");
|
||||||
|
|
||||||
|
// Sometimes the event context doesn't decrypt for us, so do that.
|
||||||
|
await this.client.decryptEventIfNeeded(event, {emit: false, isRetry: false});
|
||||||
|
|
||||||
|
const file = event.getContent()['file'];
|
||||||
|
const httpUrl = this.client.mxcUrlToHttp(file['url']);
|
||||||
|
|
||||||
|
return { info: file, httpUrl: httpUrl };
|
||||||
|
}
|
||||||
|
}
|
@@ -15,11 +15,12 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixClient } from "../client";
|
import { MatrixClient } from "../client";
|
||||||
import { EventType } from "../@types/event";
|
import { EventType, IEncryptedFile, MsgType, UNSTABLE_MSC3089_BRANCH, UNSTABLE_MSC3089_LEAF } from "../@types/event";
|
||||||
import { Room } from "./room";
|
import { Room } from "./room";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import { MatrixEvent } from "./event";
|
import { MatrixEvent } from "./event";
|
||||||
import { averageBetweenStrings, DEFAULT_ALPHABET, nextString, prevString } from "../utils";
|
import { averageBetweenStrings, DEFAULT_ALPHABET, nextString, prevString } from "../utils";
|
||||||
|
import { MSC3089Branch } from "./MSC3089Branch";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The recommended defaults for a tree space's power levels. Note that this
|
* The recommended defaults for a tree space's power levels. Note that this
|
||||||
@@ -180,7 +181,7 @@ export class MSC3089TreeSpace {
|
|||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
try {
|
try {
|
||||||
const tree = this.client.unstableGetFileTreeSpace(child.getStateKey());
|
const tree = this.client.unstableGetFileTreeSpace(child.getStateKey());
|
||||||
trees.push(tree);
|
if (tree) trees.push(tree);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn("Unable to create tree space instance for listing. Are we joined?", e);
|
logger.warn("Unable to create tree space instance for listing. Are we joined?", e);
|
||||||
}
|
}
|
||||||
@@ -378,4 +379,51 @@ export class MSC3089TreeSpace {
|
|||||||
order: newOrder,
|
order: newOrder,
|
||||||
}, this.roomId);
|
}, this.roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates (uploads) a new file to this tree. The file must have already been encrypted for the room.
|
||||||
|
* @param {string} name The name of the file.
|
||||||
|
* @param {ArrayBuffer} encryptedContents The encrypted contents.
|
||||||
|
* @param {Partial<IEncryptedFile>} info The encrypted file information.
|
||||||
|
* @returns {Promise<void>} Resolves when uploaded.
|
||||||
|
*/
|
||||||
|
public async createFile(name: string, encryptedContents: ArrayBuffer, info: Partial<IEncryptedFile>): Promise<void> {
|
||||||
|
const mxc = await this.client.uploadContent(new Blob([encryptedContents]), {
|
||||||
|
includeFilename: false,
|
||||||
|
onlyContentUri: true,
|
||||||
|
});
|
||||||
|
info.url = mxc;
|
||||||
|
|
||||||
|
const res = await this.client.sendMessage(this.roomId, {
|
||||||
|
msgtype: MsgType.File,
|
||||||
|
body: name,
|
||||||
|
url: mxc,
|
||||||
|
file: info,
|
||||||
|
[UNSTABLE_MSC3089_LEAF.name]: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.client.sendStateEvent(this.roomId, UNSTABLE_MSC3089_BRANCH.name, {
|
||||||
|
active: true,
|
||||||
|
name: name,
|
||||||
|
}, res['event_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a file from the tree.
|
||||||
|
* @param {string} fileEventId The event ID of the file.
|
||||||
|
* @returns {MSC3089Branch} The file, or falsy if not found.
|
||||||
|
*/
|
||||||
|
public getFile(fileEventId: string): MSC3089Branch {
|
||||||
|
const branch = this.room.currentState.getStateEvents(UNSTABLE_MSC3089_BRANCH.name, fileEventId);
|
||||||
|
return branch ? new MSC3089Branch(this.client, branch) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an array of all known files for the tree.
|
||||||
|
* @returns {MSC3089Branch[]} The known files. May be empty, but not null.
|
||||||
|
*/
|
||||||
|
public listFiles(): MSC3089Branch[] {
|
||||||
|
const branches = this.room.currentState.getStateEvents(UNSTABLE_MSC3089_BRANCH.name) ?? [];
|
||||||
|
return branches.map(e => new MSC3089Branch(this.client, e)).filter(b => b.isActive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user