Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/project/conat/api/index.ts
1450 views
1
/*
2
3
DEVELOPMENT:
4
5
How to do development (so in a dev project doing cc-in-cc dev).
6
7
0. From the browser, terminate this api server running in the project:
8
9
await cc.client.conat_client.projectApi(cc.current()).system.terminate({service:'api'})
10
11
1. Create a file project-env.sh as explained in projects/conat/README.md, which defines these environment variables (your values will be different):
12
13
export COCALC_PROJECT_ID="00847397-d6a8-4cb0-96a8-6ef64ac3e6cf"
14
export COCALC_USERNAME=`echo $COCALC_PROJECT_ID | tr -d '-'`
15
export HOME="/projects/6b851643-360e-435e-b87e-f9a6ab64a8b1/cocalc/src/data/projects/$COCALC_PROJECT_ID"
16
export DATA=$HOME/.smc
17
18
# CRITICAL: make sure to create and set an api key! Otherwise you will be blocked:
19
export API_KEY=sk-OUwxAN8d0n7Ecd48000055
20
export COMPUTE_SERVER_ID=0
21
22
# optional for more logging
23
export DEBUG=cocalc:*
24
export DEBUG_CONSOLE=yes
25
26
If API_KEY is a project-wide API key, then you can change
27
COCALC_PROJECT_ID however you want and don't have to worry
28
about whether the project is running or the project secret
29
key changing when the project is restarted.
30
31
2. Then do this:
32
33
$ . project-env.sh
34
$ node
35
...
36
> require("@cocalc/project/conat/api/index").init()
37
38
You can then easily be able to grab some state, e.g., by writing this in any cocalc code,
39
rebuilding and restarting:
40
41
global.x = {...}
42
43
Remember, if you don't set API_KEY, then the project MUST be running so that the secret token in $HOME/.smc/secret_token is valid.
44
45
3. Use the browser to see the project is on the conat network and works:
46
47
a = cc.client.conat_client.projectApi({project_id:'81e0c408-ac65-4114-bad5-5f4b6539bd0e'});
48
await a.system.ping();
49
await a.system.exec({command:'echo $COCALC_PROJECT_ID'});
50
51
*/
52
53
import { type ProjectApi } from "@cocalc/conat/project/api";
54
import { connectToConat } from "@cocalc/project/conat/connection";
55
import { getSubject } from "../names";
56
import { terminate as terminateOpenFiles } from "@cocalc/project/conat/open-files";
57
import { close as closeListings } from "@cocalc/project/conat/listings";
58
import { project_id } from "@cocalc/project/data";
59
import { close as closeFilesRead } from "@cocalc/project/conat/files/read";
60
import { close as closeFilesWrite } from "@cocalc/project/conat/files/write";
61
import { getLogger } from "@cocalc/project/logger";
62
63
const logger = getLogger("conat:api");
64
65
export function init() {
66
serve();
67
}
68
69
let terminate = false;
70
async function serve() {
71
logger.debug("serve: create project conat api service");
72
const cn = connectToConat();
73
const subject = getSubject({ service: "api" });
74
// @ts-ignore
75
const name = `project-${project_id}`;
76
logger.debug(`serve: creating api service ${name}`);
77
const api = await cn.subscribe(subject);
78
logger.debug(`serve: subscribed to subject='${subject}'`);
79
await listen(api, subject);
80
}
81
82
async function listen(api, subject) {
83
for await (const mesg of api) {
84
if (terminate) {
85
return;
86
}
87
(async () => {
88
try {
89
await handleMessage(api, subject, mesg);
90
} catch (err) {
91
logger.debug(`WARNING: issue handling a message -- ${err}`);
92
}
93
})();
94
}
95
}
96
97
async function handleMessage(api, subject, mesg) {
98
const request = mesg.data ?? ({} as any);
99
// logger.debug("got message", request);
100
if (request.name == "system.terminate") {
101
// TODO: should be part of handleApiRequest below, but done differently because
102
// one case halts this loop
103
const { service } = request.args[0] ?? {};
104
if (service == "open-files") {
105
terminateOpenFiles();
106
mesg.respond({ status: "terminated", service });
107
return;
108
} else if (service == "listings") {
109
closeListings();
110
mesg.respond({ status: "terminated", service });
111
return;
112
} else if (service == "files:read") {
113
await closeFilesRead();
114
mesg.respond({ status: "terminated", service });
115
return;
116
} else if (service == "files:write") {
117
await closeFilesWrite();
118
mesg.respond({ status: "terminated", service });
119
return;
120
} else if (service == "api") {
121
// special hook so admin can terminate handling. This is useful for development.
122
terminate = true;
123
console.warn("TERMINATING listening on ", subject);
124
logger.debug("TERMINATING listening on ", subject);
125
mesg.respond({ status: "terminated", service });
126
api.stop();
127
return;
128
} else {
129
mesg.respond({ error: `Unknown service ${service}` });
130
}
131
} else {
132
handleApiRequest(request, mesg);
133
}
134
}
135
136
async function handleApiRequest(request, mesg) {
137
let resp;
138
const { name, args } = request as any;
139
if (name == "ping") {
140
resp = "pong";
141
} else {
142
try {
143
// logger.debug("handling project.api request:", { name });
144
resp = (await getResponse({ name, args })) ?? null;
145
} catch (err) {
146
logger.debug(`project.api request err = ${err}`, { name });
147
resp = { error: `${err}` };
148
}
149
}
150
mesg.respond(resp);
151
}
152
153
import * as system from "./system";
154
import * as editor from "./editor";
155
import * as sync from "./sync";
156
157
export const projectApi: ProjectApi = {
158
system,
159
editor,
160
sync,
161
};
162
163
async function getResponse({ name, args }) {
164
const [group, functionName] = name.split(".");
165
const f = projectApi[group]?.[functionName];
166
if (f == null) {
167
throw Error(`unknown function '${name}'`);
168
}
169
return await f(...args);
170
}
171
172