Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/util/message.js
1447 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
// Library for working with JSON messages for Salvus.
7
//
8
// We use functions to work with messages to ensure some level of
9
// consistency, defaults, and avoid errors from typos, etc.
10
11
const doc_intro = `\
12
## Purpose
13
14
The purpose of the CoCalc API (application programming interface) is to make
15
essential operations within the CoCalc platform available to automated
16
clients. This allows embedding of CoCalc services within other products
17
and customizing the external look and feel of the application.
18
19
## Protocol and Data Format
20
21
Each API command is invoked using an HTTPS POST request.
22
All commands support request parameters in JSON format, with request header
23
\`Content-Type: application/json\`. Many commands (those that do not
24
require lists or objects as parameters)
25
also accept request parameters as key-value pairs, i.e.
26
\`Content-Type: application/x-www-form-urlencoded\`.
27
28
Responses are formatted as JSON strings.
29
Note that it is possible for a request to fail and return
30
a response code of 200. In that case, the response
31
string may contain helpful information on the nature of
32
the failure. In other cases, if the request cannnot
33
be completed, a response code other than 200 may be
34
returned, and the response body may be a
35
generic HTML message rather than a JSON string.
36
37
## Authentication
38
39
A valid API key is required on all API requests.
40
41
To obtain a key manually, log into
42
CoCalc and click on Settings (gear icon next to user name at upper
43
right), and look under \`Account Settings\`.
44
With the \`API key\` dialogue, you can create a key,
45
view a previously assigned key, generate a replacement key,
46
and delete your key entirely.
47
48
.. index:: API; get_api_key
49
50
It is also possible to obtain an API key using a javascript-enabled automated web client.
51
This option is useful for applications that embed CoCalc
52
in a custom environment, for example [juno.sh](https://juno.sh),
53
the iOS application for Jupyter notebooks.
54
Visiting the link :samp:\`https://cocalc.com/app?get_api_key=myapp\`,
55
where "myapp" is an identifier for your application,
56
returns a modified sign-in page with the banner
57
"CoCalc API Key Access for Myapp".
58
The web client must
59
sign in with credentials for the account in question.
60
Response headers from a successful sign-in will include a url of the form
61
:samp:\`https://authenticated/?api_key=sk_abcdefQWERTY090900000000\`.
62
The client should intercept this response and capture the string
63
after the equals sign as the API key.
64
65
Your API key carries access privileges, just like your login and password.
66
__Keep it secret.__
67
Do not share your API key with others or post it in publicly accessible forums.
68
69
## Additional References
70
71
- The [CoCalc API tutorial](https://cocalc.com/share/65f06a34-6690-407d-b95c-f51bbd5ee810/Public/README.md?viewer=share) illustrates API calls in Python.
72
- The CoCalc PostgreSQL schema definition [src/packages/util/db-schema](https://github.com/sagemathinc/cocalc/blob/master/src/packages/util/db-schema) has information on tables and fields used with the API \`query\` request.
73
- The API test suite [src/packages/hub/test/api/](https://github.com/sagemathinc/cocalc/tree/master/src/packages/hub/test/api) contains mocha unit tests for the API messages.
74
- The CoCalc message definition file [src/packages/util/message.js](https://github.com/sagemathinc/cocalc/blob/master/src/packages/util/message.js) contains the source for this guide.
75
76
## API Message Reference
77
78
The remainder of this guide explains the individual API endpoints.
79
Each API request definition begins with the path of the
80
URL used to invoke the request,
81
for example \`/api/v1/change_email_address\`.
82
The path name ends with the name of the request,
83
for example, \`change_email_address\`.
84
Following the path is the list of options.
85
After options are one or more sample invocations
86
illustrating format of the request as made with the \`curl\`
87
command, and the format of the response.
88
89
The following two options appear on all API messages
90
(request parameters are often referred to
91
as 'options' in the guide):
92
93
- **event**: the command to be executed, for example "ping"
94
- **id**: uuid for the API call, returned in response in most cases.
95
If id is not provided in the API message, a random id will be
96
generated and returned in the response.\
97
`;
98
99
const misc = require("./misc");
100
const { defaults } = misc;
101
const { required } = defaults;
102
const _ = require("underscore");
103
104
function message(obj) {
105
exports[obj.event] = function (opts, strict) {
106
if (opts == null) {
107
opts = {};
108
}
109
if (strict == null) {
110
strict = false;
111
}
112
if (opts.event != null) {
113
throw Error(
114
`ValueError: must not define 'event' when calling message creation function (opts=${JSON.stringify(
115
opts,
116
)}, obj=${JSON.stringify(obj)})`,
117
);
118
}
119
return defaults(opts, obj, false, strict);
120
};
121
return obj;
122
}
123
124
// message2 for "version 2" of the message definitions
125
// TODO document it, for now just search for "message2" to see examples
126
function message2(obj) {
127
function mk_desc(val) {
128
let { desc } = val;
129
if (val.init === required) {
130
desc += " (required)";
131
} else if (val.init != null) {
132
desc += ` (default: ${misc.to_json(val.init)})`;
133
}
134
return desc;
135
}
136
137
// reassembling a version 1 message from a version 2 message
138
const mesg_v1 = _.mapObject(obj.fields, (val) => val.init);
139
mesg_v1.event = obj.event;
140
// extracting description for the documentation
141
const fdesc = _.mapObject(obj.fields, mk_desc);
142
exports.documentation.events[obj.event] = {
143
description: obj.desc != null ? obj.desc : "",
144
fields: fdesc,
145
};
146
// ... and the examples
147
exports.examples[obj.event] = obj.examples;
148
// wrapped version 1 message
149
message(mesg_v1);
150
return obj;
151
}
152
153
// messages that can be used by the HTTP api. {'event':true, ...}
154
exports.api_messages = {};
155
156
// this holds the documentation for the message protocol
157
exports.documentation = {
158
intro: doc_intro,
159
events: {},
160
};
161
162
// holds all the examples: list of expected in/out objects for each message
163
exports.examples = {};
164
165
const API = (obj) =>
166
// obj could be message version 1 or 2!
167
(exports.api_messages[obj.event] = true);
168
169
//###########################################
170
// Sage session management; executing code
171
//############################################
172
173
// hub --> sage_server&console_server, etc. and browser --> hub
174
message({
175
event: "start_session",
176
type: required, // "sage", "console"; later this could be "R", "octave", etc.
177
// TODO: project_id should be required
178
project_id: undefined, // the project that this session will start in
179
session_uuid: undefined, // set by the hub -- client setting this will be ignored.
180
params: undefined, // extra parameters that control the type of session
181
id: undefined,
182
limits: undefined,
183
});
184
185
// hub --> browser
186
message({
187
event: "session_started",
188
id: undefined,
189
session_uuid: undefined,
190
limits: undefined,
191
data_channel: undefined,
192
}); // The data_channel is a single UTF-16
193
// character; this is used for
194
// efficiently sending and receiving
195
// non-JSON data (except channel
196
// '\u0000', which is JSON).
197
198
// Output resulting from evaluating code that is displayed by the browser.
199
// sage_server --> local hub --> hubs --> clients
200
message({
201
event: "output",
202
id: undefined, // the id for this particular computation
203
stdout: undefined, // plain text stream
204
stderr: undefined, // error text stream -- colored to indicate an error
205
html: undefined, // arbitrary html stream
206
md: undefined, // github flavored markdown
207
tex: undefined, // tex/latex stream -- is an object {tex:..., display:...}
208
d3: undefined, // d3 data document, e.g,. {d3:{viewer:'graph', data:{...}}}
209
hide: undefined, // 'input' or 'output'; hide display of given component of cell
210
show: undefined, // 'input' or 'output'; show display of given component of cell
211
auto: undefined, // true or false; sets whether or not cell auto-executess on process restart
212
javascript: undefined, // javascript code evaluation stream (see also 'execute_javascript' to run code directly in browser that is not part of the output stream).
213
interact: undefined, // create an interact layout defined by a JSON object
214
obj: undefined, // used for passing any JSON-able object along as output; this is used, e.g., by interact.
215
file: undefined, // used for passing a file -- is an object {filename:..., uuid:..., show:true}; the file is at https://cloud.sagemath.com/blobs/filename?uuid=[the uuid]
216
raw_input: undefined, // used for getting blocking input from client -- {raw_input:{prompt:'input stuff?', value:'', submitted:false}}
217
done: false, // the sequences of messages for a given code evaluation is done.
218
session_uuid: undefined, // the uuid of the session that produced this output
219
once: undefined, // if given, message is transient; it is not saved by the worksheet, etc.
220
clear: undefined, // if true, clears all output of the current cell before rendering message.
221
events: undefined,
222
}); // {'event_name':'name of Python callable to call', ...} -- only for images right now
223
224
// This message tells the client to execute the given Javascript code
225
// in the browser. (For safety, the client may choose to ignore this
226
// message.) If coffeescript==true, then the code is assumed to be
227
// coffeescript and is first compiled to Javascript. This message is
228
// "out of band", i.e., not meant to be part of any particular output
229
// cell. That is why there is no id key.
230
231
// sage_server --> hub --> client
232
message({
233
event: "execute_javascript",
234
session_uuid: undefined, // set by the hub, since sage_server doesn't (need to) know the session_uuid.
235
code: required,
236
obj: undefined,
237
coffeescript: false,
238
cell_id: undefined,
239
}); // if set, eval scope contains an object cell that refers to the cell in the worksheet with this id.
240
241
//###########################################
242
// Account Management
243
//############################################
244
245
// client --> hub
246
API(
247
message2({
248
event: "create_account",
249
fields: {
250
id: {
251
init: undefined,
252
desc: "A unique UUID for the query",
253
},
254
255
first_name: {
256
init: undefined,
257
},
258
last_name: {
259
init: undefined,
260
},
261
email_address: {
262
init: undefined,
263
},
264
password: {
265
init: undefined,
266
desc: "if given, must be between 6 and 64 characters in length",
267
},
268
agreed_to_terms: {
269
init: undefined,
270
desc: "must be true or user will get nagged",
271
},
272
token: {
273
init: undefined, // only required when token is set.
274
desc: "account creation token - see src/dev/docker/README.md",
275
},
276
get_api_key: {
277
init: undefined,
278
desc: "if set to anything truth-ish, will create (if needed) and return api key with signed_in message",
279
},
280
usage_intent: {
281
init: undefined,
282
desc: "response to Cocalc usage intent at sign up",
283
},
284
},
285
desc: `\
286
Examples:
287
288
Create a new account:
289
\`\`\`
290
curl -u sk_abcdefQWERTY090900000000: \\
291
-d first_name=John00 \\
292
-d last_name=Doe00 \\
293
-d [email protected] \\
294
-d password=xyzabc09090 \\
295
-d agreed_to_terms=true https://cocalc.com/api/v1/create_account
296
\`\`\`
297
298
Option \`agreed_to_terms\` must be present and specified as true.
299
Account creation fails if there is already an account using the
300
given email address, if \`email_address\` is improperly formatted,
301
and if password is fewer than 6 or more than 64 characters.
302
303
Attempting to create the same account a second time results in an error:
304
\`\`\`
305
curl -u sk_abcdefQWERTY090900000000: \\
306
-d first_name=John00 \\
307
-d last_name=Doe00 \\
308
-d [email protected] \\
309
-d password=xyzabc09090 \\
310
-d agreed_to_terms=true https://cocalc.com/api/v1/create_account
311
==> {"event":"account_creation_failed",
312
"id":"2332be03-aa7d-49a6-933a-cd9824b7331a",
313
"reason":{"email_address":"This e-mail address is already taken."}}
314
\`\`\`\
315
`,
316
}),
317
);
318
319
message({
320
event: "account_created",
321
id: undefined,
322
account_id: required,
323
});
324
325
// hub --> client
326
message({
327
event: "account_creation_failed",
328
id: undefined,
329
reason: required,
330
});
331
332
// client --> hub
333
message2({
334
event: "delete_account",
335
fields: {
336
id: {
337
init: undefined,
338
desc: "A unique UUID for the query",
339
},
340
account_id: {
341
init: required,
342
desc: "account_id for account to be deleted",
343
},
344
},
345
desc: `\
346
Example:
347
348
Delete an existing account:
349
\`\`\`
350
curl -u sk_abcdefQWERTY090900000000: \\
351
-d account_id=99ebde5c-58f8-4e29-b6e4-b55b8fd71a1b \\
352
https://cocalc.com/api/v1/delete_account
353
==> {"event":"account_deleted","id":"9e8b68ac-08e8-432a-a853-398042fae8c9"}
354
\`\`\`
355
356
Event \`account_deleted\` is also returned if the account was already
357
deleted before the API call, or if the account never existed.
358
359
After successful \`delete_account\`, the owner of the deleted account
360
will not be able to login, but will still be listed as collaborator
361
or owner on projects which the user collaborated on or owned
362
respectively.\
363
`,
364
});
365
366
// hub --> client
367
message({
368
event: "account_deleted",
369
id: undefined,
370
error: undefined,
371
});
372
373
// client --> hub
374
message({
375
id: undefined,
376
event: "sign_in",
377
email_address: required,
378
password: required,
379
remember_me: false,
380
get_api_key: undefined,
381
}); // same as for create_account
382
383
// hub --> client
384
message({
385
id: undefined,
386
event: "remember_me_failed",
387
reason: required,
388
});
389
390
// client --> hub
391
message({
392
id: undefined,
393
event: "sign_in_failed",
394
email_address: required,
395
reason: required,
396
});
397
398
// hub --> client; sent in response to either create_account or log_in
399
message({
400
event: "signed_in",
401
id: undefined, // message uuid
402
remember_me: required, // true if sign in accomplished via remember_me cookie; otherwise, false.
403
hub: required, // ip address (on vpn) of hub user connected to.
404
account_id: required, // uuid of user's account
405
email_address: undefined, // email address they signed in under
406
// Alternatively, if email_address isn't set, there might be an lti_id.
407
// There might NOT be an lti_id either, if it is anonymous account!
408
lti_id: undefined,
409
first_name: undefined,
410
last_name: undefined,
411
api_key: undefined, // user's api key, if requested in sign_in or create_account messages.
412
});
413
414
// client --> hub
415
message({
416
event: "sign_out",
417
everywhere: false,
418
id: undefined,
419
});
420
421
// hub --> client
422
message({
423
event: "signed_out",
424
id: undefined,
425
});
426
427
message({
428
event: "error",
429
id: undefined,
430
error: undefined,
431
});
432
433
message({
434
event: "success",
435
id: undefined,
436
});
437
438
// You need to reconnect.
439
message({
440
event: "reconnect",
441
id: undefined,
442
reason: undefined,
443
}); // optional to make logs more informative
444
445
//#####################################################################################
446
// This is a message that goes
447
// hub --> client
448
// In response, the client grabs "/cookies?id=...,set=...,get=..." via an AJAX call.
449
// During that call the server can get/set HTTP-only cookies.
450
// (Note that the /cookies url gets customized by base_path.)
451
//#####################################################################################
452
message({
453
event: "cookies",
454
id: required,
455
url: "/cookies",
456
get: undefined, // name of a cookie to get
457
set: undefined, // name of a cookie to set
458
value: undefined,
459
}); // value to set cookie to
460
461
/*
462
463
Project Server <---> Hub interaction
464
465
These messages are mainly focused on working with individual projects.
466
467
Architecture:
468
469
* The database stores a files object (with the file tree), logs
470
(of each branch) and a sequence of git bundles that when
471
combined together give the complete history of the repository.
472
Total disk usage per project is limited by hard/soft disk quota,
473
and includes the space taken by the revision history (the .git
474
directory).
475
476
* A project should only be opened by at most one project_server at
477
any given time (not implemented: if this is violated then we'll
478
merge the resulting conflicting repo's.)
479
480
* Which project_server that has a project opened is stored in the
481
database. If a hub cannot connect to a given project server,
482
the hub assigns a new project_server for the project and opens
483
the project on the new project_server. (The error also gets
484
logged to the database.) All hubs will use this new project
485
server henceforth.
486
487
*/
488
489
// The open_project message causes the project_server to create a new
490
// project or prepare to receive one (as a sequence of blob messages)
491
// from a hub.
492
//
493
// hub --> project_server
494
message({
495
event: "open_project",
496
id: required,
497
project_id: required, // uuid of the project, which impacts
498
// where project is extracted, etc.
499
quota: required, // Maximum amount of disk space/inodes this
500
// project can use. This is an object
501
// {disk:{soft:megabytes, hard:megabytes}, inode:{soft:num, hard:num}}
502
idle_timeout: required, // A time in seconds; if the project_server
503
// does not receive any messages related
504
// to this project for this many seconds,
505
// then it does the same thing as when
506
// receiving a 'close_project' message.
507
ssh_public_key: required,
508
}); // ssh key of the one UNIX user that is allowed to access this account (this is running the hub).
509
510
// A project_server sends the project_opened message to the hub once
511
// the project_server has received and unbundled all bundles that
512
// define a project.
513
// project_server --> hub
514
message({
515
event: "project_opened",
516
id: required,
517
});
518
519
//#####################################################################
520
// Execute a shell command in a given project
521
//#####################################################################
522
523
// client --> project
524
API(
525
message2({
526
event: "project_exec",
527
fields: {
528
id: {
529
init: undefined,
530
desc: "A unique UUID for the query",
531
},
532
project_id: {
533
init: required,
534
desc: "id of project where command is to be executed",
535
},
536
path: {
537
init: "",
538
desc: "path of working directory for the command",
539
},
540
command: {
541
init: required,
542
desc: "command to be executed",
543
},
544
args: {
545
init: [],
546
desc: "command line options for the command",
547
},
548
timeout: {
549
init: 10,
550
desc: "maximum allowed time, in seconds",
551
},
552
aggregate: {
553
init: undefined,
554
desc: "If there are multiple attempts to run the given command with the same time, they are all aggregated and run only one time by the project; if requests comes in with a greater value (time, sequence number, etc.), they all run in another group after the first one finishes. Meant for compiling code on save.",
555
},
556
max_output: {
557
init: undefined,
558
desc: "maximum number of characters in the output",
559
},
560
bash: {
561
init: false,
562
desc: "if true, args are ignored and command is run as a bash command",
563
},
564
err_on_exit: {
565
init: true,
566
desc: "if exit code is nonzero send error return message instead of the usual output",
567
},
568
},
569
desc: `\
570
Execute a shell command in a given project.
571
572
Examples:
573
574
Simple built-in shell command.
575
\`\`\`
576
curl -u sk_abcdefQWERTY090900000000: \\
577
-d command=pwd \\
578
-d project_id=e49e86aa-192f-410b-8269-4b89fd934fba \\
579
https://cocalc.com/api/v1/project_exec
580
==> {"event":"project_exec_output",
581
"id":"8a78a37d-b2fb-4e29-94ae-d66acdeac949",
582
"stdout":"/projects/e49e86aa-192f-410b-8269-4b89fd934fba\\n","stderr":"","exit_code":0}
583
\`\`\`
584
585
Shell command with different working directory.
586
\`\`\`
587
curl -u sk_abcdefQWERTY090900000000: \\
588
-d command=pwd \\
589
-d path=Private \\
590
-d project_id=e49e86aa-192f-410b-8269-4b89fd934fba \\
591
https://cocalc.com/api/v1/project_exec
592
==> {"event":"project_exec_output",
593
"id":"8a78a37d-b2fb-4e29-94ae-d66acdeac949",
594
"stdout":"/projects/e49e86aa-192f-410b-8269-4b89fd934fba/Private\\n","stderr":"","exit_code":0}
595
\`\`\`
596
597
Command line arguments specified by 'args' option. Note JSON format for request parameters.
598
\`\`\`
599
curl -u sk_abcdefQWERTY090900000000: \\
600
-H 'Content-Type: application/json' \\
601
-d '{"command":"echo","args":["xyz","abc"],"project_id":"e49e86aa-192f-410b-8269-4b89fd934fba"}' \\
602
https://cocalc.com/api/v1/project_exec
603
==> {"event":"project_exec_output",
604
"id":"39289ba7-0333-48ad-984e-b25c8b8ffa0e",
605
"stdout":"xyz abc\\n",
606
"stderr":"",
607
"exit_code":0}
608
\`\`\`
609
610
Limiting output of the command to 3 characters.
611
\`\`\`
612
curl -u sk_abcdefQWERTY090900000000: \\
613
-H 'Content-Type: application/json' \\
614
-d '{"command":"echo","args":["xyz","abc"],"max_output":3,"project_id":"e49e86aa-192f-410b-8269-4b89fd934fba"}' \\
615
https://cocalc.com/api/v1/project_exec
616
==> {"event":"project_exec_output",
617
"id":"02feab6c-a743-411a-afca-8a23b58988a9",
618
"stdout":"xyz (truncated at 3 characters)",
619
"stderr":"",
620
"exit_code":0}
621
\`\`\`
622
623
Setting a timeout for the command.
624
\`\`\`
625
curl -u sk_abcdefQWERTY090900000000: \\
626
-H 'Content-Type: application/json' \\
627
-d '{"command":"sleep 5","timeout":2,"project_id":"e49e86aa-192f-410b-8269-4b89fd934fba"}' \\
628
https://cocalc.com/api/v1/project_exec
629
==> {"event":"error",
630
"id":"86fea3f0-6a90-495b-a541-9c14a25fbe58",
631
"error":"Error executing command 'sleep 5' with args '' -- killed command 'bash /tmp/f-11757-1677-8ei2z0.t4fex0qkt9', , "}
632
\`\`\`
633
634
Notes:
635
- Argument \`command\` may invoke an executable file or a built-in shell command. It may include
636
a path and command line arguments.
637
- If option \`args\` is provided, options must be sent as a JSON object.
638
- Argument \`path\` is optional. When provided, \`path\` is relative to home directory in target project
639
and specifies the working directory in which the command will be run.
640
- If the project is stopped or archived, this API call will cause it to be started. Starting the project can take
641
several seconds. In this case, the call may return a timeout error and will need to be repeated. \
642
`,
643
}),
644
);
645
646
// project --> client
647
message({
648
event: "project_exec_output",
649
id: required,
650
stdout: required,
651
stderr: required,
652
exit_code: required,
653
type: undefined,
654
job_id: undefined,
655
start: undefined,
656
status: undefined,
657
elapsed_s: undefined,
658
pid: undefined,
659
stats: undefined,
660
});
661
662
//#####################################################################
663
// Named Server
664
//#####################################################################
665
666
// starts a named server in a project, e.g, 'jupyterlab', and reports the
667
// port it is running at
668
// hub <--> project
669
message({
670
event: "named_server_port",
671
name: required, // 'jupyter', 'jupyterlab', 'code', 'pluto' or whatever project supports...
672
port: undefined, // gets set in the response
673
id: undefined,
674
});
675
676
//############################################################################
677
678
// The read_file_from_project message is sent by the hub to request
679
// that the project_server read a file from a project and send it back
680
// to the hub as a blob. Also sent by client to hub to request a file
681
// or directory. If path is a directory, the optional archive field
682
// specifies how to create a single file archive, with supported
683
// options including: 'tar', 'tar.bz2', 'tar.gz', 'zip', '7z'.
684
//
685
// client --> hub --> project_server
686
message({
687
event: "read_file_from_project",
688
id: undefined,
689
project_id: required,
690
path: required,
691
archive: "tar.bz2",
692
ttlSeconds: undefined, // if given, time to live in seconds for blob; default is "1 day".
693
});
694
695
// The file_read_from_project message is sent by the project_server
696
// when it finishes reading the file from disk.
697
// project_server --> hub
698
message({
699
event: "file_read_from_project",
700
id: required,
701
data_uuid: required, // The project_server will send the raw data of the file as a blob with this uuid.
702
archive: undefined, // if defined, means that file (or directory) was archived (tarred up) and this string was added to end of filename.
703
});
704
705
// The client sends this message to the hub in order to read
706
// a plain text file (binary files not allowed, since sending
707
// them via JSON makes no sense).
708
// client --> hub
709
API(
710
message2({
711
event: "read_text_file_from_project",
712
fields: {
713
id: {
714
init: undefined,
715
desc: "A unique UUID for the query",
716
},
717
project_id: {
718
init: required,
719
desc: "id of project containing file to be read (or array of project_id's)",
720
},
721
path: {
722
init: required,
723
desc: "path to file to be read in target project (or array of paths)",
724
},
725
},
726
desc: `\
727
Read a text file in the project whose \`project_id\` is supplied.
728
729
Argument \`'path'\` is relative to home directory in target project.
730
731
You can also read multiple \`project_id\`/\`path\`'s at once by
732
making \`project_id\` and \`path\` arrays (of the same length).
733
In that case, the result will be an array
734
of \`{project_id, path, content}\` objects, in some random order.
735
If there is an error reading a particular file,
736
instead \`{project_id, path, error}\` is included.
737
738
**Note:** You need to have read access to the project,
739
the Linux user \`user\` in the target project must have permissions to read the file
740
and containing directories.
741
742
Example:
743
744
Read a text file.
745
\`\`\`
746
curl -u sk_abcdefQWERTY090900000000: \\
747
-d project_id=e49e86aa-192f-410b-8269-4b89fd934fba \\
748
-d path=Assignments/A1/h1.txt \\
749
https://cocalc.com/api/v1/read_text_file_from_project
750
==> {"event":"text_file_read_from_project",
751
"id":"481d6055-5609-450f-a229-480e518b2f84",
752
"content":"hello"}
753
\`\`\`\
754
`,
755
}),
756
);
757
758
// hub --> client
759
message({
760
event: "text_file_read_from_project",
761
id: required,
762
content: required,
763
});
764
765
// The write_file_to_project message is sent from the hub to the
766
// project_server to tell the project_server to write a file to a
767
// project. If the path includes directories that don't exists,
768
// they are automatically created (this is in fact the only way
769
// to make a new directory except of course project_exec).
770
// hub --> project_server
771
message({
772
event: "write_file_to_project",
773
id: required,
774
project_id: required,
775
path: required,
776
data_uuid: required,
777
}); // hub sends raw data as a blob with this uuid immediately.
778
779
// The client sends this message to the hub in order to write (or
780
// create) a plain text file (binary files not allowed, since sending
781
// them via JSON makes no sense).
782
// client --> hub
783
API(
784
message2({
785
event: "write_text_file_to_project",
786
fields: {
787
id: {
788
init: undefined,
789
desc: "A unique UUID for the query",
790
},
791
project_id: {
792
init: required,
793
desc: "id of project where file is created",
794
},
795
path: {
796
init: required,
797
desc: "path to file, relative to home directory in destination project",
798
},
799
content: {
800
init: required,
801
desc: "contents of the text file to be written",
802
},
803
},
804
desc: `\
805
Create a text file in the target project with the given \`project_id\`.
806
Directories containing the file are created if they do not exist already.
807
If a file already exists at the destination path, it is overwritten.
808
809
**Note:** You need to have read access to the project.
810
The Linux user \`user\` in the target project must have permissions to create files
811
and containing directories if they do not already exist.
812
813
Example:
814
815
Create a text file.
816
\`\`\`
817
curl -u sk_abcdefQWERTY090900000000: \\
818
-d project_id=e49e86aa-192f-410b-8269-4b89fd934fba \\
819
-d "content=hello$'\\n'world" \\
820
-d path=Assignments/A1/h1.txt \\
821
https://cocalc.com/api/v1/write_text_file_to_project
822
\`\`\`\
823
`,
824
}),
825
);
826
827
// The file_written_to_project message is sent by a project_server to
828
// confirm successful write of the file to the project.
829
// project_server --> hub
830
message({
831
event: "file_written_to_project",
832
id: required,
833
});
834
835
//###########################################
836
// Managing multiple projects
837
//###########################################
838
839
// hub --> client
840
message({
841
event: "user_search_results",
842
id: undefined,
843
results: required,
844
}); // list of {first_name:, last_name:, account_id:, last_active:?, created:?, email_address:?} objects.; email_address only for admin
845
846
// hub --> client
847
message({
848
event: "project_users",
849
id: undefined,
850
users: required,
851
}); // list of {account_id:?, first_name:?, last_name:?, mode:?, state:?}
852
853
/*
854
Send/receive the current webapp code version number.
855
856
This can be used by clients to suggest a refresh/restart.
857
The client may sends their version number on connect.
858
If the client sends their version and later it is out of date
859
due to an update, the server sends a new version number update
860
message to that client.
861
*/
862
// client <---> hub
863
message({
864
event: "version",
865
version: undefined, // gets filled in by the hub
866
min_version: undefined,
867
}); // if given, then client version must be at least min_version to be allowed to connect.
868
869
//############################################
870
//
871
// Message sent in response to attempt to save a blob
872
// to the database.
873
//
874
// hub --> local_hub [--> sage_server]
875
//
876
//############################################
877
message({
878
event: "save_blob",
879
id: undefined,
880
sha1: required, // the sha-1 hash of the blob that we just processed
881
ttl: undefined, // ttl in seconds of the blob if saved; 0=infinite
882
error: undefined,
883
}); // if not saving, a message explaining why.
884
885
message({
886
event: "projects_running_on_server",
887
id: undefined,
888
projects: undefined,
889
}); // for response
890
891
/*
892
Direct messaging between browser client and local_hub,
893
forwarded on by global hub after ensuring write access.
894
*/
895
message({
896
event: "local_hub",
897
project_id: required,
898
timeout: undefined,
899
id: undefined,
900
multi_response: false,
901
message: required,
902
}); // arbitrary message
903
904
//##########################################################
905
//
906
// Copy a path from one project to another.
907
//
908
//##########################################################
909
API(
910
message2({
911
event: "copy_path_between_projects",
912
fields: {
913
id: {
914
init: undefined,
915
desc: "A unique UUID for the query",
916
},
917
src_project_id: {
918
init: required,
919
desc: "id of source project",
920
},
921
src_path: {
922
init: required,
923
desc: "relative path of directory or file in the source project",
924
},
925
target_project_id: {
926
init: required,
927
desc: "id of target project",
928
},
929
target_path: {
930
init: undefined,
931
desc: "defaults to src_path",
932
},
933
overwrite_newer: {
934
init: false,
935
desc: "overwrite newer versions of file at destination (destructive)",
936
},
937
delete_missing: {
938
init: false,
939
desc: "delete files in dest that are missing from source (destructive)",
940
},
941
backup: {
942
init: false,
943
desc: "make ~ backup files instead of overwriting changed files",
944
},
945
timeout: {
946
init: undefined,
947
desc: 'seconds to wait before reporting "error" (though copy could still succeed)',
948
},
949
wait_until_done: {
950
init: false,
951
desc: "if false, the operation returns immediately with the copy_path_id for querying copy_path_status. (Only implemented for https://cocalc.com.)",
952
},
953
scheduled: {
954
init: undefined,
955
desc: "if set, the copy operation runs earliest after the given time and wait_until_done is automatically set to false. Must be a `new Date(...)` parseable string. (Only implemented for https://cocalc.com.)",
956
},
957
exclude: {
958
init: undefined,
959
desc: "array of rsync patterns to exclude; each item in this string[] array is passed as a --exclude option to rsync",
960
},
961
},
962
desc: `\
963
Copy a file or directory from one project to another.
964
965
**Note:** the \`timeout\` option is passed to a call to the \`rsync\` command.
966
If no data is transferred for the specified number of seconds, then
967
the copy terminates. The default is 0, which means no timeout.
968
969
Relative paths (paths not beginning with '/') are relative to the user's
970
home directory in source and target projects.
971
972
**Note:** You need to have read/write access to the associated src/target project.
973
974
Further options:
975
976
- \`wait_until_done\`: set this to false to immediately retrieve the \`copy_path_id\`.
977
This is the **recommended way** to use this endpoint,
978
because a blocking request might time out and you'll never learn about outcome of the copy operation.
979
Learn about the status (success or failure, including an error message) via the :doc:\`copy_path_status\` endpoint.
980
- \`scheduled\`: set this to a date in the future or postpone the copy operation.
981
Suitable timestamps can be created as follows:
982
- Bash: 1 minute in the future \`date -d '+1 minute' --utc +'%Y-%m-%dT%H:%M:%S'\`
983
- Python using [arrow](https://arrow.readthedocs.io/en/latest/) library:
984
- 1 minute in the future: \`arrow.now('UTC').shift(minutes=+1).for_json()\`
985
- At a specific time: \`arrow.get("2019-08-29 22:00").for_json()\`
986
Later, learn about its outcome via :doc:\`copy_path_status\` as well.
987
988
Example:
989
990
Copy file \`A/doc.txt\` from source project to target project.
991
Folder \`A\` will be created in target project if it does not exist already.
992
993
\`\`\`
994
curl -u sk_abcdefQWERTY090900000000: \\
995
-d src_project_id=e49e86aa-192f-410b-8269-4b89fd934fba \\
996
-d src_path=A/doc.txt \\
997
-d target_project_id=2aae4347-214d-4fd1-809c-b327150442d8 \\
998
https://cocalc.com/api/v1/copy_path_between_projects
999
==> {"event":"success",
1000
"id":"45d851ac-5ea0-4aea-9997-99a06c054a60"}
1001
\`\`\`\
1002
`,
1003
}),
1004
);
1005
1006
message({
1007
event: "copy_path_between_projects_response",
1008
id: required,
1009
copy_path_id: undefined,
1010
note: "Query copy_path_status with the copy_path_id to learn if the copy operation was successful.",
1011
});
1012
1013
API(
1014
message2({
1015
event: "copy_path_status",
1016
fields: {
1017
copy_path_id: {
1018
init: undefined,
1019
desc: "A unique UUID for a copy path operation",
1020
},
1021
src_project_id: {
1022
init: undefined,
1023
desc: "Source of copy operation to filter on",
1024
},
1025
target_project_id: {
1026
init: undefined,
1027
desc: "Target of copy operation to filter on",
1028
},
1029
src_path: {
1030
init: undefined,
1031
desc: "(src/targ only) Source path of copy operation to filter on",
1032
},
1033
limit: {
1034
init: 1000,
1035
desc: "(src/targ only) maximum number of results (max 1000)",
1036
},
1037
offset: {
1038
init: undefined,
1039
desc: "(src/targ only) default 0; set this to a multiple of the limit",
1040
},
1041
pending: {
1042
init: true,
1043
desc: "(src/targ only) true returns copy ops, which did not finish yet (default: true)",
1044
},
1045
failed: {
1046
init: false,
1047
desc: "(src/targ only) if true, only show finished and failed copy ops (default: false)",
1048
},
1049
},
1050
desc: `\
1051
Retrieve status information about copy path operation(s).
1052
1053
There are two ways to query:
1054
1055
- **single result** for a specific \`copy_path_id\`,
1056
which was returned by \`copy_path_between_projects\` earlier;
1057
- **array of results**, for at last one of \`src_project_id\` or \`target_project_id\`,
1058
and additionally filtered by an optionally given \`src_path\`.
1059
1060
Check for the field \`"finished"\`, containing the timestamp when the operation completed.
1061
There might also be an \`"error"\`!
1062
1063
**Note:** You need to have read/write access to the associated src/target project.
1064
`,
1065
}),
1066
);
1067
1068
message({
1069
event: "copy_path_status_response",
1070
id: required,
1071
data: required,
1072
});
1073
1074
API(
1075
message2({
1076
event: "copy_path_delete",
1077
fields: {
1078
copy_path_id: {
1079
init: undefined,
1080
desc: "A unique UUID for a scheduled future copy path operation",
1081
},
1082
},
1083
desc: `\
1084
Delete a copy_path operation with the given \`copy_path_id\`.
1085
You need to have read/write access to the associated src/target project.
1086
1087
**Note:** This will only remove entries which are *scheduled* and not yet completed.
1088
`,
1089
}),
1090
);
1091
1092
//############################################
1093
// Admin Functionality
1094
//############################################
1095
1096
/*
1097
Printing Files
1098
*/
1099
message({
1100
event: "print_to_pdf",
1101
id: undefined,
1102
path: required,
1103
options: undefined,
1104
});
1105
1106
message({
1107
event: "printed_to_pdf",
1108
id: undefined,
1109
path: required,
1110
});
1111
1112
/*
1113
Heartbeat message for connection from hub to project.
1114
*/
1115
message({
1116
event: "heartbeat",
1117
});
1118
1119
/*
1120
Ping/pong -- used for clock sync, etc.
1121
*/
1122
API(
1123
message2({
1124
event: "ping",
1125
fields: {
1126
id: {
1127
init: undefined,
1128
desc: "A unique UUID for the query",
1129
},
1130
},
1131
desc: `\
1132
Test API connection, return time as ISO string when server responds to ping.
1133
1134
Security key may be blank.
1135
1136
Examples:
1137
1138
Omitting request id:
1139
\`\`\`
1140
curl -X POST -u sk_abcdefQWERTY090900000000: https://cocalc.com/api/v1/ping
1141
==> {"event":"pong","id":"c74afb40-d89b-430f-836a-1d889484c794","now":"2017-05-24T13:29:11.742Z"}
1142
\`\`\`
1143
1144
Omitting request id and using blank security key:
1145
\`\`\`
1146
curl -X POST -u : https://cocalc.com/api/v1/ping
1147
==> {"event":"pong","id":"d90f529b-e026-4a60-8131-6ce8b6d4adc8","now":"2017-11-05T21:10:46.585Z"}
1148
\`\`\`
1149
1150
Using \`uuid\` shell command to create a request id:
1151
\`\`\`
1152
uuid
1153
==> 553f2815-1508-416d-8e69-2dde5af3aed8
1154
curl -u sk_abcdefQWERTY090900000000: https://cocalc.com/api/v1/ping -d id=553f2815-1508-416d-8e69-2dde5af3aed8
1155
==> {"event":"pong","id":"553f2815-1508-416d-8e69-2dde5af3aed8","now":"2017-05-24T13:47:21.312Z"}
1156
\`\`\`
1157
1158
Using JSON format to provide request id:
1159
\`\`\`
1160
curl -u sk_abcdefQWERTY090900000000: -H "Content-Type: application/json" \\
1161
-d '{"id":"8ec4ac73-2595-42d2-ad47-0b9641043b46"}' https://cocalc.com/api/v1/ping
1162
==> {"event":"pong","id":"8ec4ac73-2595-42d2-ad47-0b9641043b46","now":"2017-05-24T17:15:59.288Z"}
1163
\`\`\`\
1164
`,
1165
}),
1166
);
1167
1168
message({
1169
event: "pong",
1170
id: undefined,
1171
now: undefined,
1172
}); // timestamp
1173
1174
API(
1175
message2({
1176
event: "log_client_error",
1177
fields: {
1178
id: {
1179
init: undefined,
1180
desc: "A unique UUID for the query",
1181
},
1182
error: {
1183
init: required,
1184
desc: "error string",
1185
},
1186
},
1187
desc: `\
1188
Log an error so that CoCalc support can look at it.
1189
1190
In the following example, an explicit message id
1191
is provided for future reference.
1192
\`\`\`
1193
curl -u sk_abcdefQWERTY090900000000: \\
1194
-d id=34a424dc-1731-4b31-ba3d-fc8a484980d9 \\
1195
-d "error=cannot load library xyz" \\
1196
https://cocalc.com/api/v1/log_client_error
1197
==> {"event":"success",
1198
"id":"34a424dc-1731-4b31-ba3d-fc8a484980d9"}
1199
\`\`\`
1200
1201
Note: the above API call will create the following record in the
1202
\`client_error_log\` database table. This table is not readable
1203
via the API and is intended for use by CoCalc support only:
1204
\`\`\`
1205
[{"id":"34a424dc-1731-4b31-ba3d-fc8a484980d9",
1206
"event":"error",
1207
"error":"cannot load library xyz",
1208
"account_id":"1c87a139-9e13-4cdd-b02c-e7d41dcfe921",
1209
"time":"2017-07-06T02:32:41.176Z"}]
1210
\`\`\`\
1211
`,
1212
}),
1213
);
1214
1215
message({
1216
event: "webapp_error",
1217
id: undefined, // ignored
1218
name: required, // string
1219
message: required, // string
1220
comment: undefined, // string
1221
stacktrace: undefined, // string
1222
file: undefined, // string
1223
path: undefined, // string
1224
lineNumber: undefined, // int
1225
columnNumber: undefined, // int
1226
severity: undefined, // string
1227
browser: undefined, // string, how feature.js detected the browser
1228
mobile: undefined, // boolean, feature.js::IS_MOBILE
1229
responsive: undefined, // boolean, feature.js::is_responsive_mode
1230
user_agent: undefined, // string
1231
smc_version: undefined, // string
1232
build_date: undefined, // string
1233
smc_git_rev: undefined, // string
1234
uptime: undefined, // string
1235
start_time: undefined,
1236
}); // timestamp
1237
1238
/*
1239
Stripe integration
1240
*/
1241
1242
// Set the stripe payment method for this user.
1243
1244
// customer info
1245
API(
1246
message({
1247
event: "stripe_get_customer",
1248
id: undefined,
1249
}),
1250
);
1251
1252
API(
1253
message({
1254
event: "stripe_customer",
1255
id: undefined,
1256
customer: undefined, // if user already has a stripe customer account, info about it.
1257
stripe_publishable_key: undefined,
1258
}),
1259
); // if stripe is configured for this SMC instance, this is the public API key.
1260
1261
// card
1262
API(
1263
message({
1264
event: "stripe_create_source",
1265
id: undefined,
1266
token: required,
1267
}),
1268
);
1269
1270
API(
1271
message({
1272
event: "stripe_delete_source",
1273
card_id: required,
1274
id: undefined,
1275
}),
1276
);
1277
1278
API(
1279
message({
1280
event: "stripe_set_default_source",
1281
card_id: required,
1282
id: undefined,
1283
}),
1284
);
1285
1286
API(
1287
message({
1288
event: "stripe_update_source",
1289
card_id: required,
1290
info: required, // see https://stripe.com/docs/api/node#update_card, except we don't allow changing metadata
1291
id: undefined,
1292
}),
1293
);
1294
1295
// subscriptions to plans
1296
1297
API(
1298
message({
1299
event: "stripe_plans",
1300
id: undefined,
1301
plans: required,
1302
}),
1303
); // [{name:'Basic', projects:1, description:'...', price:'$10/month', trial_period:'30 days', ...}, ...]
1304
1305
// Create a subscription to a plan
1306
API(
1307
message({
1308
event: "stripe_create_subscription",
1309
id: undefined,
1310
plan: required, // name of plan
1311
quantity: 1,
1312
coupon_id: undefined,
1313
}),
1314
);
1315
1316
// Delete a subscription to a plan
1317
API(
1318
message({
1319
event: "stripe_cancel_subscription",
1320
id: undefined,
1321
subscription_id: required,
1322
at_period_end: true,
1323
}),
1324
);
1325
1326
// Modify a subscription to a plan, e.g., change which projects plan applies to.
1327
API(
1328
message({
1329
event: "stripe_update_subscription",
1330
id: undefined,
1331
subscription_id: required,
1332
quantity: undefined, // only give if changing
1333
projects: undefined, // change associated projects from what they were to new list
1334
plan: undefined, // change plan to this
1335
coupon_id: undefined,
1336
}),
1337
); // apply a coupon to this subscription
1338
1339
API(
1340
message({
1341
event: "stripe_get_subscriptions",
1342
id: undefined,
1343
limit: undefined, // between 1 and 100 (default: 10)
1344
ending_before: undefined, // see https://stripe.com/docs/api/node#list_charges
1345
starting_after: undefined,
1346
}),
1347
);
1348
1349
message({
1350
event: "stripe_subscriptions",
1351
id: undefined,
1352
subscriptions: undefined,
1353
});
1354
1355
API(
1356
message({
1357
event: "stripe_get_coupon",
1358
id: undefined,
1359
coupon_id: required,
1360
}),
1361
);
1362
1363
message({
1364
event: "stripe_coupon",
1365
id: undefined,
1366
coupon: undefined,
1367
});
1368
1369
// charges
1370
API(
1371
message({
1372
event: "stripe_get_charges",
1373
id: undefined,
1374
limit: undefined, // between 1 and 100 (default: 10)
1375
ending_before: undefined, // see https://stripe.com/docs/api/node#list_charges
1376
starting_after: undefined,
1377
}),
1378
);
1379
1380
message({
1381
event: "stripe_charges",
1382
id: undefined,
1383
charges: undefined,
1384
});
1385
1386
// invoices
1387
API(
1388
message({
1389
event: "stripe_get_invoices",
1390
id: undefined,
1391
limit: undefined, // between 1 and 100 (default: 10)
1392
ending_before: undefined, // see https://stripe.com/docs/api/node#list_customer_invoices
1393
starting_after: undefined,
1394
}),
1395
);
1396
1397
message({
1398
event: "stripe_invoices",
1399
id: undefined,
1400
invoices: undefined,
1401
});
1402
1403
message({
1404
event: "stripe_admin_create_invoice_item",
1405
id: undefined,
1406
email_address: undefined, // one of email or account_id must be given.
1407
account_id: undefined, // user who will be invoiced
1408
amount: undefined, // currently in US dollars (if amount or desc not given, then only creates customer, not invoice)
1409
description: undefined,
1410
});
1411
1412
/*
1413
Queries directly to the database (sort of like Facebook's GraphQL)
1414
*/
1415
1416
API(
1417
message2({
1418
event: "query",
1419
fields: {
1420
id: {
1421
init: undefined,
1422
desc: "A unique UUID for the query",
1423
},
1424
query: {
1425
init: required,
1426
desc: "The actual query",
1427
},
1428
changes: {
1429
init: undefined,
1430
desc: "",
1431
},
1432
multi_response: {
1433
init: false,
1434
desc: "",
1435
},
1436
options: {
1437
init: undefined,
1438
desc: "",
1439
},
1440
},
1441
desc: `\
1442
This queries directly the database (sort of Facebook's GraphQL)
1443
Options for the 'query' API message must be sent as JSON object.
1444
A query is either _get_ (read from database), or _set_ (write to database).
1445
A query is _get_ if any query keys are null, otherwise the query is _set_.
1446
1447
Note: queries with \`multi_response\` set to \`true\` are not supported.
1448
1449
#### Examples of _get_ query:
1450
1451
Get title and description for a project, given the project id.
1452
\`\`\`
1453
curl -u sk_abcdefQWERTY090900000000: -H "Content-Type: application/json" \\
1454
-d '{"query":{"projects":{"project_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d","title":null,"description":null}}}' \\
1455
https://cocalc.com/api/v1/query
1456
==> {"event":"query",
1457
"id":"8ec4ac73-2595-42d2-ad47-0b9641043b46",
1458
"query":{"projects":{"project_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d",
1459
"title":"MY NEW PROJECT 2",
1460
"description":"desc 2"}},
1461
"multi_response":false}
1462
\`\`\`
1463
1464
Get info on all projects for the account whose security key is provided.
1465
The information returned may be any of the api-accessible fields in the
1466
\`projects\` table. These fields are listed in CoCalc source directory
1467
src/packages/util/db-schema, under \`schema.projects.user_query\`.
1468
In this example, project name and description are returned.
1469
1470
Note: to get info only on projects active in the past 3 weeks, use
1471
\`projects\` instead of \`projects_all\` in the query.
1472
1473
\`\`\`
1474
curl -u sk_abcdefQWERTY090900000000: -H "Content-Type: application/json" \\
1475
-d '{"query":{"projects_all":[{"project_id":null,"title":null,"description":null}]}}' \\
1476
https://cocalc.com/api/v1/query
1477
==> {"event":"query",
1478
"id":"8ec4ac73-2595-42d2-ad47-0b9641043b46",
1479
"multi_response": False,
1480
"query": {"projects_all": [{"description": "Synthetic Monitoring",
1481
"project_id": "1fa1626e-ce25-4871-9b0e-19191cd03325",
1482
"title": "SYNTHMON"},
1483
{"description": "No Description",
1484
"project_id": "639a6b2e-7499-41b5-ac1f-1701809699a7",
1485
"title": "TESTPROJECT 99"}]}}
1486
\`\`\`
1487
1488
1489
Get project id, given title and description.
1490
\`\`\`
1491
curl -u sk_abcdefQWERTY090900000000: -H "Content-Type: application/json" \\
1492
-d '{"query":{"projects":{"project_id":null,"title":"MY NEW PROJECT 2","description":"desc 2"}}}' \\
1493
https://cocalc.com/api/v1/query
1494
==> {"event":"query",
1495
"query":{"projects":{"project_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d",
1496
"title":"MY NEW PROJECT 2",
1497
"description":"desc 2"}},
1498
"multi_response":false,
1499
"id":"2be22e08-f00c-4128-b112-fa8581c2d584"}
1500
\`\`\`
1501
1502
Get users, given the project id.
1503
\`\`\`
1504
curl -u sk_abcdefQWERTY090900000000: \\
1505
-H "Content-Type: application/json" \\
1506
-d '{"query":{"projects":{"project_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d","users":null}}}' \\
1507
https://cocalc.com/api/v1/query
1508
==> {"event":"query",
1509
"query":{"projects":{"project_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d",
1510
"users":{"6c28c5f4-3235-46be-b025-166b4dcaac7e":{"group":"owner"},
1511
"111634c0-7048-41e7-b2d0-f87129fd409e":{"group":"collaborator"}}}},
1512
"multi_response":false,
1513
"id":"9dd3ef3f-002b-4893-b31f-ff51440c855f"}
1514
\`\`\`
1515
1516
1517
Show project upgrades. Like the preceding example, this is a query to get users.
1518
In this example, there are no collaborators, but upgrades have been applied to the
1519
selected project. Upgrades do not show if none are applied.
1520
1521
The project shows the following upgrades:
1522
- cpu cores: 1
1523
- memory: 3000 MB
1524
- idle timeout: 24 hours (86400 seconds)
1525
- internet access: true
1526
- cpu shares: 3 (stored in database as 768 = 3 * 256)
1527
- disk space: 27000 MB
1528
- member hosting: true
1529
1530
\`\`\`
1531
curl -u sk_abcdefQWERTY090900000000: \\
1532
-H "Content-Type: application/json" \\
1533
-d '{"query":{"projects":{"project_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d","users":null}}}' \\
1534
https://cocalc.com/api/v1/query
1535
==> {"event":"query",
1536
"query":{"projects":{"project_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d",
1537
"users":{"6c28c5f4-3235-46be-b025-166b4dcaac7e":{
1538
"group":"owner",
1539
"upgrades":{"cores":1,
1540
"memory":3000,
1541
"mintime":86400,
1542
"network":1,
1543
"cpu_shares":768,
1544
"disk_quota":27000,
1545
"member_host":1}}}}},
1546
"multi_response":false,
1547
"id":"9dd3ef3f-002b-4893-b31f-ff51440c855f"}
1548
\`\`\`
1549
1550
Get editor settings for the present user.
1551
1552
\`\`\`
1553
curl -u sk_abcdefQWERTY090900000000: \\
1554
-H "Content-Type: application/json" \\
1555
-d '{"query":{"accounts":{"account_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d","editor_settings":null}}}' \\
1556
https://cocalc.com/api/v1/query
1557
==> {"event":"query",
1558
"multi_response":false,
1559
"id":"9dd3ef3f-002b-4893-b31f-ff51440c855f",
1560
"query": {"accounts": {"account_id": "29163de6-b5b0-496f-b75d-24be9aa2aa1d",
1561
"editor_settings": {"auto_close_brackets": True,
1562
"auto_close_xml_tags": True,
1563
"bindings": "standard",
1564
"code_folding": True,
1565
"electric_chars": True,
1566
"extra_button_bar": True,
1567
"first_line_number": 1,
1568
"indent_unit": 4,
1569
"jupyter_classic": False,
1570
"line_numbers": True,
1571
"line_wrapping": True,
1572
"match_brackets": True,
1573
"match_xml_tags": True,
1574
"multiple_cursors": True,
1575
"show_trailing_whitespace": True,
1576
"smart_indent": True,
1577
"spaces_instead_of_tabs": True,
1578
"strip_trailing_whitespace": False,
1579
"tab_size": 4,
1580
"theme": "default",
1581
"track_revisions": True,
1582
"undo_depth": 300}}}}
1583
\`\`\`
1584
1585
#### Examples of _set_ query.
1586
1587
Set title and description for a project, given the project id.
1588
\`\`\`
1589
curl -u sk_abcdefQWERTY090900000000: \\
1590
-H "Content-Type: application/json" \\
1591
-d '{"query":{"projects":{"project_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d", \\
1592
"title":"REVISED TITLE", \\
1593
"description":"REVISED DESC"}}}' \\
1594
https://cocalc.com/api/v1/query
1595
==> {"event":"query",
1596
"query":{},
1597
"multi_response":false,
1598
"id":"ad7d6b17-f5a9-4c5c-abc3-3823b1e1773f"}
1599
\`\`\`
1600
1601
Make a path public (publish a file).
1602
\`\`\`
1603
curl -u sk_abcdefQWERTY090900000000: \\
1604
-H "Content-Type: application/json" \\
1605
-d '{"query":{"public_paths":{"project_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d", \\
1606
"path":"myfile.txt", \\
1607
"description":"a shared text file"}}}' \\
1608
https://cocalc.com/api/v1/query
1609
==> {"event":"query",
1610
"query":{},
1611
"multi_response":false,
1612
"id":"ad7d6b17-f5a9-4c5c-abc3-3823b1e1773f"}
1613
1614
\`\`\`
1615
1616
Add an upgrade to a project. In the "get" example above showing project upgrades,
1617
change cpu upgrades from 3 to 4. The \`users\` object is returned as
1618
read, with \`cpu_shares\` increased to 1024 = 4 * 256.
1619
It is not necessary to specify the entire \`upgrades\` object
1620
if you are only setting the \`cpu_shares\` attribute because changes are merged in.
1621
1622
\`\`\`
1623
curl -u sk_abcdefQWERTY090900000000: \\
1624
-H "Content-Type: application/json" \\
1625
-d '{"query":{"projects":{"project_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d", \\
1626
"users":{"6c28c5f4-3235-46be-b025-166b4dcaac7e":{ \\
1627
"upgrades": {"cpu_shares":1024}}}}}}' \\
1628
https://cocalc.com/api/v1/query
1629
==> {"event":"query",
1630
"query":{},
1631
"multi_response":false,
1632
"id":"ec822d6f-f9fe-443d-9845-9cd5f68bac20"}
1633
\`\`\`
1634
1635
Set present user to open Jupyter notebooks in
1636
"CoCalc Jupyter Notebook" as opposed to "Classical Notebook".
1637
This change not usually needed, because accounts
1638
default to "CoCalc Jupyter Notebook".
1639
1640
It is not necessary to specify the entire \`editor_settings\` object
1641
if you are only setting the \`jupyter_classic\` attribute because changes are merged in.
1642
\`\`\`
1643
curl -u sk_abcdefQWERTY090900000000: \\
1644
-H "Content-Type: application/json" \\
1645
-d '{"query":{"accounts":{"account_id":"29163de6-b5b0-496f-b75d-24be9aa2aa1d","editor_settings":{"jupyter_classic":false}}}}' \\
1646
https://cocalc.com/api/v1/query
1647
==> {"event":"query",
1648
"multi_response":false,
1649
"id":"9dd3ef3f-002b-4893-b31f-ff51440c855f",
1650
"query": {}}
1651
\`\`\`
1652
1653
1654
__NOTE:__ Information on which fields are gettable and settable in the database tables
1655
via API message is in the directory 'db-schema', in CoCalc sources on GitHub at
1656
https://github.com/sagemathinc/cocalc/blob/master/src/packages/util/db-schema
1657
1658
Within directory 'db-schema':
1659
1660
- for _project_ fields you can get, see the definition of
1661
\`schema.projects.user_query.get.fields\`
1662
- for _project_ fields you can set, see the definition of
1663
\`schema.projects.user_query.set.fields\`
1664
- for _user account_ fields you can get, see the definition of
1665
\`schema.accounts.user_query.get.fields\`
1666
- for _user account_ fields you can set, see the definition of
1667
\`schema.accounts.user_query.set.fields\`\
1668
`,
1669
examples: [
1670
// TODO: create real examples! These are not done.
1671
[
1672
{ id: "uuid", query: "example1-query" },
1673
{ id: "uuid", event: "query", response: "..." },
1674
],
1675
[
1676
{ id: "uuid", query: "example2-query" },
1677
{ id: "uuid", event: "query", response: "..." },
1678
],
1679
],
1680
}),
1681
);
1682
1683
message({
1684
event: "query_cancel",
1685
id: undefined,
1686
});
1687
1688
/*
1689
API Key management for an account
1690
*/
1691
1692
// client --> hub
1693
message({
1694
event: "api_key",
1695
id: undefined,
1696
action: required, // 'get', 'delete', 'regenerate'
1697
password: undefined,
1698
});
1699
1700
// hub --> client
1701
message({
1702
event: "api_key_info",
1703
id: undefined,
1704
api_key: required,
1705
});
1706
1707
// client --> hub
1708
message({
1709
event: "api_keys",
1710
id: undefined,
1711
action: required, // 'get', 'delete', 'edit', 'create'
1712
project_id: undefined, // optional - if given then refers to api_key(s) for a project
1713
key_id: undefined, // integer id of the key
1714
expire: undefined, // used for setting or changing expiration date
1715
name: undefined,
1716
});
1717
1718
message({
1719
event: "api_keys_response",
1720
id: undefined,
1721
response: undefined,
1722
});
1723
1724
// client --> hub
1725
API(
1726
message2({
1727
event: "user_auth",
1728
fields: {
1729
id: {
1730
init: undefined,
1731
desc: "A unique UUID for the query",
1732
},
1733
account_id: {
1734
init: required,
1735
desc: "account_id for account to get an auth token for",
1736
},
1737
password: {
1738
init: required,
1739
desc: "password for account to get token for",
1740
},
1741
},
1742
desc: `\
1743
.. index:: pair: Token; Authentication
1744
Example:
1745
1746
Obtain a temporary authentication token for an account, which
1747
is a 24 character string. Tokens last for **12 hours**. You can
1748
only obtain an auth token for accounts that have a password.
1749
1750
\`\`\`
1751
curl -u sk_abcdefQWERTY090900000000: \\
1752
-d account_id=99ebde5c-58f8-4e29-b6e4-b55b8fd71a1b \\
1753
-d password=secret_password \\
1754
https://cocalc.com/api/v1/user_auth
1755
==> {"event":"user_auth_token","id":"9e8b68ac-08e8-432a-a853-398042fae8c9","auth_token":"BQokikJOvBiI2HlWgH4olfQ2"}
1756
\`\`\`
1757
1758
You can now use the auth token to craft a URL like this:
1759
1760
https://cocalc.com/auth/impersonate?auth_token=BQokikJOvBiI2HlWgH4olfQ2
1761
1762
and provide that to a user. When they visit that URL, they will be temporarily signed in as that user.\
1763
`,
1764
}),
1765
);
1766
1767
// Info about available upgrades for a given user
1768
API(
1769
message2({
1770
event: "get_available_upgrades",
1771
fields: {
1772
id: {
1773
init: undefined,
1774
desc: "A unique UUID for the query",
1775
},
1776
},
1777
desc: `\
1778
This request returns information on project upgrdes for the user
1779
whose API key appears in the request.
1780
Two objects are returned, total upgrades and available upgrades.
1781
1782
See https://github.com/sagemathinc/cocalc/blob/master/src/packages/util/upgrade-spec.js for units
1783
1784
Example:
1785
\`\`\`
1786
curl -X POST -u sk_abcdefQWERTY090900000000: https://cocalc.com/api/v1/get_available_upgrades
1787
==>
1788
{"id":"57fcfd71-b50f-44ef-ba66-1e37cac858ef",
1789
"event":"available_upgrades",
1790
"total":{
1791
"cores":10,
1792
"cpu_shares":2048,
1793
"disk_quota":200000,
1794
"member_host":80,
1795
"memory":120000,
1796
"memory_request":8000,
1797
"mintime":3456000,
1798
"network":400},
1799
"excess":{},
1800
"available":{
1801
"cores":6,
1802
"cpu_shares":512,
1803
"disk_quota":131000,
1804
"member_host":51,
1805
"memory":94000,
1806
"memory_request":8000,
1807
"mintime":1733400,
1808
"network":372}}
1809
\`\`\`\
1810
`,
1811
}),
1812
);
1813
1814
// client --> hub
1815
API(
1816
message2({
1817
event: "disconnect_from_project",
1818
fields: {
1819
id: {
1820
init: undefined,
1821
desc: "A unique UUID for the query",
1822
},
1823
project_id: {
1824
init: required,
1825
desc: "id of project to disconnect from",
1826
},
1827
},
1828
desc: "Disconnect the hub that gets this message from the project. This is used entirely for internal debugging and development.",
1829
}),
1830
);
1831
1832
// client <-- hub
1833
message({
1834
event: "available_upgrades",
1835
id: undefined,
1836
total: required, // total upgrades the user has purchased
1837
excess: required, // upgrades where the total allocated exceeds what user has purchased
1838
available: required,
1839
}); // how much of each purchased upgrade is available
1840
1841
// Remove *all* upgrades applied by the signed in user to any projects,
1842
// or just from a specific list.
1843
// client --> hub
1844
message({
1845
event: "remove_all_upgrades",
1846
projects: undefined, // optional array of project_id's.
1847
id: undefined,
1848
});
1849
1850
/*
1851
Sage Worksheet Support, v2
1852
*/
1853
// client --> project
1854
message({
1855
event: "sagews_execute_code",
1856
id: undefined,
1857
path: required,
1858
code: required,
1859
data: undefined,
1860
cell_id: undefined, // if is a cell, which is being executed (so if client does not ack, output is still recorded)
1861
preparse: true,
1862
});
1863
1864
// project --> client
1865
message({
1866
event: "sagews_output",
1867
id: required,
1868
path: required,
1869
output: required,
1870
}); // the actual output message
1871
1872
// client --> project
1873
message({
1874
event: "sagews_output_ack",
1875
id: required,
1876
});
1877
1878
// client --> project
1879
message({
1880
event: "sagews_interrupt",
1881
id: undefined,
1882
path: required,
1883
});
1884
1885
// client --> project
1886
message({
1887
event: "sagews_quit",
1888
id: undefined,
1889
path: required,
1890
});
1891
1892
// client --> project
1893
message({
1894
event: "sagews_start",
1895
id: undefined,
1896
path: required,
1897
});
1898
1899
// client --> hub
1900
// It's an error if user is not signed in, since
1901
// then we don't know who to track.
1902
message({
1903
event: "user_tracking",
1904
id: undefined,
1905
evt: required, // string -- the event being tracked (max length 80 characters)
1906
value: required, // map -- additional info about that event
1907
});
1908
1909
// Request to purchase a license (either via stripe or a quote)
1910
API(
1911
message({
1912
event: "purchase_license",
1913
id: undefined,
1914
info: required, // import { PurchaseInfo } from "@cocalc/util/licenses/purchase/util";
1915
}),
1916
);
1917
1918
message({
1919
event: "purchase_license_resp",
1920
id: undefined,
1921
resp: required, // a string - basically a message to show the user
1922
});
1923
1924
API(
1925
message({
1926
event: "chatgpt",
1927
id: undefined,
1928
text: required, // text of the question
1929
system: undefined, // optional (highly recommended!) extra system context, e.g,. "using cocalc".
1930
history: undefined, // optional history of this conversation in chatgpt format, so { role: "assistant" | "user" | "system"; content: string }[];
1931
project_id: undefined,
1932
path: undefined,
1933
model: undefined,
1934
tag: undefined,
1935
stream: undefined, // if true, instead sends many little chatgpt_response messages with the last text value undefined.
1936
}),
1937
);
1938
1939
message({
1940
event: "chatgpt_response",
1941
id: undefined,
1942
text: undefined, // text of the response
1943
multi_response: undefined, // used for streaming
1944
});
1945
1946
API(
1947
// Read
1948
message({
1949
event: "openai_embeddings_search",
1950
scope: required,
1951
id: undefined,
1952
text: undefined, // at least one of text or filter must be specified; if text given, does vector search
1953
filter: undefined,
1954
limit: required,
1955
selector: undefined,
1956
offset: undefined,
1957
}),
1958
);
1959
1960
message({
1961
event: "openai_embeddings_search_response",
1962
id: undefined,
1963
matches: required, // matching points
1964
});
1965
1966
API(
1967
// Create/Update
1968
message({
1969
event: "openai_embeddings_save",
1970
project_id: required,
1971
path: required,
1972
data: required,
1973
id: undefined,
1974
}),
1975
);
1976
1977
message({
1978
event: "openai_embeddings_save_response",
1979
id: undefined,
1980
ids: required, // uuid's of saved data
1981
});
1982
1983
API(
1984
// Delete
1985
message({
1986
event: "openai_embeddings_remove",
1987
id: undefined,
1988
project_id: required,
1989
path: required,
1990
data: required,
1991
}),
1992
);
1993
1994
message({
1995
event: "openai_embeddings_remove_response",
1996
id: undefined,
1997
ids: required, // uuid's of removed data
1998
});
1999
2000
API(
2001
message({
2002
event: "jupyter_execute",
2003
id: undefined,
2004
hash: undefined, // give either hash *or* kernel, input, history, etc.
2005
kernel: undefined, // jupyter kernel
2006
input: undefined, // input code to execute
2007
history: undefined, // optional history of this conversation as a list of input strings. Do not include output
2008
project_id: undefined, // project it should run in.
2009
path: undefined, // optional path where execution happens
2010
tag: undefined,
2011
pool: undefined, // {size?: number; timeout_s?: number;}
2012
limits: undefined, // see packages/jupyter/nbgrader/jupyter-run.ts
2013
}),
2014
);
2015
2016
message({
2017
event: "jupyter_execute_response",
2018
id: undefined,
2019
output: required, // the response
2020
total_time_s: undefined,
2021
time: undefined,
2022
});
2023
2024
API(
2025
message({
2026
event: "jupyter_kernels",
2027
id: undefined,
2028
project_id: undefined,
2029
kernels: undefined, // response is same message but with this filled in with array of data giving available kernels
2030
}),
2031
);
2032
2033
API(
2034
message({
2035
event: "jupyter_start_pool",
2036
id: undefined,
2037
project_id: undefined,
2038
kernels: undefined, // response is same message but with this filled in with array of data giving available kernels
2039
}),
2040
);
2041
2042