Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/conat/client.ts
1452 views
1
/*
2
DEVELOPMENT:
3
4
~/cocalc/src/packages/backend$ node
5
> require('@cocalc/backend/conat'); c = require('@cocalc/conat/client').getClient()
6
> c.state
7
'connected'
8
*/
9
10
import { init } from "./time";
11
import { EventEmitter } from "events";
12
import { type Client as ConatClient } from "@cocalc/conat/core/client";
13
14
interface Client {
15
conat: (opts?) => ConatClient;
16
account_id?: string;
17
project_id?: string;
18
compute_server_id?: number;
19
getLogger?: (name) => Logger;
20
// if defined, causes a client-defined version of reconnecting.
21
reconnect?: () => Promise<void>;
22
}
23
24
type State = "closed" | "connected" | "connecting" | "disconnected";
25
26
interface Logger {
27
debug: Function;
28
info: Function;
29
warn: Function;
30
}
31
32
const FALLBACK_LOGGER = {
33
debug: () => {},
34
info: () => {},
35
warn: () => {},
36
silly: () => {},
37
} as Logger;
38
39
export class ClientWithState extends EventEmitter {
40
conatClient?: ConatClient;
41
account_id?: string;
42
project_id?: string;
43
compute_server_id?: number;
44
state: State = "disconnected";
45
_getLogger?: (name) => Logger;
46
_reconnect?: () => Promise<void>;
47
conat: () => ConatClient;
48
49
constructor(client: Client) {
50
super();
51
// many things potentially listen for these events -- way more than 10 things.
52
this.setMaxListeners(1000);
53
// this.conat only ever returns *ONE* connection
54
this.conat = () => {
55
if (this.state == "closed") {
56
throw Error("client already closed");
57
}
58
if (this.conatClient) {
59
return this.conatClient;
60
}
61
this.conatClient = client.conat();
62
return this.conatClient;
63
};
64
this.account_id = client.account_id;
65
this.project_id = client.project_id;
66
this.compute_server_id = client.compute_server_id;
67
this._getLogger = client.getLogger;
68
this._reconnect = client.reconnect;
69
}
70
71
numSubscriptions = () => {
72
this.conatClient?.numSubscriptions() ?? 0;
73
};
74
75
reconnect = async () => {
76
await this._reconnect?.();
77
};
78
79
getLogger = (name): Logger => {
80
if (this._getLogger != null) {
81
return this._getLogger(name);
82
} else {
83
return FALLBACK_LOGGER;
84
}
85
};
86
87
close = () => {
88
this.conatClient?.close();
89
this.setConnectionState("closed");
90
this.removeAllListeners();
91
delete this.conatClient;
92
};
93
94
private setConnectionState = (state: State) => {
95
if (state == this.state) {
96
return;
97
}
98
this.state = state;
99
this.emit(state);
100
this.emit("state", state);
101
};
102
}
103
104
// do NOT do this until some explicit use of conat is initiated, since we shouldn't
105
// connect to conat until something tries to do so.
106
let timeInitialized = false;
107
function initTime() {
108
if (timeInitialized) {
109
return;
110
}
111
timeInitialized = true;
112
init();
113
}
114
115
let globalClient: null | ClientWithState = null;
116
export function setConatClient(client: Client) {
117
globalClient = new ClientWithState(client);
118
}
119
120
export async function reconnect() {
121
await globalClient?.reconnect();
122
}
123
124
export const conat: () => ConatClient = () => {
125
if (globalClient == null) {
126
throw Error("must set the global Conat client");
127
}
128
initTime();
129
return globalClient.conat();
130
};
131
132
export function getClient(): ClientWithState {
133
if (globalClient == null) {
134
throw Error("must set the global Conat client");
135
}
136
initTime();
137
return globalClient;
138
}
139
140
function tmpLogger(s, name, logger) {
141
return (...args) => {
142
if (globalClient == null) {
143
return;
144
}
145
const f = globalClient.getLogger(name);
146
for (const k in f) {
147
logger[k] = f[k];
148
}
149
logger[s](...args);
150
};
151
}
152
153
export function getLogger(name) {
154
// weird code since getLogger can get called very early, before
155
// globalClient is even initialized.
156
try {
157
if (globalClient != null) {
158
return globalClient.getLogger(name);
159
}
160
} catch {}
161
// make logger that starts working after global client is set
162
const logger: any = {};
163
for (const s of ["debug", "info", "warn", "silly"]) {
164
logger[s] = tmpLogger(s, name, logger);
165
}
166
logger.silly = logger.debug;
167
return logger;
168
}
169
170
export function numSubscriptions(): number {
171
return globalClient?.numSubscriptions() ?? 0;
172
}
173
174