Threads, timelines, auth
This commit is contained in:
parent
3a5c212f51
commit
b0cf2ad840
38 changed files with 878 additions and 343 deletions
|
@ -2,6 +2,7 @@
|
||||||
"imports": {
|
"imports": {
|
||||||
"nanoid": "https://deno.land/x/nanoid@v3.0.0/mod.ts",
|
"nanoid": "https://deno.land/x/nanoid@v3.0.0/mod.ts",
|
||||||
"events": "https://deno.land/x/events@v1.0.0/mod.ts",
|
"events": "https://deno.land/x/events@v1.0.0/mod.ts",
|
||||||
"typed-emitter": "npm:typed-emitter"
|
"typed-emitter": "npm:typed-emitter",
|
||||||
|
"@matrix-org/matrix-sdk-crypto-wasm": "npm:@matrix-org/matrix-sdk-crypto-wasm"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
21
dist/src/api.js
vendored
21
dist/src/api.js
vendored
|
@ -1,3 +1,24 @@
|
||||||
// all the types for the api
|
// all the types for the api
|
||||||
export {};
|
export {};
|
||||||
|
// export interface WellKnownClient {
|
||||||
|
// baseUrl: string,
|
||||||
|
// }
|
||||||
|
// export interface ServerConfig {
|
||||||
|
// components: {
|
||||||
|
// api: string,
|
||||||
|
// sync: string,
|
||||||
|
// media: string,
|
||||||
|
// voip: string,
|
||||||
|
// },
|
||||||
|
// server: {
|
||||||
|
// name: string,
|
||||||
|
// version: string,
|
||||||
|
// url: string,
|
||||||
|
// },
|
||||||
|
// admins: Array<{
|
||||||
|
// user_id: string,
|
||||||
|
// email_address?: string,
|
||||||
|
// purposes: Array<"m.admin" | "m.moderation" | "m.security">,
|
||||||
|
// }>,
|
||||||
|
// }
|
||||||
//# sourceMappingURL=api.js.map
|
//# sourceMappingURL=api.js.map
|
2
dist/src/api.js.map
vendored
2
dist/src/api.js.map
vendored
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"AAAA,4BAA4B"}
|
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"AAAA,4BAA4B;;AAwP5B,qCAAqC;AACrC,qBAAqB;AACrB,IAAI;AAEJ,kCAAkC;AAClC,kBAAkB;AAClB,mBAAmB;AACnB,oBAAoB;AACpB,qBAAqB;AACrB,oBAAoB;AACpB,OAAO;AACP,cAAc;AACd,oBAAoB;AACpB,uBAAuB;AACvB,mBAAmB;AACnB,OAAO;AACP,oBAAoB;AACpB,uBAAuB;AACvB,8BAA8B;AAC9B,kEAAkE;AAClE,QAAQ;AACR,IAAI"}
|
10
dist/src/client.d.ts
vendored
10
dist/src/client.d.ts
vendored
|
@ -3,13 +3,13 @@ import { Network } from "./net.js";
|
||||||
import { Room } from "./room.js";
|
import { Room } from "./room.js";
|
||||||
import { Connection } from "./sync.js";
|
import { Connection } from "./sync.js";
|
||||||
import TypedEmitter from "typed-emitter";
|
import TypedEmitter from "typed-emitter";
|
||||||
interface ClientConfig {
|
export interface ClientConfig {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
}
|
}
|
||||||
type ClientState = {
|
export type ClientState = {
|
||||||
state: "stop";
|
state: "stop";
|
||||||
} | {
|
} | {
|
||||||
state: "sync";
|
state: "sync";
|
||||||
|
@ -21,6 +21,9 @@ type ClientState = {
|
||||||
} | {
|
} | {
|
||||||
state: "retry";
|
state: "retry";
|
||||||
backoff: number;
|
backoff: number;
|
||||||
|
} | {
|
||||||
|
state: "logout";
|
||||||
|
soft: boolean;
|
||||||
};
|
};
|
||||||
type ClientEvents = {
|
type ClientEvents = {
|
||||||
state: (state: ClientState) => void;
|
state: (state: ClientState) => void;
|
||||||
|
@ -30,7 +33,7 @@ type ClientEvents = {
|
||||||
accountData: (type: string, content: string) => void;
|
accountData: (type: string, content: string) => void;
|
||||||
toDevice: (event: ApiDeviceEvent) => void;
|
toDevice: (event: ApiDeviceEvent) => void;
|
||||||
};
|
};
|
||||||
type RoomList = {
|
export type RoomList = {
|
||||||
count: number;
|
count: number;
|
||||||
rooms: Array<Room>;
|
rooms: Array<Room>;
|
||||||
};
|
};
|
||||||
|
@ -69,5 +72,6 @@ export declare class Client extends Client_base {
|
||||||
private setState;
|
private setState;
|
||||||
start(): void;
|
start(): void;
|
||||||
stop(): void;
|
stop(): void;
|
||||||
|
logout(): Promise<void>;
|
||||||
}
|
}
|
||||||
export {};
|
export {};
|
||||||
|
|
7
dist/src/client.js
vendored
7
dist/src/client.js
vendored
|
@ -119,9 +119,14 @@ export class Client extends EventEmitter {
|
||||||
}
|
}
|
||||||
// Stop receiving events from /sync.
|
// Stop receiving events from /sync.
|
||||||
stop() {
|
stop() {
|
||||||
this.conn.abort();
|
this.conn.abort("stop sync");
|
||||||
this.conn = new Connection(this);
|
this.conn = new Connection(this);
|
||||||
this.setState({ state: "stop" });
|
this.setState({ state: "stop" });
|
||||||
}
|
}
|
||||||
|
async logout() {
|
||||||
|
this.stop();
|
||||||
|
await this.net.authLogout();
|
||||||
|
this.setState({ state: "logout", soft: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//# sourceMappingURL=client.js.map
|
//# sourceMappingURL=client.js.map
|
2
dist/src/client.js.map
vendored
2
dist/src/client.js.map
vendored
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
10
dist/src/index.d.ts
vendored
10
dist/src/index.d.ts
vendored
|
@ -1,6 +1,8 @@
|
||||||
export { Client } from "./client.js";
|
export { Client } from "./client.js";
|
||||||
|
export { Setup } from "./setup.js";
|
||||||
export { ThreadPaginator } from "./room.js";
|
export { ThreadPaginator } from "./room.js";
|
||||||
export type { Event, StateEvent } from "./event.js";
|
export { Event, StateEvent } from "./event.js";
|
||||||
export type { Room } from "./room.js";
|
export { Room } from "./room.js";
|
||||||
export type { Thread } from "./thread.js";
|
export { Thread } from "./thread.js";
|
||||||
export type { Timeline } from "./timeline.js";
|
export type { RoomList, ClientState } from "./client.js";
|
||||||
|
export type { UserId, RoomId, EventId } from "./api.js";
|
||||||
|
|
4
dist/src/index.js
vendored
4
dist/src/index.js
vendored
|
@ -1,3 +1,7 @@
|
||||||
export { Client } from "./client.js";
|
export { Client } from "./client.js";
|
||||||
|
export { Setup } from "./setup.js";
|
||||||
export { ThreadPaginator } from "./room.js";
|
export { ThreadPaginator } from "./room.js";
|
||||||
|
export { Event, StateEvent } from "./event.js";
|
||||||
|
export { Room } from "./room.js";
|
||||||
|
export { Thread } from "./thread.js";
|
||||||
//# sourceMappingURL=index.js.map
|
//# sourceMappingURL=index.js.map
|
2
dist/src/index.js.map
vendored
2
dist/src/index.js.map
vendored
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC"}
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
3
dist/src/net.d.ts
vendored
3
dist/src/net.d.ts
vendored
|
@ -47,5 +47,8 @@ export declare class Network {
|
||||||
sendEvent(roomId: t.RoomId, type: string, txnId: string, content: any): Promise<t.SendResponse>;
|
sendEvent(roomId: t.RoomId, type: string, txnId: string, content: any): Promise<t.SendResponse>;
|
||||||
sendState(roomId: t.RoomId, type: string, stateKey: string, content: any): Promise<t.SendResponse>;
|
sendState(roomId: t.RoomId, type: string, stateKey: string, content: any): Promise<t.SendResponse>;
|
||||||
ack(acks: t.AckRequest): Promise<any>;
|
ack(acks: t.AckRequest): Promise<any>;
|
||||||
|
authLogin(): Promise<void>;
|
||||||
|
authRegister(): Promise<void>;
|
||||||
|
authLogout(): Promise<any>;
|
||||||
}
|
}
|
||||||
export {};
|
export {};
|
||||||
|
|
44
dist/src/net.js
vendored
44
dist/src/net.js
vendored
|
@ -76,8 +76,19 @@ export class Network {
|
||||||
body: (isJson ? JSON.stringify(options.body) : options.body),
|
body: (isJson ? JSON.stringify(options.body) : options.body),
|
||||||
...options.extra,
|
...options.extra,
|
||||||
});
|
});
|
||||||
if (!req.ok)
|
// FIXME: handle uiaa and make this less awful
|
||||||
|
if (!req.ok) {
|
||||||
|
// const response = await req.json();
|
||||||
|
// throw { response, error: new Error(`Request failed: ${await req.text()}`) };
|
||||||
|
if (req.status === 401) {
|
||||||
|
this.client.stop();
|
||||||
|
this.client.setState({ state: "logout" });
|
||||||
throw new Error(`Request failed: ${await req.text()}`);
|
throw new Error(`Request failed: ${await req.text()}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error(`Request failed: ${await req.text()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (options.raw)
|
if (options.raw)
|
||||||
return req.body;
|
return req.body;
|
||||||
return req.json();
|
return req.json();
|
||||||
|
@ -188,5 +199,36 @@ export class Network {
|
||||||
body: acks,
|
body: acks,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async authLogin() {
|
||||||
|
throw new Error("todo!");
|
||||||
|
// return this.fetch({
|
||||||
|
// method: "POST",
|
||||||
|
// path: `/_matrix/client/v3/login`,
|
||||||
|
// body: {},
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
async authRegister() {
|
||||||
|
throw new Error("todo!");
|
||||||
|
// return this.fetch({
|
||||||
|
// method: "POST",
|
||||||
|
// path: `/_matrix/client/v3/login`,
|
||||||
|
// body: {},
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
// public async authGuest() {
|
||||||
|
// throw new Error("todo!")
|
||||||
|
// // return this.fetch({
|
||||||
|
// // method: "POST",
|
||||||
|
// // path: `/_matrix/client/v3/login`,
|
||||||
|
// // body: {},
|
||||||
|
// // });
|
||||||
|
// }
|
||||||
|
async authLogout() {
|
||||||
|
return this.fetch({
|
||||||
|
method: "POST",
|
||||||
|
path: `/_matrix/client/v3/logout`,
|
||||||
|
body: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//# sourceMappingURL=net.js.map
|
//# sourceMappingURL=net.js.map
|
2
dist/src/net.js.map
vendored
2
dist/src/net.js.map
vendored
File diff suppressed because one or more lines are too long
8
dist/src/room.d.ts
vendored
8
dist/src/room.d.ts
vendored
|
@ -1,12 +1,10 @@
|
||||||
import TypedEmitter from "typed-emitter";
|
import TypedEmitter from "typed-emitter";
|
||||||
import { ApiEphemeralEvent, EventId, IncludeThreads, SyncResponseRoom, Unreads } from "./api.js";
|
import { EventId, IncludeThreads, SyncResponseRoom, Unreads } from "./api.js";
|
||||||
import { Client } from "./client.js";
|
import { Client } from "./client.js";
|
||||||
import { Event, StateEvent } from "./event.js";
|
import { Event, StateEvent } from "./event.js";
|
||||||
import { TimelineSet } from "./timeline.js";
|
import { RoomTimelineSet } from "./timeline.js";
|
||||||
import { Thread } from "./thread.js";
|
import { Thread } from "./thread.js";
|
||||||
type RoomEvents = {
|
type RoomEvents = {
|
||||||
timeline: (event: Event) => void;
|
|
||||||
ephemeral: (event: ApiEphemeralEvent) => void;
|
|
||||||
thread: (thread: Thread) => void;
|
thread: (thread: Thread) => void;
|
||||||
state: (event: StateEvent) => void;
|
state: (event: StateEvent) => void;
|
||||||
notifications: (notifs: Unreads) => void;
|
notifications: (notifs: Unreads) => void;
|
||||||
|
@ -21,7 +19,7 @@ export declare class Room extends Room_base {
|
||||||
client: Client;
|
client: Client;
|
||||||
id: string;
|
id: string;
|
||||||
private state;
|
private state;
|
||||||
timelines: TimelineSet;
|
timelines: RoomTimelineSet;
|
||||||
events: Map<EventId, Event>;
|
events: Map<EventId, Event>;
|
||||||
threads: RoomThreads;
|
threads: RoomThreads;
|
||||||
constructor(client: Client, id: string, data: SyncResponseRoom);
|
constructor(client: Client, id: string, data: SyncResponseRoom);
|
||||||
|
|
23
dist/src/room.js
vendored
23
dist/src/room.js
vendored
|
@ -1,6 +1,6 @@
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import { Event, StateEvent } from "./event.js";
|
import { Event, StateEvent } from "./event.js";
|
||||||
import { TimelineSet } from "./timeline.js";
|
import { RoomTimelineSet } from "./timeline.js";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { Thread } from "./thread.js";
|
import { Thread } from "./thread.js";
|
||||||
export class Room extends EventEmitter {
|
export class Room extends EventEmitter {
|
||||||
|
@ -43,7 +43,7 @@ export class Room extends EventEmitter {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
writable: true,
|
writable: true,
|
||||||
value: new TimelineSet(this)
|
value: new RoomTimelineSet(this)
|
||||||
});
|
});
|
||||||
Object.defineProperty(this, "events", {
|
Object.defineProperty(this, "events", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
|
@ -76,7 +76,20 @@ export class Room extends EventEmitter {
|
||||||
const events = data.timeline
|
const events = data.timeline
|
||||||
.filter(raw => !this.events.has(raw.event_id))
|
.filter(raw => !this.events.has(raw.event_id))
|
||||||
.map(raw => new Event(this, raw));
|
.map(raw => new Event(this, raw));
|
||||||
this.timelines._appendEvents(events);
|
for (const event of events) {
|
||||||
|
this.events.set(event.id, event);
|
||||||
|
this.timelines.live._eventList.push(...events);
|
||||||
|
for (const event of events)
|
||||||
|
this.timelines.live.emit("timelineAppend", event);
|
||||||
|
const threadId = event.content["m.relations"]?.find((rel) => rel.rel_type === "m.thread")?.event_id;
|
||||||
|
const thread = this.threads.get(threadId);
|
||||||
|
if (thread) {
|
||||||
|
thread.messageCount++;
|
||||||
|
thread.latestEvent = event;
|
||||||
|
thread.timelines.live._eventList.push(event);
|
||||||
|
thread.timelines.live.emit("timelineAppend", event);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getState(type, stateKey = "") {
|
getState(type, stateKey = "") {
|
||||||
|
@ -97,11 +110,11 @@ export class Room extends EventEmitter {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
const sub = (event) => {
|
const sub = (event) => {
|
||||||
if (event.unsigned.transaction_id === txn) {
|
if (event.unsigned.transaction_id === txn) {
|
||||||
this.off("timeline", sub);
|
this.timelines.live.off("timelineAppend", sub);
|
||||||
res(event);
|
res(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.on("timeline", sub);
|
this.timelines.live.on("timelineAppend", sub);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async leave(reason) {
|
async leave(reason) {
|
||||||
|
|
2
dist/src/room.js.map
vendored
2
dist/src/room.js.map
vendored
File diff suppressed because one or more lines are too long
32
dist/src/setup.d.ts
vendored
32
dist/src/setup.d.ts
vendored
|
@ -1,2 +1,32 @@
|
||||||
declare class Setup {
|
import { Client } from "./client.js";
|
||||||
|
type Flows = {
|
||||||
|
"m.login.password": {
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
"m.login.recaptcha": {
|
||||||
|
response: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
type Identifier = {
|
||||||
|
type: "m.id.user";
|
||||||
|
user: string;
|
||||||
|
} | {
|
||||||
|
type: string;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
export declare class Setup {
|
||||||
|
target: "login" | "register" | "guest";
|
||||||
|
deviceName: string;
|
||||||
|
baseUrl: string | null;
|
||||||
|
endpoint: string | null;
|
||||||
|
session: string | null;
|
||||||
|
flows: Array<string> | null;
|
||||||
|
params: Record<string, string> | null;
|
||||||
|
identifier: Identifier | null;
|
||||||
|
constructor(target: "login" | "register" | "guest", deviceName?: string);
|
||||||
|
useDomain(_domain: string): Promise<void>;
|
||||||
|
useBaseUrl(baseUrl: string): Promise<void>;
|
||||||
|
useIdentifier(id: Identifier): void;
|
||||||
|
exec<K extends keyof Flows>(type: K, options: Flows[K]): Promise<Client | null>;
|
||||||
}
|
}
|
||||||
|
export {};
|
||||||
|
|
117
dist/src/setup.js
vendored
117
dist/src/setup.js
vendored
|
@ -1,5 +1,118 @@
|
||||||
"use strict";
|
|
||||||
// Used for initiating a client, handling well-known and authentication.
|
// Used for initiating a client, handling well-known and authentication.
|
||||||
class Setup {
|
/*
|
||||||
|
I'd love to rip this out and replace this with oidc a la "matrix 2.0",
|
||||||
|
but I'm not sure if that's a good idea yet.
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
1. It will be more secure since users won't enter credentials into random clients.
|
||||||
|
2. It separates concerns.
|
||||||
|
2.1 client and server devs can focus on matrix instead of security best practices
|
||||||
|
2.2 server admins can add new auth mechanisms without needing spec changes
|
||||||
|
3. I can use existing sdks and libraries to do authentication - rolling your own auth is a bad idea.
|
||||||
|
|
||||||
|
Drawbacks:
|
||||||
|
|
||||||
|
1. The ux will be worse since there's now a separtate place to do auth (login, register, manage sessions).
|
||||||
|
2. It will be much more complex. Server admins now have extra stuff to setup, maintain, and debug. Arguably, it's more difficult for client devs to use as well.
|
||||||
|
3. It forces users to have some kind of web browser to login.
|
||||||
|
*/
|
||||||
|
// TODO: properly integrate with net.js
|
||||||
|
import { Client } from "./client.js";
|
||||||
|
export class Setup {
|
||||||
|
constructor(target, deviceName = "unnamed (sdk-ts)") {
|
||||||
|
Object.defineProperty(this, "target", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: target
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "deviceName", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: deviceName
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "baseUrl", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: null
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "endpoint", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: null
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "session", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: null
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "flows", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: null
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "params", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: null
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "identifier", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: null
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
async useDomain(_domain) {
|
||||||
|
throw new Error("todo!");
|
||||||
|
}
|
||||||
|
async useBaseUrl(baseUrl) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
this.endpoint = `${baseUrl}/_matrix/client/v3/${this.target === "login" ? "login" : "register"}${this.target === "guest" ? "?kind=guest" : ""}`;
|
||||||
|
const res = await fetch(this.endpoint).then(res => res.json());
|
||||||
|
this.flows = res.flows;
|
||||||
|
this.params = res.params;
|
||||||
|
}
|
||||||
|
useIdentifier(id) {
|
||||||
|
this.identifier = id;
|
||||||
|
}
|
||||||
|
async exec(type, options) {
|
||||||
|
if (!this.endpoint)
|
||||||
|
throw new Error("you need to call useBaseUrl first");
|
||||||
|
const res = await fetch(this.endpoint, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
identifier: this.identifier,
|
||||||
|
initial_device_display_name: this.deviceName,
|
||||||
|
session: this.session ?? undefined,
|
||||||
|
type: type,
|
||||||
|
...options,
|
||||||
|
}),
|
||||||
|
method: "POST",
|
||||||
|
}).then(res => res.json());
|
||||||
|
if (res.status === 400) {
|
||||||
|
this.session = res.session;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new Client({
|
||||||
|
baseUrl: this.baseUrl,
|
||||||
|
token: res.access_token,
|
||||||
|
deviceId: res.device_id,
|
||||||
|
userId: res.user_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// const setup = new Setup("login");
|
||||||
|
// // setup.useIdentifier({ type: "m.id.user", user: "@localpart:server.tld" });
|
||||||
|
// setup.useIdentifier({ type: "m.id.user", user: "localpart" });
|
||||||
|
// await setup.useBaseUrl("http://localhost:6167");
|
||||||
|
// await setup.exec("m.login.password", { password: "a" });
|
||||||
//# sourceMappingURL=setup.js.map
|
//# sourceMappingURL=setup.js.map
|
2
dist/src/setup.js.map
vendored
2
dist/src/setup.js.map
vendored
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/setup.ts"],"names":[],"mappings":";AAAA,wEAAwE;AAExE,MAAM,KAAK;CACV"}
|
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/setup.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAExE;;;;;;;;;;;;;;;;;EAiBE;AAEF,uCAAuC;AAEvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAWrC,MAAM,OAAO,KAAK;IAQhB,YACS,MAAsC,EACtC,aAAqB,kBAAkB;QAD9C;;;;mBAAO,MAAM;WAAgC;QAC7C;;;;mBAAO,UAAU;WAA6B;QATzC;;;;mBAAyB,IAAI;WAAC;QAC9B;;;;mBAA0B,IAAI;WAAC;QAC/B;;;;mBAAyB,IAAI;WAAC;QAC9B;;;;mBAA8B,IAAI;WAAC;QACnC;;;;mBAAwC,IAAI;WAAC;QAC7C;;;;mBAAgC,IAAI;WAAC;IAKzC,CAAC;IAEJ,KAAK,CAAC,SAAS,CAAC,OAAe;QAC7B,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,OAAe;QAC9B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,GAAG,OAAO,sBAAsB,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAChJ,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,aAAa,CAAC,EAAc;QAC1B,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,IAAI,CAAwB,IAAO,EAAE,OAAiB;QAC1D,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACzE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;YACrC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,2BAA2B,EAAE,IAAI,CAAC,UAAU;gBAC5C,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,SAAS;gBAClC,IAAI,EAAE,IAAI;gBACV,GAAG,OAAO;aACX,CAAC;YACF,MAAM,EAAE,MAAM;SACf,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,MAAM,CAAC;gBAChB,OAAO,EAAE,IAAI,CAAC,OAAQ;gBACtB,KAAK,EAAE,GAAG,CAAC,YAAY;gBACvB,QAAQ,EAAE,GAAG,CAAC,SAAS;gBACvB,MAAM,EAAE,GAAG,CAAC,OAAO;aACpB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF;AAED,oCAAoC;AACpC,gFAAgF;AAChF,iEAAiE;AACjE,mDAAmD;AACnD,2DAA2D"}
|
2
dist/src/sync.js
vendored
2
dist/src/sync.js
vendored
|
@ -52,6 +52,8 @@ export class Connection {
|
||||||
}, this.controller.signal).catch((reason) => {
|
}, this.controller.signal).catch((reason) => {
|
||||||
if (reason === "update query")
|
if (reason === "update query")
|
||||||
return null;
|
return null;
|
||||||
|
if (reason === "stop sync")
|
||||||
|
return null;
|
||||||
throw reason;
|
throw reason;
|
||||||
});
|
});
|
||||||
if (!json)
|
if (!json)
|
||||||
|
|
2
dist/src/sync.js.map
vendored
2
dist/src/sync.js.map
vendored
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/sync.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/sync.ts"],"names":[],"mappings":"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"}
|
2
dist/src/thread.d.ts
vendored
2
dist/src/thread.d.ts
vendored
|
@ -2,6 +2,7 @@ import TypedEmitter from "typed-emitter";
|
||||||
import { EventId, Unreads } from "./api.js";
|
import { EventId, Unreads } from "./api.js";
|
||||||
import { Room } from "./room.js";
|
import { Room } from "./room.js";
|
||||||
import { Event } from "./event.js";
|
import { Event } from "./event.js";
|
||||||
|
import { ThreadTimelineSet } from "./timeline.js";
|
||||||
type ThreadEvents = {
|
type ThreadEvents = {
|
||||||
timeline: (event: Event) => void;
|
timeline: (event: Event) => void;
|
||||||
notifications: (notifs: Unreads) => void;
|
notifications: (notifs: Unreads) => void;
|
||||||
|
@ -11,6 +12,7 @@ export declare class Thread extends Thread_base {
|
||||||
baseEvent: Event;
|
baseEvent: Event;
|
||||||
room: Room;
|
room: Room;
|
||||||
id: string;
|
id: string;
|
||||||
|
timelines: ThreadTimelineSet;
|
||||||
participation: string;
|
participation: string;
|
||||||
messageCount: number;
|
messageCount: number;
|
||||||
latestEvent: Event;
|
latestEvent: Event;
|
||||||
|
|
7
dist/src/thread.js
vendored
7
dist/src/thread.js
vendored
|
@ -1,5 +1,6 @@
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import { Event } from "./event.js";
|
import { Event } from "./event.js";
|
||||||
|
import { ThreadTimelineSet } from "./timeline.js";
|
||||||
export class Thread extends EventEmitter {
|
export class Thread extends EventEmitter {
|
||||||
constructor(baseEvent) {
|
constructor(baseEvent) {
|
||||||
const threadRel = baseEvent.unsigned["m.relations"]?.["m.thread"];
|
const threadRel = baseEvent.unsigned["m.relations"]?.["m.thread"];
|
||||||
|
@ -24,6 +25,12 @@ export class Thread extends EventEmitter {
|
||||||
writable: true,
|
writable: true,
|
||||||
value: this.baseEvent.id
|
value: this.baseEvent.id
|
||||||
});
|
});
|
||||||
|
Object.defineProperty(this, "timelines", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: new ThreadTimelineSet(this)
|
||||||
|
});
|
||||||
Object.defineProperty(this, "participation", {
|
Object.defineProperty(this, "participation", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
|
2
dist/src/thread.js.map
vendored
2
dist/src/thread.js.map
vendored
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"thread.js","sourceRoot":"","sources":["../../src/thread.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAIlC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAUnC,MAAM,OAAO,MAAO,SAAS,YAAgE;IAQ3F,YAAmB,SAAgB;QACjC,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;QAClE,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAErF,KAAK,EAAE,CAAC;QAJE;;;;mBAAO,SAAS;WAAO;QAP5B;;;;mBAAa,IAAI,CAAC,SAAS,CAAC,IAAI;WAAC;QACjC;;;;mBAAK,IAAI,CAAC,SAAS,CAAC,EAAE;WAAC;QAEvB;;;;mBAAgB,eAAe;WAAC;QAChC;;;;;WAAqB;QACrB;;;;;WAAmB;QAQxB,MAAM,SAAS,GAAG,SAAS,CAAC,YAAY,CAAC;QACzC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC9D,MAAM,WAAW,GAAG,YAAY,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACpE,IAAI,CAAC,YAAY;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QACrE,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC;IACtC,CAAC;IAED,uCAAuC;IACvC,gDAAgD;IAChD,uEAAuE;IACvE,IAAI;IAEJ,sDAAsD;IACtD,sDAAsD;IACtD,IAAI;IAEJ,KAAK,CAAC,GAAG,CAAC,OAAiB;QACzB,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YAC7B,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;SACnF,CAAC,CAAC;IACL,CAAC;CACF"}
|
{"version":3,"file":"thread.js","sourceRoot":"","sources":["../../src/thread.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAIlC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAUlD,MAAM,OAAO,MAAO,SAAS,YAAgE;IAS3F,YAAmB,SAAgB;QACjC,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;QAClE,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAErF,KAAK,EAAE,CAAC;QAJE;;;;mBAAO,SAAS;WAAO;QAR5B;;;;mBAAa,IAAI,CAAC,SAAS,CAAC,IAAI;WAAC;QACjC;;;;mBAAK,IAAI,CAAC,SAAS,CAAC,EAAE;WAAC;QACvB;;;;mBAAY,IAAI,iBAAiB,CAAC,IAAI,CAAC;WAAC;QAExC;;;;mBAAgB,eAAe;WAAC;QAChC;;;;;WAAqB;QACrB;;;;;WAAmB;QAQxB,MAAM,SAAS,GAAG,SAAS,CAAC,YAAY,CAAC;QACzC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC9D,MAAM,WAAW,GAAG,YAAY,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACpE,IAAI,CAAC,YAAY;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QACrE,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC;IACtC,CAAC;IAED,uCAAuC;IACvC,gDAAgD;IAChD,uEAAuE;IACvE,IAAI;IAEJ,sDAAsD;IACtD,sDAAsD;IACtD,IAAI;IAEJ,KAAK,CAAC,GAAG,CAAC,OAAiB;QACzB,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YAC7B,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;SACnF,CAAC,CAAC;IACL,CAAC;CACF"}
|
58
dist/src/timeline.d.ts
vendored
58
dist/src/timeline.d.ts
vendored
|
@ -3,49 +3,55 @@ import { Room } from "./room.js";
|
||||||
import { Event } from "./event.js";
|
import { Event } from "./event.js";
|
||||||
import { Thread } from "./thread.js";
|
import { Thread } from "./thread.js";
|
||||||
import TypedEmitter from "typed-emitter";
|
import TypedEmitter from "typed-emitter";
|
||||||
type TimelineEvents = {
|
type TimelineEvents<T> = {
|
||||||
timelineUpdate: (batch: Array<Event>, toBeginning: boolean) => void;
|
timelineUpdate: (batch: Array<Event>, toBeginning: boolean) => void;
|
||||||
timelineAppend: (event: Event) => void;
|
timelineAppend: (event: Event) => void;
|
||||||
timelineReplace: (timeine: Timeline) => void;
|
timelineReplace: (timeine: T) => void;
|
||||||
ephemeral: (event: ApiEphemeralEvent) => void;
|
ephemeral: (event: ApiEphemeralEvent) => void;
|
||||||
};
|
};
|
||||||
export interface Timeline extends TypedEmitter<TimelineEvents> {
|
declare const Timeline_base: new () => TypedEmitter<TimelineEvents<any>>;
|
||||||
isAtBeginning: boolean;
|
declare abstract class Timeline extends Timeline_base {
|
||||||
isAtEnd: boolean;
|
|
||||||
getEvents(): Array<Event>;
|
|
||||||
paginate(dir: "f" | "b", limit: number): Promise<boolean>;
|
|
||||||
}
|
|
||||||
declare const RoomTimeline_base: new () => TypedEmitter<TimelineEvents>;
|
|
||||||
export declare class RoomTimeline extends RoomTimeline_base implements Timeline {
|
|
||||||
room: Room;
|
|
||||||
isAtBeginning: boolean;
|
isAtBeginning: boolean;
|
||||||
isAtEnd: boolean;
|
isAtEnd: boolean;
|
||||||
_eventList: Array<Event>;
|
_eventList: Array<Event>;
|
||||||
prevBatch: string | undefined;
|
prevBatch: string | undefined;
|
||||||
nextBatch: string | undefined;
|
nextBatch: string | undefined;
|
||||||
constructor(room: Room);
|
|
||||||
getEvents(): Array<Event>;
|
getEvents(): Array<Event>;
|
||||||
|
abstract paginate(dir: "f" | "b", limit: number): Promise<boolean>;
|
||||||
|
}
|
||||||
|
export declare class RoomTimeline extends Timeline implements TypedEmitter<TimelineEvents<RoomTimeline>> {
|
||||||
|
private timelineSet;
|
||||||
|
room: Room;
|
||||||
|
constructor(timelineSet: RoomTimelineSet, room: Room);
|
||||||
paginate(dir: "f" | "b", limit?: number): Promise<boolean>;
|
paginate(dir: "f" | "b", limit?: number): Promise<boolean>;
|
||||||
}
|
}
|
||||||
declare const ThreadTimeline_base: new () => TypedEmitter<TimelineEvents>;
|
export declare class ThreadTimeline extends Timeline implements TypedEmitter<TimelineEvents<ThreadTimeline>> {
|
||||||
export declare class ThreadTimeline extends ThreadTimeline_base implements Timeline {
|
private timelineSet;
|
||||||
thread: Thread;
|
thread: Thread;
|
||||||
isAtBeginning: boolean;
|
constructor(timelineSet: ThreadTimelineSet, thread: Thread);
|
||||||
isAtEnd: boolean;
|
|
||||||
_eventList: Array<Event>;
|
|
||||||
prevBatch: string | undefined;
|
|
||||||
nextBatch: string | undefined;
|
|
||||||
constructor(thread: Thread);
|
|
||||||
getEvents(): Array<Event>;
|
|
||||||
paginate(dir: "f" | "b", limit?: number): Promise<boolean>;
|
paginate(dir: "f" | "b", limit?: number): Promise<boolean>;
|
||||||
}
|
}
|
||||||
export declare class TimelineSet {
|
declare abstract class TimelineSet {
|
||||||
|
abstract timelines: Set<Timeline>;
|
||||||
|
abstract timelineMap: Map<EventId, Timeline>;
|
||||||
|
merge(events: Array<Event>): Timeline | null;
|
||||||
|
}
|
||||||
|
export declare class ThreadTimelineSet extends TimelineSet {
|
||||||
|
thread: Thread;
|
||||||
|
client: import("./client.js").Client;
|
||||||
|
live: ThreadTimeline;
|
||||||
|
timelines: Set<ThreadTimeline>;
|
||||||
|
timelineMap: Map<EventId, ThreadTimeline>;
|
||||||
|
constructor(thread: Thread);
|
||||||
|
fetch(at: EventId | "start" | "end", limit?: number): Promise<ThreadTimeline>;
|
||||||
|
}
|
||||||
|
export declare class RoomTimelineSet extends TimelineSet {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
client: import("./client.js").Client;
|
||||||
live: RoomTimeline;
|
live: RoomTimeline;
|
||||||
timelines: Set<Timeline>;
|
timelines: Set<RoomTimeline>;
|
||||||
|
timelineMap: Map<EventId, RoomTimeline>;
|
||||||
constructor(room: Room);
|
constructor(room: Room);
|
||||||
forThread(thread: Thread, at: EventId | "start" | "end"): Promise<ThreadTimeline>;
|
fetch(at: EventId | "start" | "end", limit?: number): Promise<RoomTimeline>;
|
||||||
forEvent(eventId: EventId): Promise<RoomTimeline>;
|
|
||||||
_appendEvents(events: Array<Event>): void;
|
|
||||||
}
|
}
|
||||||
export {};
|
export {};
|
||||||
|
|
306
dist/src/timeline.js
vendored
306
dist/src/timeline.js
vendored
|
@ -9,15 +9,9 @@ const intoEvent = (room) => (raw) => {
|
||||||
room.events.set(raw.event_id, event);
|
room.events.set(raw.event_id, event);
|
||||||
return event;
|
return event;
|
||||||
};
|
};
|
||||||
export class RoomTimeline extends EventEmitter {
|
class Timeline extends EventEmitter {
|
||||||
constructor(room) {
|
constructor() {
|
||||||
super();
|
super(...arguments);
|
||||||
Object.defineProperty(this, "room", {
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true,
|
|
||||||
value: room
|
|
||||||
});
|
|
||||||
Object.defineProperty(this, "isAtBeginning", {
|
Object.defineProperty(this, "isAtBeginning", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
@ -53,6 +47,23 @@ export class RoomTimeline extends EventEmitter {
|
||||||
getEvents() {
|
getEvents() {
|
||||||
return this._eventList;
|
return this._eventList;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
export class RoomTimeline extends Timeline {
|
||||||
|
constructor(timelineSet, room) {
|
||||||
|
super();
|
||||||
|
Object.defineProperty(this, "timelineSet", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: timelineSet
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "room", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: room
|
||||||
|
});
|
||||||
|
}
|
||||||
async paginate(dir, limit = 50) {
|
async paginate(dir, limit = 50) {
|
||||||
if (dir === "b" && this.isAtBeginning)
|
if (dir === "b" && this.isAtBeginning)
|
||||||
return false;
|
return false;
|
||||||
|
@ -87,57 +98,38 @@ export class RoomTimeline extends EventEmitter {
|
||||||
this.isAtBeginning = true;
|
this.isAtBeginning = true;
|
||||||
}
|
}
|
||||||
this._eventList.unshift(...events);
|
this._eventList.unshift(...events);
|
||||||
for (const event of events)
|
for (const event of events) {
|
||||||
|
const existing = this.timelineSet.timelineMap.get(event.id);
|
||||||
|
if (existing) {
|
||||||
|
const otherIdx = existing.getEvents().indexOf(event);
|
||||||
|
if (otherIdx === -1)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.timelineSet.timelineMap.set(event.id, this);
|
||||||
this.room.events.set(event.id, event);
|
this.room.events.set(event.id, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.emit("timelineUpdate", events, true);
|
this.emit("timelineUpdate", events, true);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class ThreadTimeline extends EventEmitter {
|
export class ThreadTimeline extends Timeline {
|
||||||
constructor(thread) {
|
constructor(timelineSet, thread) {
|
||||||
super();
|
super();
|
||||||
|
Object.defineProperty(this, "timelineSet", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: timelineSet
|
||||||
|
});
|
||||||
Object.defineProperty(this, "thread", {
|
Object.defineProperty(this, "thread", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
writable: true,
|
writable: true,
|
||||||
value: thread
|
value: thread
|
||||||
});
|
});
|
||||||
Object.defineProperty(this, "isAtBeginning", {
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true,
|
|
||||||
value: false
|
|
||||||
});
|
|
||||||
Object.defineProperty(this, "isAtEnd", {
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true,
|
|
||||||
value: false
|
|
||||||
});
|
|
||||||
// private eventIdToTimeline: Map<EventId, Timeline> = new Map();
|
|
||||||
// These should be private, but typescript doesn't have "private to module"
|
|
||||||
Object.defineProperty(this, "_eventList", {
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true,
|
|
||||||
value: []
|
|
||||||
});
|
|
||||||
Object.defineProperty(this, "prevBatch", {
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true,
|
|
||||||
value: void 0
|
|
||||||
});
|
|
||||||
Object.defineProperty(this, "nextBatch", {
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true,
|
|
||||||
value: void 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
getEvents() {
|
|
||||||
return this._eventList;
|
|
||||||
}
|
}
|
||||||
async paginate(dir, limit = 50) {
|
async paginate(dir, limit = 50) {
|
||||||
if (dir === "b" && this.isAtBeginning)
|
if (dir === "b" && this.isAtBeginning)
|
||||||
|
@ -175,36 +167,50 @@ export class ThreadTimeline extends EventEmitter {
|
||||||
this.isAtBeginning = true;
|
this.isAtBeginning = true;
|
||||||
}
|
}
|
||||||
this._eventList.unshift(...events);
|
this._eventList.unshift(...events);
|
||||||
for (const event of events)
|
for (const event of events) {
|
||||||
|
const existing = this.timelineSet.timelineMap.get(event.id);
|
||||||
|
if (existing) {
|
||||||
|
const otherIdx = existing.getEvents().indexOf(event);
|
||||||
|
if (otherIdx === -1)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.timelineSet.timelineMap.set(event.id, this);
|
||||||
room.events.set(event.id, event);
|
room.events.set(event.id, event);
|
||||||
// TODO: make more performant
|
}
|
||||||
// const timelines = [...this.thread.room.timelines.timelines].filter(i => i instanceof ThreadTimeline && i.thread === this.thread) as Array<ThreadTimeline>;
|
}
|
||||||
// triple loops!
|
|
||||||
// this only definitely merges once backwards and once forwards, but that's fine since it would've already been merged anyways if there's multiple overlaps
|
|
||||||
// for (const timeline of timelines) {
|
|
||||||
// for (const event of events) {
|
|
||||||
// // this: [1, 2, 3, 4, 5]
|
|
||||||
// // other: [3, 4, 5, 6, 7, 8]
|
|
||||||
// const otherIdx = timeline.getEvents().indexOf(event);
|
|
||||||
// if (otherIdx === -1) continue;
|
|
||||||
// // const thisIdx = this._eventList.indexOf(event);
|
|
||||||
// // this._eventList
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
this.emit("timelineUpdate", events, true);
|
this.emit("timelineUpdate", events, true);
|
||||||
return !!data.prev_batch;
|
return !!data.prev_batch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class TimelineSet {
|
class TimelineSet {
|
||||||
constructor(room) {
|
merge(events) {
|
||||||
Object.defineProperty(this, "room", {
|
for (const event of events) {
|
||||||
|
const tl = this.timelineMap.get(event.id);
|
||||||
|
if (!tl)
|
||||||
|
continue;
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ThreadTimelineSet extends TimelineSet {
|
||||||
|
constructor(thread) {
|
||||||
|
super();
|
||||||
|
Object.defineProperty(this, "thread", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
writable: true,
|
writable: true,
|
||||||
value: room
|
value: thread
|
||||||
|
});
|
||||||
|
// This is the one live timeline
|
||||||
|
Object.defineProperty(this, "client", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: this.thread.room.client
|
||||||
});
|
});
|
||||||
// Other timelines *may* be live, but this one is guaranteed to be live
|
|
||||||
Object.defineProperty(this, "live", {
|
Object.defineProperty(this, "live", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
@ -217,50 +223,134 @@ export class TimelineSet {
|
||||||
writable: true,
|
writable: true,
|
||||||
value: new Set()
|
value: new Set()
|
||||||
});
|
});
|
||||||
this.live = new RoomTimeline(room);
|
Object.defineProperty(this, "timelineMap", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: new Map()
|
||||||
|
});
|
||||||
|
this.live = new ThreadTimeline(this, thread);
|
||||||
this.live.isAtEnd = true;
|
this.live.isAtEnd = true;
|
||||||
this.timelines.add(this.live);
|
this.timelines.add(this.live);
|
||||||
}
|
}
|
||||||
// Get a timeline for a thread. Becomes a live timeline if `atEnd = true`.
|
async fetch(at, limit = 50) {
|
||||||
async forThread(thread, at) {
|
|
||||||
if (at === "end") {
|
if (at === "end") {
|
||||||
const existing = [...this.timelines].find(i => i instanceof ThreadTimeline && i.thread === thread && i.isAtEnd);
|
const fetchCount = limit - this.live.getEvents().length;
|
||||||
if (existing)
|
if (fetchCount > 0)
|
||||||
return existing;
|
await this.live.paginate("b", fetchCount);
|
||||||
const tl = new ThreadTimeline(thread);
|
return this.live;
|
||||||
await tl.paginate("b");
|
|
||||||
tl.isAtEnd = true;
|
|
||||||
return tl;
|
|
||||||
}
|
}
|
||||||
else if (at === "start") {
|
else if (at === "start") {
|
||||||
const existing = [...this.timelines].find(i => i instanceof ThreadTimeline && i.thread === thread && i.isAtBeginning);
|
const existing = [...this.timelines].find(i => i.isAtBeginning);
|
||||||
if (existing)
|
if (existing) {
|
||||||
|
const fetchCount = limit - existing.getEvents().length;
|
||||||
|
if (fetchCount > 0)
|
||||||
|
await existing.paginate("f", fetchCount);
|
||||||
return existing;
|
return existing;
|
||||||
const tl = new ThreadTimeline(thread);
|
|
||||||
await tl.paginate("b");
|
|
||||||
this.timelines.add(tl);
|
|
||||||
return tl;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const existing = [...this.timelines].find(i => i instanceof ThreadTimeline && i.thread === thread && i.getEvents().some(ev => ev.id === at));
|
const tl = new ThreadTimeline(this, this.thread);
|
||||||
if (existing)
|
await tl.paginate("b", limit);
|
||||||
return existing;
|
|
||||||
const tl = new ThreadTimeline(thread);
|
|
||||||
const context = await this.room.client.net.fetchContext(this.room.id, at, 0);
|
|
||||||
tl._eventList = [intoEvent(this.room)(context.event)];
|
|
||||||
tl.prevBatch = context.start;
|
|
||||||
tl.nextBatch = context.end;
|
|
||||||
this.timelines.add(tl);
|
this.timelines.add(tl);
|
||||||
return tl;
|
return tl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Get a timeline for an event (context).
|
else {
|
||||||
async forEvent(eventId) {
|
// TODO: respect limit?
|
||||||
const existing = [...this.timelines].find(tl => tl instanceof RoomTimeline && tl.getEvents().some(ev => ev.id === eventId));
|
const existing = this.timelineMap.get(at);
|
||||||
if (existing)
|
if (existing) {
|
||||||
|
const fetchCount = limit - existing.getEvents().length;
|
||||||
|
if (fetchCount > 0)
|
||||||
|
await existing.paginate("f", fetchCount);
|
||||||
return existing;
|
return existing;
|
||||||
const context = await this.room.client.net.fetchContext(this.room.id, eventId);
|
}
|
||||||
const tl = new RoomTimeline(this.room);
|
else {
|
||||||
|
const tl = new ThreadTimeline(this, this.thread);
|
||||||
|
const context = await this.client.net.fetchContext(this.thread.room.id, at, 0);
|
||||||
|
const event = intoEvent(this.thread.room)(context.event);
|
||||||
|
tl._eventList = [event];
|
||||||
|
tl.prevBatch = context.start;
|
||||||
|
tl.nextBatch = context.end;
|
||||||
|
this.timelineMap.set(event.id, tl);
|
||||||
|
this.timelines.add(tl);
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class RoomTimelineSet extends TimelineSet {
|
||||||
|
constructor(room) {
|
||||||
|
super();
|
||||||
|
Object.defineProperty(this, "room", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: room
|
||||||
|
});
|
||||||
|
// Other timelines *may* be live, but this one is guaranteed to be live
|
||||||
|
Object.defineProperty(this, "client", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: this.room.client
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "live", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: void 0
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "timelines", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: new Set()
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, "timelineMap", {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: new Map()
|
||||||
|
});
|
||||||
|
this.live = new RoomTimeline(this, room);
|
||||||
|
this.live.isAtEnd = true;
|
||||||
|
this.timelines.add(this.live);
|
||||||
|
}
|
||||||
|
// Get a timeline for an event (context).
|
||||||
|
async fetch(at, limit = 50) {
|
||||||
|
if (at === "end") {
|
||||||
|
const fetchCount = limit - this.live.getEvents().length;
|
||||||
|
if (fetchCount > 0)
|
||||||
|
await this.live.paginate("b", fetchCount);
|
||||||
|
return this.live;
|
||||||
|
}
|
||||||
|
else if (at === "start") {
|
||||||
|
const existing = [...this.timelines].find(i => i.isAtBeginning);
|
||||||
|
if (existing) {
|
||||||
|
const fetchCount = limit - existing.getEvents().length;
|
||||||
|
if (fetchCount > 0)
|
||||||
|
await existing.paginate("f", fetchCount);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const tl = new RoomTimeline(this, this.room);
|
||||||
|
await tl.paginate("b", limit);
|
||||||
|
this.timelines.add(tl);
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: respect limit?
|
||||||
|
const existing = this.timelineMap.get(at);
|
||||||
|
if (existing) {
|
||||||
|
const fetchCount = limit - existing.getEvents().length;
|
||||||
|
if (fetchCount > 0)
|
||||||
|
await existing.paginate("f", fetchCount);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const context = await this.client.net.fetchContext(this.room.id, at);
|
||||||
|
const tl = new RoomTimeline(this, this.room);
|
||||||
const events = context.events_before
|
const events = context.events_before
|
||||||
.reverse()
|
.reverse()
|
||||||
.concat([context.event])
|
.concat([context.event])
|
||||||
|
@ -270,26 +360,10 @@ export class TimelineSet {
|
||||||
tl.prevBatch = context.start;
|
tl.prevBatch = context.start;
|
||||||
tl.nextBatch = context.end;
|
tl.nextBatch = context.end;
|
||||||
this.timelines.add(tl);
|
this.timelines.add(tl);
|
||||||
return tl;
|
// FIXME: merge timelines
|
||||||
}
|
|
||||||
_appendEvents(events) {
|
|
||||||
// FIXME: there should only be one live timeline for each room and
|
|
||||||
// thread, they need to be merged together
|
|
||||||
for (const event of events) {
|
|
||||||
this.room.events.set(event.id, event);
|
|
||||||
this.live._eventList.push(...events);
|
|
||||||
for (const event of events)
|
for (const event of events)
|
||||||
this.live.emit("timelineAppend", event);
|
this.timelineMap.set(event.id, tl);
|
||||||
const threadId = event.content["m.relations"]?.find((rel) => rel.rel_type === "m.thread")?.event_id;
|
return tl;
|
||||||
const thread = this.room.threads.get(threadId);
|
|
||||||
if (thread) {
|
|
||||||
thread.messageCount++;
|
|
||||||
thread.latestEvent = event;
|
|
||||||
const tl = [...this.timelines].find(i => i instanceof ThreadTimeline && i.thread === thread && i.isAtEnd);
|
|
||||||
if (tl) {
|
|
||||||
tl._eventList.push(event);
|
|
||||||
tl.emit("timelineAppend", event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
dist/src/timeline.js.map
vendored
2
dist/src/timeline.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/tsconfig.tsbuildinfo
vendored
2
dist/tsconfig.tsbuildinfo
vendored
File diff suppressed because one or more lines are too long
|
@ -15,6 +15,7 @@
|
||||||
"typescript": "^5.3.2"
|
"typescript": "^5.3.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@matrix-org/matrix-sdk-crypto-wasm": "^3.4.0",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"nanoid": "^5.0.4"
|
"nanoid": "^5.0.4"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ settings:
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@matrix-org/matrix-sdk-crypto-wasm':
|
||||||
|
specifier: ^3.4.0
|
||||||
|
version: 3.4.0
|
||||||
events:
|
events:
|
||||||
specifier: ^3.3.0
|
specifier: ^3.3.0
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
|
@ -25,6 +28,11 @@ devDependencies:
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
/@matrix-org/matrix-sdk-crypto-wasm@3.4.0:
|
||||||
|
resolution: {integrity: sha512-noO6QnH+ypT//CxewoQdlK/z2iuyQo1Ecp1PDaYyr/NV5yXkWvGfGIIcShXqrQJfL5kuWxg/14edNplXsaXoDQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/events@3.0.3:
|
/@types/events@3.0.3:
|
||||||
resolution: {integrity: sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==}
|
resolution: {integrity: sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
23
src/api.ts
23
src/api.ts
|
@ -245,3 +245,26 @@ export interface AckRequest {
|
||||||
thread_id?: EventId,
|
thread_id?: EventId,
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export interface WellKnownClient {
|
||||||
|
// baseUrl: string,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export interface ServerConfig {
|
||||||
|
// components: {
|
||||||
|
// api: string,
|
||||||
|
// sync: string,
|
||||||
|
// media: string,
|
||||||
|
// voip: string,
|
||||||
|
// },
|
||||||
|
// server: {
|
||||||
|
// name: string,
|
||||||
|
// version: string,
|
||||||
|
// url: string,
|
||||||
|
// },
|
||||||
|
// admins: Array<{
|
||||||
|
// user_id: string,
|
||||||
|
// email_address?: string,
|
||||||
|
// purposes: Array<"m.admin" | "m.moderation" | "m.security">,
|
||||||
|
// }>,
|
||||||
|
// }
|
||||||
|
|
|
@ -7,18 +7,19 @@ import { Connection } from "./sync.js";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import TypedEmitter from "typed-emitter";
|
import TypedEmitter from "typed-emitter";
|
||||||
|
|
||||||
interface ClientConfig {
|
export interface ClientConfig {
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
token: string,
|
token: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientState = { state: "stop" } // The client is stopped and inactive
|
export type ClientState = { state: "stop" } // The client is stopped and inactive
|
||||||
| { state: "sync" } // The client is active and syncing
|
| { state: "sync" } // The client is active and syncing
|
||||||
| { state: "catchup" } // The client is catching up after a `retry`
|
| { state: "catchup" } // The client is catching up after a `retry`
|
||||||
| { state: "error", reason: any } // The client failed and will not retry
|
| { state: "error", reason: any } // The client failed and will not retry
|
||||||
| { state: "retry", backoff: number } // The client failed and is retrying
|
| { state: "retry", backoff: number } // The client failed and is retrying
|
||||||
|
| { state: "logout", soft: boolean } // The client isn't logged in
|
||||||
|
|
||||||
type ClientEvents = {
|
type ClientEvents = {
|
||||||
// The client's state changed.
|
// The client's state changed.
|
||||||
|
@ -43,7 +44,7 @@ type ClientEvents = {
|
||||||
toDevice: (event: ApiDeviceEvent) => void,
|
toDevice: (event: ApiDeviceEvent) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
type RoomList = {
|
export type RoomList = {
|
||||||
// The total number of rooms in this list
|
// The total number of rooms in this list
|
||||||
count: number,
|
count: number,
|
||||||
|
|
||||||
|
@ -144,8 +145,14 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<C
|
||||||
|
|
||||||
// Stop receiving events from /sync.
|
// Stop receiving events from /sync.
|
||||||
stop() {
|
stop() {
|
||||||
this.conn.abort();
|
this.conn.abort("stop sync");
|
||||||
this.conn = new Connection(this);
|
this.conn = new Connection(this);
|
||||||
this.setState({ state: "stop" });
|
this.setState({ state: "stop" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
this.stop();
|
||||||
|
await this.net.authLogout();
|
||||||
|
this.setState({ state: "logout", soft: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
10
src/index.ts
10
src/index.ts
|
@ -1,6 +1,8 @@
|
||||||
export { Client } from "./client.js";
|
export { Client } from "./client.js";
|
||||||
|
export { Setup } from "./setup.js";
|
||||||
export { ThreadPaginator } from "./room.js";
|
export { ThreadPaginator } from "./room.js";
|
||||||
export type { Event, StateEvent } from "./event.js";
|
export { Event, StateEvent } from "./event.js";
|
||||||
export type { Room } from "./room.js";
|
export { Room } from "./room.js";
|
||||||
export type { Thread } from "./thread.js";
|
export { Thread } from "./thread.js";
|
||||||
export type { Timeline } from "./timeline.js";
|
export type { RoomList, ClientState } from "./client.js";
|
||||||
|
export type { UserId, RoomId, EventId } from "./api.js";
|
||||||
|
|
48
src/net.ts
48
src/net.ts
|
@ -91,7 +91,18 @@ export class Network {
|
||||||
body: (isJson ? JSON.stringify(options.body) : options.body) as BodyInit | undefined,
|
body: (isJson ? JSON.stringify(options.body) : options.body) as BodyInit | undefined,
|
||||||
...options.extra,
|
...options.extra,
|
||||||
});
|
});
|
||||||
if (!req.ok) throw new Error(`Request failed: ${await req.text()}`);
|
// FIXME: handle uiaa and make this less awful
|
||||||
|
if (!req.ok) {
|
||||||
|
// const response = await req.json();
|
||||||
|
// throw { response, error: new Error(`Request failed: ${await req.text()}`) };
|
||||||
|
if (req.status === 401) {
|
||||||
|
this.client.stop();
|
||||||
|
(this.client as any).setState({ state: "logout" });
|
||||||
|
throw new Error(`Request failed: ${await req.text()}`);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Request failed: ${await req.text()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (options.raw) return req.body;
|
if (options.raw) return req.body;
|
||||||
return req.json();
|
return req.json();
|
||||||
}
|
}
|
||||||
|
@ -217,4 +228,39 @@ export class Network {
|
||||||
body: acks,
|
body: acks,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async authLogin() {
|
||||||
|
throw new Error("todo!")
|
||||||
|
// return this.fetch({
|
||||||
|
// method: "POST",
|
||||||
|
// path: `/_matrix/client/v3/login`,
|
||||||
|
// body: {},
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async authRegister() {
|
||||||
|
throw new Error("todo!")
|
||||||
|
// return this.fetch({
|
||||||
|
// method: "POST",
|
||||||
|
// path: `/_matrix/client/v3/login`,
|
||||||
|
// body: {},
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
// public async authGuest() {
|
||||||
|
// throw new Error("todo!")
|
||||||
|
// // return this.fetch({
|
||||||
|
// // method: "POST",
|
||||||
|
// // path: `/_matrix/client/v3/login`,
|
||||||
|
// // body: {},
|
||||||
|
// // });
|
||||||
|
// }
|
||||||
|
|
||||||
|
public async authLogout() {
|
||||||
|
return this.fetch({
|
||||||
|
method: "POST",
|
||||||
|
path: `/_matrix/client/v3/logout`,
|
||||||
|
body: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
34
src/room.ts
34
src/room.ts
|
@ -1,21 +1,13 @@
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import TypedEmitter from "typed-emitter";
|
import TypedEmitter from "typed-emitter";
|
||||||
import { ApiEphemeralEvent, EventId, IncludeThreads, RoomId, SyncResponseRoom, Unreads } from "./api.js";
|
import { EventId, IncludeThreads, RoomId, SyncResponseRoom, Unreads } from "./api.js";
|
||||||
import { Client } from "./client.js";
|
import { Client } from "./client.js";
|
||||||
import { Event, StateEvent } from "./event.js";
|
import { Event, StateEvent } from "./event.js";
|
||||||
import { TimelineSet } from "./timeline.js";
|
import { RoomTimelineSet } from "./timeline.js";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { Thread } from "./thread.js";
|
import { Thread } from "./thread.js";
|
||||||
|
|
||||||
type RoomEvents = {
|
type RoomEvents = {
|
||||||
// an event is appended to this room's live timeline
|
|
||||||
// @deprecated: use timelines.live
|
|
||||||
timeline: (event: Event) => void,
|
|
||||||
|
|
||||||
// an ephemeral event was received
|
|
||||||
// @deprecated: use timelines.live
|
|
||||||
ephemeral: (event: ApiEphemeralEvent) => void,
|
|
||||||
|
|
||||||
// A thread was created
|
// A thread was created
|
||||||
thread: (thread: Thread) => void,
|
thread: (thread: Thread) => void,
|
||||||
|
|
||||||
|
@ -36,7 +28,7 @@ export class Room extends (EventEmitter as unknown as new () => TypedEmitter<Roo
|
||||||
// The (possibly incomplete) state of this room
|
// The (possibly incomplete) state of this room
|
||||||
private state: Map<string, Map<string, StateEvent>> = new Map();
|
private state: Map<string, Map<string, StateEvent>> = new Map();
|
||||||
|
|
||||||
public timelines = new TimelineSet(this);
|
public timelines = new RoomTimelineSet(this);
|
||||||
public events: Map<EventId, Event> = new Map();
|
public events: Map<EventId, Event> = new Map();
|
||||||
public threads = new RoomThreads(this);
|
public threads = new RoomThreads(this);
|
||||||
// public members: Members;
|
// public members: Members;
|
||||||
|
@ -80,7 +72,21 @@ room.unban(userid)
|
||||||
const events = data.timeline
|
const events = data.timeline
|
||||||
.filter(raw => !this.events.has(raw.event_id))
|
.filter(raw => !this.events.has(raw.event_id))
|
||||||
.map(raw => new Event(this, raw));
|
.map(raw => new Event(this, raw));
|
||||||
this.timelines._appendEvents(events);
|
|
||||||
|
for (const event of events) {
|
||||||
|
this.events.set(event.id, event);
|
||||||
|
this.timelines.live._eventList.push(...events);
|
||||||
|
for (const event of events) this.timelines.live.emit("timelineAppend", event);
|
||||||
|
|
||||||
|
const threadId = event.content["m.relations"]?.find((rel: any) => rel.rel_type === "m.thread")?.event_id;
|
||||||
|
const thread = this.threads.get(threadId);
|
||||||
|
if (thread) {
|
||||||
|
thread.messageCount++;
|
||||||
|
thread.latestEvent = event;
|
||||||
|
thread.timelines.live._eventList.push(event);
|
||||||
|
thread.timelines.live.emit("timelineAppend", event);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,12 +111,12 @@ room.unban(userid)
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
const sub = (event: Event) => {
|
const sub = (event: Event) => {
|
||||||
if (event.unsigned.transaction_id === txn) {
|
if (event.unsigned.transaction_id === txn) {
|
||||||
this.off("timeline", sub);
|
this.timelines.live.off("timelineAppend", sub);
|
||||||
res(event);
|
res(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.on("timeline", sub);
|
this.timelines.live.on("timelineAppend", sub);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
92
src/setup.ts
92
src/setup.ts
|
@ -1,4 +1,94 @@
|
||||||
// Used for initiating a client, handling well-known and authentication.
|
// Used for initiating a client, handling well-known and authentication.
|
||||||
|
|
||||||
class Setup {
|
/*
|
||||||
|
I'd love to rip this out and replace this with oidc a la "matrix 2.0",
|
||||||
|
but I'm not sure if that's a good idea yet.
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
1. It will be more secure since users won't enter credentials into random clients.
|
||||||
|
2. It separates concerns.
|
||||||
|
2.1 client and server devs can focus on matrix instead of security best practices
|
||||||
|
2.2 server admins can add new auth mechanisms without needing spec changes
|
||||||
|
3. I can use existing sdks and libraries to do authentication - rolling your own auth is a bad idea.
|
||||||
|
|
||||||
|
Drawbacks:
|
||||||
|
|
||||||
|
1. The ux will be worse since there's now a separtate place to do auth (login, register, manage sessions).
|
||||||
|
2. It will be much more complex. Server admins now have extra stuff to setup, maintain, and debug. Arguably, it's more difficult for client devs to use as well.
|
||||||
|
3. It forces users to have some kind of web browser to login.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: properly integrate with net.js
|
||||||
|
|
||||||
|
import { Client } from "./client.js";
|
||||||
|
|
||||||
|
type Flows = {
|
||||||
|
"m.login.password": { password: string, },
|
||||||
|
"m.login.recaptcha": { response: string },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Identifier =
|
||||||
|
{ type: "m.id.user", user: string } |
|
||||||
|
{ type: string, [key: string]: any }
|
||||||
|
|
||||||
|
export class Setup {
|
||||||
|
public baseUrl: string | null = null;
|
||||||
|
public endpoint: string | null = null;
|
||||||
|
public session: string | null = null;
|
||||||
|
public flows: Array<string> | null = null;
|
||||||
|
public params: Record<string, string> | null = null;
|
||||||
|
public identifier: Identifier | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public target: "login" | "register" | "guest",
|
||||||
|
public deviceName: string = "unnamed (sdk-ts)",
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async useDomain(_domain: string) {
|
||||||
|
throw new Error("todo!");
|
||||||
|
}
|
||||||
|
|
||||||
|
async useBaseUrl(baseUrl: string) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
this.endpoint = `${baseUrl}/_matrix/client/v3/${this.target === "login" ? "login" : "register"}${this.target === "guest" ? "?kind=guest" : ""}`;
|
||||||
|
const res = await fetch(this.endpoint).then(res => res.json());
|
||||||
|
this.flows = res.flows;
|
||||||
|
this.params = res.params;
|
||||||
|
}
|
||||||
|
|
||||||
|
useIdentifier(id: Identifier) {
|
||||||
|
this.identifier = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async exec<K extends keyof Flows>(type: K, options: Flows[K]): Promise<Client | null> {
|
||||||
|
if (!this.endpoint) throw new Error("you need to call useBaseUrl first");
|
||||||
|
const res = await fetch(this.endpoint, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
identifier: this.identifier,
|
||||||
|
initial_device_display_name: this.deviceName,
|
||||||
|
session: this.session ?? undefined,
|
||||||
|
type: type,
|
||||||
|
...options,
|
||||||
|
}),
|
||||||
|
method: "POST",
|
||||||
|
}).then(res => res.json());
|
||||||
|
if (res.status === 400) {
|
||||||
|
this.session = res.session;
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new Client({
|
||||||
|
baseUrl: this.baseUrl!,
|
||||||
|
token: res.access_token,
|
||||||
|
deviceId: res.device_id,
|
||||||
|
userId: res.user_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const setup = new Setup("login");
|
||||||
|
// // setup.useIdentifier({ type: "m.id.user", user: "@localpart:server.tld" });
|
||||||
|
// setup.useIdentifier({ type: "m.id.user", user: "localpart" });
|
||||||
|
// await setup.useBaseUrl("http://localhost:6167");
|
||||||
|
// await setup.exec("m.login.password", { password: "a" });
|
||||||
|
|
|
@ -25,6 +25,7 @@ export class Connection {
|
||||||
...this.query,
|
...this.query,
|
||||||
}, this.controller.signal).catch((reason) => {
|
}, this.controller.signal).catch((reason) => {
|
||||||
if (reason === "update query") return null;
|
if (reason === "update query") return null;
|
||||||
|
if (reason === "stop sync") return null;
|
||||||
throw reason;
|
throw reason;
|
||||||
});
|
});
|
||||||
if (!json) return;
|
if (!json) return;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import TypedEmitter from "typed-emitter";
|
||||||
import { EventId, Unreads } from "./api.js";
|
import { EventId, Unreads } from "./api.js";
|
||||||
import { Room } from "./room.js";
|
import { Room } from "./room.js";
|
||||||
import { Event } from "./event.js";
|
import { Event } from "./event.js";
|
||||||
|
import { ThreadTimelineSet } from "./timeline.js";
|
||||||
|
|
||||||
type ThreadEvents = {
|
type ThreadEvents = {
|
||||||
// an event is appended to this thread's live timeline
|
// an event is appended to this thread's live timeline
|
||||||
|
@ -15,6 +16,7 @@ type ThreadEvents = {
|
||||||
export class Thread extends (EventEmitter as unknown as new () => TypedEmitter<ThreadEvents>) {
|
export class Thread extends (EventEmitter as unknown as new () => TypedEmitter<ThreadEvents>) {
|
||||||
public room: Room = this.baseEvent.room;
|
public room: Room = this.baseEvent.room;
|
||||||
public id = this.baseEvent.id;
|
public id = this.baseEvent.id;
|
||||||
|
public timelines = new ThreadTimelineSet(this);
|
||||||
|
|
||||||
public participation = "participating";
|
public participation = "participating";
|
||||||
public messageCount: number;
|
public messageCount: number;
|
||||||
|
|
242
src/timeline.ts
242
src/timeline.ts
|
@ -8,7 +8,7 @@ import { Thread } from "./thread.js";
|
||||||
import TypedEmitter from "typed-emitter";
|
import TypedEmitter from "typed-emitter";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
|
|
||||||
type TimelineEvents = {
|
type TimelineEvents<T> = {
|
||||||
// This room's live timeline is updated, usually via pagination.
|
// This room's live timeline is updated, usually via pagination.
|
||||||
timelineUpdate: (batch: Array<Event>, toBeginning: boolean) => void,
|
timelineUpdate: (batch: Array<Event>, toBeginning: boolean) => void,
|
||||||
|
|
||||||
|
@ -17,31 +17,12 @@ type TimelineEvents = {
|
||||||
timelineAppend: (event: Event) => void,
|
timelineAppend: (event: Event) => void,
|
||||||
|
|
||||||
// This timeline has been merged with another timeline.
|
// This timeline has been merged with another timeline.
|
||||||
timelineReplace: (timeine: Timeline) => void,
|
timelineReplace: (timeine: T) => void,
|
||||||
|
|
||||||
// An ephemeral event was received
|
// An ephemeral event was received
|
||||||
ephemeral: (event: ApiEphemeralEvent) => void,
|
ephemeral: (event: ApiEphemeralEvent) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Timeline extends TypedEmitter<TimelineEvents> {
|
|
||||||
// if this timeline has reached the beginning (earliest event)
|
|
||||||
isAtBeginning: boolean,
|
|
||||||
|
|
||||||
// if this timeline has reached the end (latest event)
|
|
||||||
// this timeline will also be live and receive new events
|
|
||||||
isAtEnd: boolean,
|
|
||||||
|
|
||||||
// isPaginating: boolean,
|
|
||||||
|
|
||||||
// Get the events in this timeline
|
|
||||||
getEvents(): Array<Event>,
|
|
||||||
|
|
||||||
// Paginate a timeline for more events
|
|
||||||
// TODO: fuse two neighboring timelines together
|
|
||||||
// TODO: don't bother paginating if a timeline is at the end
|
|
||||||
paginate(dir: "f" | "b", limit: number): Promise<boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const intoEvent = (room: Room) => (raw: ApiEvent): Event => {
|
const intoEvent = (room: Room) => (raw: ApiEvent): Event => {
|
||||||
const existing = room.events.get(raw.event_id);
|
const existing = room.events.get(raw.event_id);
|
||||||
if (existing) return existing;
|
if (existing) return existing;
|
||||||
|
@ -50,7 +31,7 @@ const intoEvent = (room: Room) => (raw: ApiEvent): Event => {
|
||||||
return event;
|
return event;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class RoomTimeline extends (EventEmitter as unknown as new () => TypedEmitter<TimelineEvents>) implements Timeline {
|
abstract class Timeline extends (EventEmitter as unknown as new () => TypedEmitter<TimelineEvents<any>>) {
|
||||||
public isAtBeginning: boolean = false;
|
public isAtBeginning: boolean = false;
|
||||||
public isAtEnd: boolean = false;
|
public isAtEnd: boolean = false;
|
||||||
|
|
||||||
|
@ -59,14 +40,21 @@ export class RoomTimeline extends (EventEmitter as unknown as new () => TypedEmi
|
||||||
prevBatch: string | undefined;
|
prevBatch: string | undefined;
|
||||||
nextBatch: string | undefined;
|
nextBatch: string | undefined;
|
||||||
|
|
||||||
constructor(public room: Room) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public getEvents(): Array<Event> {
|
public getEvents(): Array<Event> {
|
||||||
return this._eventList;
|
return this._eventList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract paginate(dir: "f" | "b", limit: number): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RoomTimeline extends Timeline implements TypedEmitter<TimelineEvents<RoomTimeline>> {
|
||||||
|
constructor(
|
||||||
|
private timelineSet: RoomTimelineSet,
|
||||||
|
public room: Room,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
public async paginate(dir: "f" | "b", limit: number = 50): Promise<boolean> {
|
public async paginate(dir: "f" | "b", limit: number = 50): Promise<boolean> {
|
||||||
if (dir === "b" && this.isAtBeginning) return false;
|
if (dir === "b" && this.isAtBeginning) return false;
|
||||||
if (dir === "f" && this.isAtEnd) return false;
|
if (dir === "f" && this.isAtEnd) return false;
|
||||||
|
@ -96,32 +84,30 @@ export class RoomTimeline extends (EventEmitter as unknown as new () => TypedEmi
|
||||||
this.isAtBeginning = true;
|
this.isAtBeginning = true;
|
||||||
}
|
}
|
||||||
this._eventList.unshift(...events);
|
this._eventList.unshift(...events);
|
||||||
for (const event of events) this.room.events.set(event.id, event);
|
for (const event of events) {
|
||||||
|
const existing = this.timelineSet.timelineMap.get(event.id);
|
||||||
|
if (existing) {
|
||||||
|
const otherIdx = existing.getEvents().indexOf(event);
|
||||||
|
if (otherIdx === -1) continue;
|
||||||
|
} else {
|
||||||
|
this.timelineSet.timelineMap.set(event.id, this);
|
||||||
|
this.room.events.set(event.id, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.emit("timelineUpdate", events, true);
|
this.emit("timelineUpdate", events, true);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ThreadTimeline extends (EventEmitter as unknown as new () => TypedEmitter<TimelineEvents>) implements Timeline {
|
export class ThreadTimeline extends Timeline implements TypedEmitter<TimelineEvents<ThreadTimeline>> {
|
||||||
public isAtBeginning: boolean = false;
|
constructor(
|
||||||
public isAtEnd: boolean = false;
|
private timelineSet: ThreadTimelineSet,
|
||||||
|
public thread: Thread,
|
||||||
// private eventIdToTimeline: Map<EventId, Timeline> = new Map();
|
) {
|
||||||
|
|
||||||
// These should be private, but typescript doesn't have "private to module"
|
|
||||||
_eventList: Array<Event> = [];
|
|
||||||
prevBatch: string | undefined;
|
|
||||||
nextBatch: string | undefined;
|
|
||||||
|
|
||||||
constructor(public thread: Thread) {
|
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEvents(): Array<Event> {
|
|
||||||
return this._eventList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async paginate(dir: "f" | "b", limit: number = 50): Promise<boolean> {
|
public async paginate(dir: "f" | "b", limit: number = 50): Promise<boolean> {
|
||||||
if (dir === "b" && this.isAtBeginning) return false;
|
if (dir === "b" && this.isAtBeginning) return false;
|
||||||
if (dir === "f" && this.isAtEnd) return false;
|
if (dir === "f" && this.isAtEnd) return false;
|
||||||
|
@ -153,75 +139,131 @@ export class ThreadTimeline extends (EventEmitter as unknown as new () => TypedE
|
||||||
this.isAtBeginning = true;
|
this.isAtBeginning = true;
|
||||||
}
|
}
|
||||||
this._eventList.unshift(...events);
|
this._eventList.unshift(...events);
|
||||||
for (const event of events) room.events.set(event.id, event);
|
for (const event of events) {
|
||||||
|
const existing = this.timelineSet.timelineMap.get(event.id);
|
||||||
// TODO: make more performant
|
if (existing) {
|
||||||
// const timelines = [...this.thread.room.timelines.timelines].filter(i => i instanceof ThreadTimeline && i.thread === this.thread) as Array<ThreadTimeline>;
|
const otherIdx = existing.getEvents().indexOf(event);
|
||||||
// triple loops!
|
if (otherIdx === -1) continue;
|
||||||
// this only definitely merges once backwards and once forwards, but that's fine since it would've already been merged anyways if there's multiple overlaps
|
} else {
|
||||||
// for (const timeline of timelines) {
|
this.timelineSet.timelineMap.set(event.id, this);
|
||||||
// for (const event of events) {
|
room.events.set(event.id, event);
|
||||||
// // this: [1, 2, 3, 4, 5]
|
}
|
||||||
// // other: [3, 4, 5, 6, 7, 8]
|
}
|
||||||
// const otherIdx = timeline.getEvents().indexOf(event);
|
|
||||||
// if (otherIdx === -1) continue;
|
|
||||||
// // const thisIdx = this._eventList.indexOf(event);
|
|
||||||
// // this._eventList
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.emit("timelineUpdate", events, true);
|
this.emit("timelineUpdate", events, true);
|
||||||
return !!data.prev_batch;
|
return !!data.prev_batch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TimelineSet {
|
abstract class TimelineSet {
|
||||||
// Other timelines *may* be live, but this one is guaranteed to be live
|
abstract timelines: Set<Timeline>;
|
||||||
public live: RoomTimeline;
|
abstract timelineMap: Map<EventId, Timeline>;
|
||||||
timelines: Set<Timeline> = new Set();
|
|
||||||
|
|
||||||
constructor(public room: Room) {
|
merge(events: Array<Event>): Timeline | null {
|
||||||
this.live = new RoomTimeline(room);
|
for (const event of events) {
|
||||||
|
const tl = this.timelineMap.get(event.id);
|
||||||
|
if (!tl) continue;
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ThreadTimelineSet extends TimelineSet {
|
||||||
|
// This is the one live timeline
|
||||||
|
public client = this.thread.room.client;
|
||||||
|
public live: ThreadTimeline;
|
||||||
|
timelines: Set<ThreadTimeline> = new Set();
|
||||||
|
timelineMap: Map<EventId, ThreadTimeline> = new Map();
|
||||||
|
|
||||||
|
constructor(public thread: Thread) {
|
||||||
|
super();
|
||||||
|
this.live = new ThreadTimeline(this, thread);
|
||||||
this.live.isAtEnd = true;
|
this.live.isAtEnd = true;
|
||||||
this.timelines.add(this.live);
|
this.timelines.add(this.live);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a timeline for a thread. Becomes a live timeline if `atEnd = true`.
|
public async fetch(at: EventId | "start" | "end", limit = 50): Promise<ThreadTimeline> {
|
||||||
public async forThread(thread: Thread, at: EventId | "start" | "end"): Promise<ThreadTimeline> {
|
|
||||||
if (at === "end") {
|
if (at === "end") {
|
||||||
const existing = [...this.timelines].find(i => i instanceof ThreadTimeline && i.thread === thread && i.isAtEnd) as ThreadTimeline | undefined;
|
const fetchCount = limit - this.live.getEvents().length;
|
||||||
if (existing) return existing;
|
if (fetchCount > 0) await this.live.paginate("b", fetchCount);
|
||||||
const tl = new ThreadTimeline(thread);
|
return this.live;
|
||||||
await tl.paginate("b");
|
|
||||||
tl.isAtEnd = true;
|
|
||||||
return tl;
|
|
||||||
} else if (at === "start") {
|
} else if (at === "start") {
|
||||||
const existing = [...this.timelines].find(i => i instanceof ThreadTimeline && i.thread === thread && i.isAtBeginning) as ThreadTimeline | undefined;
|
const existing = [...this.timelines].find(i => i.isAtBeginning);
|
||||||
if (existing) return existing;
|
if (existing) {
|
||||||
const tl = new ThreadTimeline(thread);
|
const fetchCount = limit - existing.getEvents().length;
|
||||||
await tl.paginate("b");
|
if (fetchCount > 0) await existing.paginate("f", fetchCount);
|
||||||
|
return existing;
|
||||||
|
} else {
|
||||||
|
const tl = new ThreadTimeline(this, this.thread);
|
||||||
|
await tl.paginate("b", limit);
|
||||||
this.timelines.add(tl);
|
this.timelines.add(tl);
|
||||||
return tl;
|
return tl;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const existing = [...this.timelines].find(i => i instanceof ThreadTimeline && i.thread === thread && i.getEvents().some(ev => ev.id === at)) as ThreadTimeline | undefined;
|
// TODO: respect limit?
|
||||||
if (existing) return existing;
|
const existing = this.timelineMap.get(at);
|
||||||
const tl = new ThreadTimeline(thread);
|
if (existing) {
|
||||||
const context = await this.room.client.net.fetchContext(this.room.id, at, 0);
|
const fetchCount = limit - existing.getEvents().length;
|
||||||
tl._eventList = [intoEvent(this.room)(context.event)];
|
if (fetchCount > 0) await existing.paginate("f", fetchCount);
|
||||||
|
return existing;
|
||||||
|
} else {
|
||||||
|
const tl = new ThreadTimeline(this, this.thread);
|
||||||
|
const context = await this.client.net.fetchContext(this.thread.room.id, at, 0);
|
||||||
|
const event = intoEvent(this.thread.room)(context.event);
|
||||||
|
tl._eventList = [event];
|
||||||
tl.prevBatch = context.start;
|
tl.prevBatch = context.start;
|
||||||
tl.nextBatch = context.end;
|
tl.nextBatch = context.end;
|
||||||
|
this.timelineMap.set(event.id, tl);
|
||||||
this.timelines.add(tl);
|
this.timelines.add(tl);
|
||||||
return tl;
|
return tl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RoomTimelineSet extends TimelineSet {
|
||||||
|
// Other timelines *may* be live, but this one is guaranteed to be live
|
||||||
|
public client = this.room.client;
|
||||||
|
public live: RoomTimeline;
|
||||||
|
timelines: Set<RoomTimeline> = new Set();
|
||||||
|
timelineMap: Map<EventId, RoomTimeline> = new Map();
|
||||||
|
|
||||||
|
constructor(public room: Room) {
|
||||||
|
super();
|
||||||
|
this.live = new RoomTimeline(this, room);
|
||||||
|
this.live.isAtEnd = true;
|
||||||
|
this.timelines.add(this.live);
|
||||||
|
}
|
||||||
|
|
||||||
// Get a timeline for an event (context).
|
// Get a timeline for an event (context).
|
||||||
public async forEvent(eventId: EventId): Promise<RoomTimeline> {
|
public async fetch(at: EventId | "start" | "end", limit = 50): Promise<RoomTimeline> {
|
||||||
const existing = [...this.timelines].find(tl => tl instanceof RoomTimeline && tl.getEvents().some(ev => ev.id === eventId)) as RoomTimeline | undefined;
|
if (at === "end") {
|
||||||
if (existing) return existing;
|
const fetchCount = limit - this.live.getEvents().length;
|
||||||
const context = await this.room.client.net.fetchContext(this.room.id, eventId);
|
if (fetchCount > 0) await this.live.paginate("b", fetchCount);
|
||||||
const tl = new RoomTimeline(this.room);
|
return this.live;
|
||||||
|
} else if (at === "start") {
|
||||||
|
const existing = [...this.timelines].find(i => i.isAtBeginning);
|
||||||
|
if (existing) {
|
||||||
|
const fetchCount = limit - existing.getEvents().length;
|
||||||
|
if (fetchCount > 0) await existing.paginate("f", fetchCount);
|
||||||
|
return existing;
|
||||||
|
} else {
|
||||||
|
const tl = new RoomTimeline(this, this.room);
|
||||||
|
await tl.paginate("b", limit);
|
||||||
|
this.timelines.add(tl);
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: respect limit?
|
||||||
|
const existing = this.timelineMap.get(at);
|
||||||
|
if (existing) {
|
||||||
|
const fetchCount = limit - existing.getEvents().length;
|
||||||
|
if (fetchCount > 0) await existing.paginate("f", fetchCount);
|
||||||
|
return existing;
|
||||||
|
} else {
|
||||||
|
const context = await this.client.net.fetchContext(this.room.id, at);
|
||||||
|
const tl = new RoomTimeline(this, this.room);
|
||||||
const events = context.events_before
|
const events = context.events_before
|
||||||
.reverse()
|
.reverse()
|
||||||
.concat([context.event])
|
.concat([context.event])
|
||||||
|
@ -231,30 +273,10 @@ export class TimelineSet {
|
||||||
tl.prevBatch = context.start;
|
tl.prevBatch = context.start;
|
||||||
tl.nextBatch = context.end;
|
tl.nextBatch = context.end;
|
||||||
this.timelines.add(tl);
|
this.timelines.add(tl);
|
||||||
|
// FIXME: merge timelines
|
||||||
|
for (const event of events) this.timelineMap.set(event.id, tl);
|
||||||
return tl;
|
return tl;
|
||||||
}
|
}
|
||||||
|
|
||||||
_appendEvents(events: Array<Event>) {
|
|
||||||
// FIXME: there should only be one live timeline for each room and
|
|
||||||
// thread, they need to be merged together
|
|
||||||
for (const event of events) {
|
|
||||||
this.room.events.set(event.id, event);
|
|
||||||
|
|
||||||
this.live._eventList.push(...events);
|
|
||||||
for (const event of events) this.live.emit("timelineAppend", event);
|
|
||||||
|
|
||||||
const threadId = event.content["m.relations"]?.find((rel: any) => rel.rel_type === "m.thread")?.event_id;
|
|
||||||
const thread = this.room.threads.get(threadId);
|
|
||||||
if (thread) {
|
|
||||||
thread.messageCount++;
|
|
||||||
thread.latestEvent = event;
|
|
||||||
|
|
||||||
const tl = [...this.timelines].find(i => i instanceof ThreadTimeline && i.thread === thread && i.isAtEnd) as ThreadTimeline | undefined;
|
|
||||||
if (tl) {
|
|
||||||
tl._eventList.push(event);
|
|
||||||
tl.emit("timelineAppend", event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue