This repository was archived by the owner on Oct 22, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathOwnProfileStore.ts
176 lines (150 loc) · 6.47 KB
/
OwnProfileStore.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
Copyright 2024 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { MatrixEvent, RoomStateEvent, MatrixError, User, UserEvent, EventType } from "matrix-js-sdk/src/matrix";
import { throttle } from "lodash";
import { ActionPayload } from "../dispatcher/payloads";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { _t } from "../languageHandler";
import { mediaFromMxc } from "../customisations/Media";
interface IState {
displayName?: string;
avatarUrl?: string;
fetchedAt?: number;
}
const KEY_DISPLAY_NAME = "mx_profile_displayname";
const KEY_AVATAR_URL = "mx_profile_avatar_url";
export class OwnProfileStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => {
const instance = new OwnProfileStore();
return instance;
})();
private monitoredUser: User | null = null;
public constructor() {
// seed from localstorage because otherwise we won't get these values until a whole network
// round-trip after the client is ready, and we often load widgets in that time, and we'd
// and up passing them an incorrect display name
super(defaultDispatcher, {
displayName: window.localStorage.getItem(KEY_DISPLAY_NAME) || undefined,
avatarUrl: window.localStorage.getItem(KEY_AVATAR_URL) || undefined,
});
}
public static get instance(): OwnProfileStore {
return OwnProfileStore.internalInstance;
}
/**
* Gets the display name for the user, or null if not present.
*/
public get displayName(): string | null {
if (!this.matrixClient) return this.state.displayName || null;
if (this.matrixClient.isGuest()) {
return _t("common|guest");
} else if (this.state.displayName) {
return this.state.displayName;
} else {
return this.matrixClient.getUserId();
}
}
public get isProfileInfoFetched(): boolean {
return !!this.state.fetchedAt;
}
/**
* Gets the MXC URI of the user's avatar, or null if not present.
*/
public get avatarMxc(): string | null {
return this.state.avatarUrl || null;
}
/**
* Gets the user's avatar as an HTTP URL of the given size. If the user's
* avatar is not present, this returns null.
* @param size The size of the avatar. If zero, a full res copy of the avatar
* will be returned as an HTTP URL.
* @returns The HTTP URL of the user's avatar
*/
public getHttpAvatarUrl(size = 0): string | null {
if (!this.avatarMxc) return null;
const media = mediaFromMxc(this.avatarMxc);
if (!size || size <= 0) {
return media.srcHttp;
} else {
return media.getSquareThumbnailHttp(size);
}
}
protected async onNotReady(): Promise<void> {
this.onProfileUpdate.cancel();
if (this.monitoredUser) {
this.monitoredUser.removeListener(UserEvent.DisplayName, this.onProfileUpdate);
this.monitoredUser.removeListener(UserEvent.AvatarUrl, this.onProfileUpdate);
}
this.matrixClient?.removeListener(RoomStateEvent.Events, this.onStateEvents);
await this.reset({});
}
protected async onReady(): Promise<void> {
if (!this.matrixClient) return;
const myUserId = this.matrixClient.getSafeUserId();
this.monitoredUser = this.matrixClient.getUser(myUserId);
if (this.monitoredUser) {
this.monitoredUser.on(UserEvent.DisplayName, this.onProfileUpdate);
this.monitoredUser.on(UserEvent.AvatarUrl, this.onProfileUpdate);
}
// We also have to listen for membership events for ourselves as the above User events
// are fired only with presence, which matrix.org (and many others) has disabled.
this.matrixClient.on(RoomStateEvent.Events, this.onStateEvents);
await this.onProfileUpdate(); // trigger an initial update
}
protected async onAction(payload: ActionPayload): Promise<void> {
// we don't actually do anything here
}
private onProfileUpdate = throttle(
async (): Promise<void> => {
if (!this.matrixClient) return;
// We specifically do not use the User object we stored for profile info as it
// could easily be wrong (such as per-room instead of global profile).
let profileInfo: { displayname?: string; avatar_url?: string } = {
displayname: undefined,
avatar_url: undefined,
};
try {
profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getSafeUserId());
} catch (error: unknown) {
if (!(error instanceof MatrixError) || error.errcode !== "M_NOT_FOUND") {
/**
* Raise any other error than M_NOT_FOUND.
* M_NOT_FOUND could occur if there is no user profile.
* {@link https://spec.matrix.org/v1.7/client-server-api/#get_matrixclientv3profileuserid}
* We should then assume an empty profile, emit UPDATE_EVENT etc..
*/
throw error;
}
}
if (profileInfo.displayname) {
window.localStorage.setItem(KEY_DISPLAY_NAME, profileInfo.displayname);
} else {
window.localStorage.removeItem(KEY_DISPLAY_NAME);
}
if (profileInfo.avatar_url) {
window.localStorage.setItem(KEY_AVATAR_URL, profileInfo.avatar_url);
} else {
window.localStorage.removeItem(KEY_AVATAR_URL);
}
await this.updateState({
displayName: profileInfo.displayname,
avatarUrl: profileInfo.avatar_url,
fetchedAt: Date.now(),
});
},
200,
{ trailing: true, leading: true },
);
private onStateEvents = async (ev: MatrixEvent): Promise<void> => {
const myUserId = MatrixClientPeg.safeGet().getUserId();
if (ev.getType() === EventType.RoomMember && ev.getSender() === myUserId && ev.getStateKey() === myUserId) {
await this.onProfileUpdate();
}
};
}