Path: blob/master/src/packages/frontend/client/users.ts
1503 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { User } from "../frame-editors/generic/client";6import { isChatBot, chatBotName } from "@cocalc/frontend/account/chatbot";7import TTL from "@isaacs/ttlcache";8import { reuseInFlight } from "@cocalc/util/reuse-in-flight";9import type { WebappClient } from "./client";1011const nameCache = new TTL({ ttl: 60 * 1000 });1213export class UsersClient {14private client: WebappClient;1516constructor(client) {17this.client = client;18}1920/*21There are two possible item types in the query list: email addresses22and strings that are not email addresses. An email query item will return23account id, first name, last name, and email address for the unique24account with that email address, if there is one. A string query item25will return account id, first name, and last name for all matching26accounts.2728We do not reveal email addresses of users queried by name to non admins.2930String query matches first and last names that start with the given string.31If a string query item consists of two strings separated by space,32the search will return accounts in which the first name begins with one33of the two strings and the last name begins with the other.34String and email queries may be mixed in the list for a single35user_search call. Searches are case-insensitive.3637Note: there is a hard limit of 50 returned items in the results, except for38admins that can search for more.39*/40user_search = reuseInFlight(41async ({42query,43limit = 20,44admin,45only_email,46}: {47query: string;48limit?: number;49admin?: boolean; // admins can do an admin version of the query, which also does substring searches on email address (not just name)50only_email?: boolean; // search only via email address51}): Promise<User[]> => {52return await this.client.conat_client.hub.system.userSearch({53query,54limit,55admin,56only_email,57});58},59);6061// Gets username with given account_id. We use caching and aggregate to62// makes it so this never calls to the backend more than once at a time63// (per minute) for a given account_id.64get_username = reuseInFlight(65async (66account_id: string,67): Promise<{ first_name: string; last_name: string }> => {68if (isChatBot(account_id)) {69return { first_name: chatBotName(account_id), last_name: "" };70}71const v = await this.getNames([account_id]);72const u = v[account_id];73if (u == null) {74throw Error(`no user with account_id ${account_id}`);75}76return u;77},78);7980// get map from account_id to first_name, last_name (or undefined if no account); cached81// for about a minute client side.82getNames = reuseInFlight(async (account_ids: string[]) => {83const x: {84[account_id: string]:85| {86first_name: string;87last_name: string;88profile?: { color?: string; image?: string };89}90| undefined;91} = {};92const v: string[] = [];93for (const account_id of account_ids) {94if (nameCache.has(account_id)) {95x[account_id] = nameCache.get(account_id);96} else {97v.push(account_id);98}99}100if (v.length > 0) {101const names = await this.client.conat_client.hub.system.getNames(v);102for (const account_id of v) {103// iterate over v to record accounts that don't exist too104x[account_id] = names[account_id];105nameCache.set(account_id, names[account_id]);106}107}108return x;109});110}111112113