Skip to content

Commit 9e2acc8

Browse files
authored
Add an action to search for users in the user directory according to MSC3973 (#79)
Signed-off-by: Dominik Henneke <[email protected]>
1 parent 839d5c2 commit 9e2acc8

9 files changed

+442
-0
lines changed

src/ClientWidgetApi.ts

+49
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ import {
7777
IReadRelationsFromWidgetActionRequest,
7878
IReadRelationsFromWidgetResponseData,
7979
} from "./interfaces/ReadRelationsAction";
80+
import {
81+
IUserDirectorySearchFromWidgetActionRequest,
82+
IUserDirectorySearchFromWidgetResponseData,
83+
} from "./interfaces/UserDirectorySearchAction";
8084

8185
/**
8286
* API handler for the client side of widgets. This raises events
@@ -619,6 +623,49 @@ export class ClientWidgetApi extends EventEmitter {
619623
}
620624
}
621625

626+
private async handleUserDirectorySearch(request: IUserDirectorySearchFromWidgetActionRequest) {
627+
if (!this.hasCapability(MatrixCapabilities.MSC3973UserDirectorySearch)) {
628+
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
629+
error: { message: "Missing capability" },
630+
});
631+
}
632+
633+
if (typeof request.data.search_term !== 'string') {
634+
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
635+
error: { message: "Invalid request - missing search term" },
636+
});
637+
}
638+
639+
if (request.data.limit !== undefined && request.data.limit < 0) {
640+
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
641+
error: { message: "Invalid request - limit out of range" },
642+
});
643+
}
644+
645+
try {
646+
const result = await this.driver.searchUserDirectory(
647+
request.data.search_term, request.data.limit,
648+
);
649+
650+
return this.transport.reply<IUserDirectorySearchFromWidgetResponseData>(
651+
request,
652+
{
653+
limited: result.limited,
654+
results: result.results.map(r => ({
655+
user_id: r.userId,
656+
display_name: r.displayName,
657+
avatar_url: r.avatarUrl,
658+
})),
659+
},
660+
);
661+
} catch (e) {
662+
console.error("error searching in the user directory", e);
663+
await this.transport.reply<IWidgetApiErrorResponseData>(request, {
664+
error: { message: "Unexpected error while searching in the user directory" },
665+
});
666+
}
667+
}
668+
622669
private handleMessage(ev: CustomEvent<IWidgetApiRequest>) {
623670
if (this.isStopped) return;
624671
const actionEv = new CustomEvent(`action:${ev.detail.action}`, {
@@ -650,6 +697,8 @@ export class ClientWidgetApi extends EventEmitter {
650697
return this.handleUnwatchTurnServers(<IUnwatchTurnServersRequest>ev.detail);
651698
case WidgetApiFromWidgetAction.MSC3869ReadRelations:
652699
return this.handleReadRelations(<IReadRelationsFromWidgetActionRequest>ev.detail);
700+
case WidgetApiFromWidgetAction.MSC3973UserDirectorySearch:
701+
return this.handleUserDirectorySearch(<IUserDirectorySearchFromWidgetActionRequest>ev.detail)
653702
default:
654703
return this.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
655704
error: {

src/WidgetApi.ts

+30
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ import {
6868
IReadRelationsFromWidgetRequestData,
6969
IReadRelationsFromWidgetResponseData,
7070
} from "./interfaces/ReadRelationsAction";
71+
import {
72+
IUserDirectorySearchFromWidgetRequestData,
73+
IUserDirectorySearchFromWidgetResponseData,
74+
} from "./interfaces/UserDirectorySearchAction";
7175

7276
/**
7377
* API handler for widgets. This raises events for each action
@@ -592,6 +596,32 @@ export class WidgetApi extends EventEmitter {
592596
}
593597
}
594598

599+
/**
600+
* Search for users in the user directory.
601+
* @param searchTerm The term to search for.
602+
* @param limit The maximum number of results to return. If not supplied, the
603+
* @returns Resolves to the search results.
604+
*/
605+
public async searchUserDirectory(
606+
searchTerm: string,
607+
limit?: number,
608+
): Promise<IUserDirectorySearchFromWidgetResponseData> {
609+
const versions = await this.getClientVersions();
610+
if (!versions.includes(UnstableApiVersion.MSC3973)) {
611+
throw new Error("The user_directory_search action is not supported by the client.")
612+
}
613+
614+
const data: IUserDirectorySearchFromWidgetRequestData = {
615+
search_term: searchTerm,
616+
limit,
617+
};
618+
619+
return this.transport.send<
620+
IUserDirectorySearchFromWidgetRequestData,
621+
IUserDirectorySearchFromWidgetResponseData
622+
>(WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data);
623+
}
624+
595625
/**
596626
* Starts the communication channel. This should be done early to ensure
597627
* that messages are not missed. Communication can only be stopped by the client.

src/driver/WidgetDriver.ts

+22
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ export interface IReadEventRelationsResult {
3232
prevBatch?: string;
3333
}
3434

35+
export interface ISearchUserDirectoryResult {
36+
limited: boolean;
37+
results: Array<{
38+
userId: string;
39+
displayName?: string;
40+
avatarUrl?: string;
41+
}>;
42+
}
43+
3544
/**
3645
* Represents the functions and behaviour the widget-api is unable to
3746
* do, such as prompting the user for information or interacting with
@@ -222,4 +231,17 @@ export abstract class WidgetDriver {
222231
public getTurnServers(): AsyncGenerator<ITurnServer> {
223232
throw new Error("TURN server support is not implemented");
224233
}
234+
235+
/**
236+
* Search for users in the user directory.
237+
* @param searchTerm The term to search for.
238+
* @param limit The maximum number of results to return. If not supplied, the
239+
* @returns Resolves to the search results.
240+
*/
241+
public searchUserDirectory(
242+
searchTerm: string,
243+
limit?: number,
244+
): Promise<ISearchUserDirectoryResult> {
245+
return Promise.resolve({ limited: false, results: [] });
246+
}
225247
}

src/interfaces/ApiVersion.ts

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export enum UnstableApiVersion {
2929
MSC3819 = "org.matrix.msc3819",
3030
MSC3846 = "town.robin.msc3846",
3131
MSC3869 = "org.matrix.msc3869",
32+
MSC3973 = "org.matrix.msc3973",
3233
}
3334

3435
export type ApiVersion = MatrixApiVersion | UnstableApiVersion | string;
@@ -45,4 +46,5 @@ export const CurrentApiVersions: ApiVersion[] = [
4546
UnstableApiVersion.MSC3819,
4647
UnstableApiVersion.MSC3846,
4748
UnstableApiVersion.MSC3869,
49+
UnstableApiVersion.MSC3973,
4850
];

src/interfaces/Capabilities.ts

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export enum MatrixCapabilities {
3030
*/
3131
MSC2931Navigate = "org.matrix.msc2931.navigate",
3232
MSC3846TurnServers = "town.robin.msc3846.turn_servers",
33+
/**
34+
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
35+
*/
36+
MSC3973UserDirectorySearch = "org.matrix.msc3973.user_directory_search",
3337
}
3438

3539
export type Capability = MatrixCapabilities | string;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2023 Nordeck IT + Consulting GmbH.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest";
18+
import { IWidgetApiResponseData } from "./IWidgetApiResponse";
19+
import { WidgetApiFromWidgetAction } from "./WidgetApiAction";
20+
21+
export interface IUserDirectorySearchFromWidgetRequestData extends IWidgetApiRequestData {
22+
search_term: string; // eslint-disable-line camelcase
23+
limit?: number;
24+
}
25+
26+
export interface IUserDirectorySearchFromWidgetActionRequest extends IWidgetApiRequest {
27+
action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch;
28+
data: IUserDirectorySearchFromWidgetRequestData;
29+
}
30+
31+
export interface IUserDirectorySearchFromWidgetResponseData extends IWidgetApiResponseData {
32+
limited: boolean;
33+
results: Array<{
34+
user_id: string; // eslint-disable-line camelcase
35+
display_name?: string; // eslint-disable-line camelcase
36+
avatar_url?: string; // eslint-disable-line camelcase
37+
}>;
38+
}
39+
40+
export interface IUserDirectorySearchFromWidgetActionResponse extends IUserDirectorySearchFromWidgetActionRequest {
41+
response: IUserDirectorySearchFromWidgetResponseData;
42+
}

src/interfaces/WidgetApiAction.ts

+5
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ export enum WidgetApiFromWidgetAction {
6262
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
6363
*/
6464
MSC3869ReadRelations = "org.matrix.msc3869.read_relations",
65+
66+
/**
67+
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
68+
*/
69+
MSC3973UserDirectorySearch = "org.matrix.msc3973.user_directory_search",
6570
}
6671

6772
export type WidgetApiAction = WidgetApiToWidgetAction | WidgetApiFromWidgetAction | string;

0 commit comments

Comments
 (0)