Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/client/client.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/4import { bind_methods } from "@cocalc/util/misc";5import { EventEmitter } from "events";6import { delay } from "awaiting";7import { alert_message } from "../alerts";8import { StripeClient } from "./stripe";9import { ProjectCollaborators } from "./project-collaborators";10import { SupportTickets } from "./support";11import { QueryClient } from "./query";12import { TimeClient } from "./time";13import { AccountClient } from "./account";14import { ProjectClient } from "./project";15import { AdminClient } from "./admin";16import { LLMClient } from "./llm";17import { PurchasesClient } from "./purchases";18import { JupyterClient } from "./jupyter";19import { SyncClient } from "@cocalc/sync/client/sync-client";20import { UsersClient } from "./users";21import { FileClient } from "./file";22import { TrackingClient } from "./tracking";23import { HubClient } from "./hub";24import { IdleClient } from "./idle";25import { version } from "@cocalc/util/smc-version";26import { start_metrics } from "../prom-client";27import { setup_global_cocalc } from "./console";28import { Query } from "@cocalc/sync/table";29import debug from "debug";3031// This DEBUG variable comes from webpack:32declare const DEBUG;3334const log = debug("cocalc");35// To get actual extreme logging though you have to also set36//37// localStorage.DEBUG='cocalc'38//39// and refresh your browser. Example, this will turn on40// all the sync activity logging and everything that calls41// client.dbg.4243export type AsyncCall = (opts: object) => Promise<any>;4445export interface WebappClient extends EventEmitter {46account_id?: string;4748stripe: StripeClient;49project_collaborators: ProjectCollaborators;50support_tickets: SupportTickets;51query_client: QueryClient;52time_client: TimeClient;53account_client: AccountClient;54project_client: ProjectClient;55admin_client: AdminClient;56openai_client: LLMClient;57purchases_client: PurchasesClient;58jupyter_client: JupyterClient;59sync_client: SyncClient;60users_client: UsersClient;61file_client: FileClient;62tracking_client: TrackingClient;63hub_client: HubClient;64idle_client: IdleClient;65client: Client;6667sync_string: Function;68sync_db: Function;6970server_time: Function;71get_username: Function;72is_signed_in: () => boolean;73synctable_project: Function;74project_websocket: Function;75prettier: Function;76exec: Function; // TODO: rewrite project_actions.ts to not use this at all.77touch_project: (project_id: string) => void;78ipywidgetsGetBuffer: (79project_id: string,80path: string,81model_id: string,82buffer_path: string,83) => Promise<ArrayBuffer>;84log_error: (any) => void;85async_call: AsyncCall;86user_tracking: Function;87send: Function;88call: Function;89dbg: (str: string) => Function;90is_project: () => boolean;91is_browser: () => boolean;92is_compute_server: () => boolean;93is_connected: () => boolean;94query: Query; // TODO typing95query_cancel: Function;96is_deleted: (filename: string, project_id: string) => boolean;97set_deleted: Function;98mark_file: (opts: any) => Promise<void>;99100set_connected?: Function;101version: Function;102}103104export const WebappClient = null; // webpack + TS es2020 modules need this105106/*107Connection events:108- 'connecting' -- trying to establish a connection109- 'connected' -- succesfully established a connection; data is the protocol as a string110- 'error' -- called when an error occurs111- 'output' -- received some output for stateless execution (not in any session)112- 'execute_javascript' -- code that server wants client to run (not for a particular session)113- 'message' -- emitted when a JSON message is received on('message', (obj) -> ...)114- 'data' -- emitted when raw data (not JSON) is received -- on('data, (id, data) -> )...115- 'signed_in' -- server pushes a succesful sign in to the client (e.g., due to116'remember me' functionality); data is the signed_in message.117- 'project_list_updated' -- sent whenever the list of projects owned by this user118changed; data is empty -- browser could ignore this unless119the project list is currently being displayed.120- 'project_data_changed - sent when data about a specific project has changed,121e.g., title/description/settings/etc.122- 'new_version', number -- sent when there is a new version of the source code so client should refresh123*/124125class Client extends EventEmitter implements WebappClient {126account_id?: string;127stripe: StripeClient;128project_collaborators: ProjectCollaborators;129support_tickets: SupportTickets;130query_client: QueryClient;131time_client: TimeClient;132account_client: AccountClient;133project_client: ProjectClient;134admin_client: AdminClient;135openai_client: LLMClient;136purchases_client: PurchasesClient;137jupyter_client: JupyterClient;138sync_client: SyncClient;139users_client: UsersClient;140file_client: FileClient;141tracking_client: TrackingClient;142hub_client: HubClient;143idle_client: IdleClient;144client: Client;145146sync_string: Function;147sync_db: Function;148149server_time: Function; // TODO: make this () => Date and deal with the fallout150ping_test: Function;151get_username: Function;152is_signed_in: () => boolean;153synctable_project: Function;154project_websocket: Function;155prettier: Function;156exec: Function; // TODO: rewrite project_actions.ts to not use this at all.157touch_project: (project_id: string) => void;158ipywidgetsGetBuffer: (159project_id: string,160path: string,161model_id: string,162buffer_path: string,163) => Promise<ArrayBuffer>;164165log_error: (any) => void;166async_call: AsyncCall;167user_tracking: Function;168send: Function;169call: Function;170is_connected: () => boolean;171query: typeof QueryClient.prototype.query;172query_cancel: Function;173174is_deleted: (filename: string, project_id: string) => boolean;175mark_file: (opts: any) => Promise<void>;176177idle_reset: Function;178latency: Function;179synctable_database: Function;180async_query: Function;181alert_message: Function;182183constructor() {184super();185186if (DEBUG) {187this.dbg = this.dbg.bind(this);188} else {189this.dbg = (..._) => {190return (..._) => {};191};192}193194this.hub_client = bind_methods(new HubClient(this));195this.is_signed_in = this.hub_client.is_signed_in.bind(this.hub_client);196this.is_connected = this.hub_client.is_connected.bind(this.hub_client);197this.call = this.hub_client.call.bind(this.hub_client);198this.async_call = this.hub_client.async_call.bind(this.hub_client);199this.latency = this.hub_client.latency.bind(this.hub_client);200201this.stripe = bind_methods(new StripeClient(this.call.bind(this)));202this.project_collaborators = bind_methods(203new ProjectCollaborators(this.async_call.bind(this)),204);205this.support_tickets = bind_methods(206new SupportTickets(this.async_call.bind(this)),207);208this.query_client = bind_methods(new QueryClient(this));209this.time_client = bind_methods(new TimeClient(this));210this.account_client = bind_methods(new AccountClient(this));211this.project_client = bind_methods(new ProjectClient(this));212213this.sync_client = bind_methods(new SyncClient(this));214this.sync_string = this.sync_client.sync_string;215this.sync_db = this.sync_client.sync_db;216217this.admin_client = bind_methods(218new AdminClient(this.async_call.bind(this)),219);220this.openai_client = bind_methods(new LLMClient(this));221//this.purchases_client = bind_methods(new PurchasesClient(this));222this.purchases_client = bind_methods(new PurchasesClient());223this.jupyter_client = bind_methods(224new JupyterClient(this.async_call.bind(this)),225);226this.users_client = bind_methods(227new UsersClient(this.call.bind(this), this.async_call.bind(this)),228);229this.tracking_client = bind_methods(new TrackingClient(this));230this.file_client = bind_methods(new FileClient(this.async_call.bind(this)));231this.idle_client = bind_methods(new IdleClient(this));232233// Expose a public API as promised by WebappClient234this.server_time = this.time_client.server_time.bind(this.time_client);235this.ping_test = this.time_client.ping_test.bind(this.time_client);236237this.idle_reset = this.idle_client.idle_reset.bind(this.idle_client);238239this.exec = this.project_client.exec.bind(this.project_client);240this.touch_project = this.project_client.touch.bind(this.project_client);241this.ipywidgetsGetBuffer = this.project_client.ipywidgetsGetBuffer.bind(242this.project_client,243);244245this.synctable_database = this.sync_client.synctable_database.bind(246this.sync_client,247);248this.synctable_project = this.sync_client.synctable_project.bind(249this.sync_client,250);251252this.query = this.query_client.query.bind(this.query_client);253this.async_query = this.query_client.query.bind(this.query_client);254this.query_cancel = this.query_client.cancel.bind(this.query_client);255256this.is_deleted = this.file_client.is_deleted.bind(this.file_client);257this.mark_file = this.file_client.mark_file.bind(this.file_client);258259this.alert_message = alert_message;260261// Tweaks the maximum number of listeners an EventEmitter can have --262// 0 would mean unlimited263// The issue is https://github.com/sagemathinc/cocalc/issues/1098 and264// the errors we got are265// (node) warning: possible EventEmitter memory leak detected.266// 301 listeners added.267// Use emitter.setMaxListeners() to increase limit.268// every open file/table/sync db listens for connect event, which adds up.269this.setMaxListeners(3000);270271// start pinging -- not used/needed for primus,272// but *is* needed for getting information about273// server_time skew and showing ping time to user.274this.once("connected", async () => {275this.time_client.ping(true);276// Ping again a few seconds after connecting the first time,277// after things have settled down a little (to not throw off278// ping time).279await delay(5000);280this.time_client.ping(); // this will ping periodically281});282283this.init_prom_client();284this.init_global_cocalc();285286bind_methods(this);287}288289private async init_global_cocalc(): Promise<void> {290await delay(1);291setup_global_cocalc(this);292}293294private init_prom_client(): void {295this.on("start_metrics", start_metrics);296}297298public dbg(f): Function {299if (log.enabled) {300return (...args) => log(new Date().toISOString(), f, ...args);301} else {302return (..._) => {};303}304// return function (...m) {305// console.log(`${new Date().toISOString()} - Client.${f}: `, ...m);306// };307}308309public version(): number {310return version;311}312313// account_id of this client314public client_id(): string | undefined {315return this.account_id;316}317318// false since this client is not a project319public is_project(): boolean {320return false;321}322323public is_browser(): boolean {324return true;325}326327public is_compute_server(): boolean {328return false;329}330331// true since this client is a user332public is_user(): boolean {333return true;334}335336public set_deleted(): void {337throw Error("not implemented for frontend");338}339}340341export const webapp_client = new Client();342343344