Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/src/packages/frontend/client/client.ts
Views: 926
/*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 { Messages } from "./messages";12import { QueryClient } from "./query";13import { TimeClient } from "./time";14import { AccountClient } from "./account";15import { ProjectClient } from "./project";16import { AdminClient } from "./admin";17import { LLMClient } from "./llm";18import { PurchasesClient } from "./purchases";19import { JupyterClient } from "./jupyter";20import { SyncClient } from "@cocalc/sync/client/sync-client";21import { UsersClient } from "./users";22import { FileClient } from "./file";23import { TrackingClient } from "./tracking";24import { HubClient } from "./hub";25import { IdleClient } from "./idle";26import { version } from "@cocalc/util/smc-version";27import { start_metrics } from "../prom-client";28import { setup_global_cocalc } from "./console";29import { Query } from "@cocalc/sync/table";30import debug from "debug";3132// This DEBUG variable comes from webpack:33declare const DEBUG;3435const log = debug("cocalc");36// To get actual extreme logging though you have to also set37//38// localStorage.DEBUG='cocalc'39//40// and refresh your browser. Example, this will turn on41// all the sync activity logging and everything that calls42// client.dbg.4344export type AsyncCall = (opts: object) => Promise<any>;4546export interface WebappClient extends EventEmitter {47account_id?: string;4849stripe: StripeClient;50project_collaborators: ProjectCollaborators;51support_tickets: SupportTickets;52messages: Messages;53query_client: QueryClient;54time_client: TimeClient;55account_client: AccountClient;56project_client: ProjectClient;57admin_client: AdminClient;58openai_client: LLMClient;59purchases_client: PurchasesClient;60jupyter_client: JupyterClient;61sync_client: SyncClient;62users_client: UsersClient;63file_client: FileClient;64tracking_client: TrackingClient;65hub_client: HubClient;66idle_client: IdleClient;67client: Client;6869sync_string: Function;70sync_db: Function;7172server_time: Function;73get_username: Function;74is_signed_in: () => boolean;75synctable_project: Function;76project_websocket: Function;77prettier: Function;78exec: Function;79touch_project: (project_id: string, compute_server_id?: number) => void;80ipywidgetsGetBuffer: (81project_id: string,82path: string,83model_id: string,84buffer_path: string,85) => Promise<ArrayBuffer>;86log_error: (any) => void;87async_call: AsyncCall;88user_tracking: Function;89send: Function;90call: Function;91dbg: (str: string) => Function;92is_project: () => boolean;93is_browser: () => boolean;94is_compute_server: () => boolean;95is_connected: () => boolean;96query: Query; // TODO typing97query_cancel: Function;98is_deleted: (filename: string, project_id: string) => boolean;99set_deleted: Function;100mark_file: (opts: any) => Promise<void>;101set_connected?: Function;102version: Function;103}104105export const WebappClient = null; // webpack + TS es2020 modules need this106107/*108Connection events:109- 'connecting' -- trying to establish a connection110- 'connected' -- succesfully established a connection; data is the protocol as a string111- 'error' -- called when an error occurs112- 'output' -- received some output for stateless execution (not in any session)113- 'execute_javascript' -- code that server wants client to run (not for a particular session)114- 'message' -- emitted when a JSON message is received on('message', (obj) -> ...)115- 'data' -- emitted when raw data (not JSON) is received -- on('data, (id, data) -> )...116- 'signed_in' -- server pushes a succesful sign in to the client (e.g., due to117'remember me' functionality); data is the signed_in message.118- 'project_list_updated' -- sent whenever the list of projects owned by this user119changed; data is empty -- browser could ignore this unless120the project list is currently being displayed.121- 'project_data_changed - sent when data about a specific project has changed,122e.g., title/description/settings/etc.123- 'new_version', number -- sent when there is a new version of the source code so client should refresh124*/125126class Client extends EventEmitter implements WebappClient {127account_id?: string;128stripe: StripeClient;129project_collaborators: ProjectCollaborators;130support_tickets: SupportTickets;131messages: Messages;132query_client: QueryClient;133time_client: TimeClient;134account_client: AccountClient;135project_client: ProjectClient;136admin_client: AdminClient;137openai_client: LLMClient;138purchases_client: PurchasesClient;139jupyter_client: JupyterClient;140sync_client: SyncClient;141users_client: UsersClient;142file_client: FileClient;143tracking_client: TrackingClient;144hub_client: HubClient;145idle_client: IdleClient;146client: Client;147148sync_string: Function;149sync_db: Function;150151server_time: Function; // TODO: make this () => Date and deal with the fallout152ping_test: Function;153get_username: Function;154is_signed_in: () => boolean;155synctable_project: Function;156project_websocket: Function;157prettier: Function;158exec: Function;159touch_project: (project_id: string, compute_server_id?: number) => void;160ipywidgetsGetBuffer: (161project_id: string,162path: string,163model_id: string,164buffer_path: string,165) => Promise<ArrayBuffer>;166167log_error: (any) => void;168async_call: AsyncCall;169user_tracking: Function;170send: Function;171call: Function;172is_connected: () => boolean;173query: typeof QueryClient.prototype.query;174query_cancel: Function;175176is_deleted: (filename: string, project_id: string) => boolean;177mark_file: (opts: any) => Promise<void>;178179idle_reset: Function;180latency: Function;181synctable_database: Function;182async_query: Function;183alert_message: Function;184185constructor() {186super();187188if (DEBUG) {189this.dbg = this.dbg.bind(this);190} else {191this.dbg = (..._) => {192return (..._) => {};193};194}195196this.hub_client = bind_methods(new HubClient(this));197this.is_signed_in = this.hub_client.is_signed_in.bind(this.hub_client);198this.is_connected = this.hub_client.is_connected.bind(this.hub_client);199this.call = this.hub_client.call.bind(this.hub_client);200this.async_call = this.hub_client.async_call.bind(this.hub_client);201this.latency = this.hub_client.latency.bind(this.hub_client);202203this.stripe = bind_methods(new StripeClient(this.call.bind(this)));204this.project_collaborators = bind_methods(205new ProjectCollaborators(this.async_call.bind(this)),206);207this.support_tickets = bind_methods(208new SupportTickets(this.async_call.bind(this)),209);210this.messages = new Messages();211this.query_client = bind_methods(new QueryClient(this));212this.time_client = bind_methods(new TimeClient(this));213this.account_client = bind_methods(new AccountClient(this));214this.project_client = bind_methods(new ProjectClient(this));215216this.sync_client = bind_methods(new SyncClient(this));217this.sync_string = this.sync_client.sync_string;218this.sync_db = this.sync_client.sync_db;219220this.admin_client = bind_methods(221new AdminClient(this.async_call.bind(this)),222);223this.openai_client = bind_methods(new LLMClient(this));224//this.purchases_client = bind_methods(new PurchasesClient(this));225this.purchases_client = bind_methods(new PurchasesClient());226this.jupyter_client = bind_methods(227new JupyterClient(this.async_call.bind(this)),228);229this.users_client = bind_methods(230new UsersClient(this.call.bind(this), this.async_call.bind(this)),231);232this.tracking_client = bind_methods(new TrackingClient(this));233this.file_client = bind_methods(new FileClient(this.async_call.bind(this)));234this.idle_client = bind_methods(new IdleClient(this));235236// Expose a public API as promised by WebappClient237this.server_time = this.time_client.server_time.bind(this.time_client);238this.ping_test = this.time_client.ping_test.bind(this.time_client);239240this.idle_reset = this.idle_client.idle_reset.bind(this.idle_client);241242this.exec = this.project_client.exec.bind(this.project_client);243this.touch_project = this.project_client.touch_project.bind(this.project_client);244this.ipywidgetsGetBuffer = this.project_client.ipywidgetsGetBuffer.bind(245this.project_client,246);247248this.synctable_database = this.sync_client.synctable_database.bind(249this.sync_client,250);251this.synctable_project = this.sync_client.synctable_project.bind(252this.sync_client,253);254255this.query = this.query_client.query.bind(this.query_client);256this.async_query = this.query_client.query.bind(this.query_client);257this.query_cancel = this.query_client.cancel.bind(this.query_client);258259this.is_deleted = this.file_client.is_deleted.bind(this.file_client);260this.mark_file = this.file_client.mark_file.bind(this.file_client);261262this.alert_message = alert_message;263264// Tweaks the maximum number of listeners an EventEmitter can have --265// 0 would mean unlimited266// The issue is https://github.com/sagemathinc/cocalc/issues/1098 and267// the errors we got are268// (node) warning: possible EventEmitter memory leak detected.269// 301 listeners added.270// Use emitter.setMaxListeners() to increase limit.271// every open file/table/sync db listens for connect event, which adds up.272this.setMaxListeners(3000);273274// start pinging -- not used/needed for primus,275// but *is* needed for getting information about276// server_time skew and showing ping time to user.277this.once("connected", async () => {278this.time_client.ping(true);279// Ping again a few seconds after connecting the first time,280// after things have settled down a little (to not throw off281// ping time).282await delay(5000);283this.time_client.ping(); // this will ping periodically284});285286this.init_prom_client();287this.init_global_cocalc();288289bind_methods(this);290}291292private async init_global_cocalc(): Promise<void> {293await delay(1);294setup_global_cocalc(this);295}296297private init_prom_client(): void {298this.on("start_metrics", start_metrics);299}300301public dbg(f): Function {302if (log.enabled) {303return (...args) => log(new Date().toISOString(), f, ...args);304} else {305return (..._) => {};306}307// return function (...m) {308// console.log(`${new Date().toISOString()} - Client.${f}: `, ...m);309// };310}311312public version(): number {313return version;314}315316// account_id of this client317public client_id(): string | undefined {318return this.account_id;319}320321// false since this client is not a project322public is_project(): boolean {323return false;324}325326public is_browser(): boolean {327return true;328}329330public is_compute_server(): boolean {331return false;332}333334// true since this client is a user335public is_user(): boolean {336return true;337}338339public set_deleted(): void {340throw Error("not implemented for frontend");341}342}343344export const webapp_client = new Client();345346347