CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/jupyter/kernel/websocket-api.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
Support for the project's websocket-based request/response API, which is used for handling
8
various messages related to working with Jupyter.
9
*/
10
11
import { get_existing_kernel } from "@cocalc/jupyter/kernel";
12
import { get_kernel_data } from "@cocalc/jupyter/kernel/kernel-data";
13
import { bufferToBase64 } from "@cocalc/util/base64";
14
15
export async function handleApiRequest(
16
path: string,
17
endpoint: string,
18
query?: any,
19
): Promise<any> {
20
// First handle endpoints that do not depend on a specific kernel.
21
switch (endpoint) {
22
case "kernels":
23
return await get_kernel_data();
24
}
25
26
// Now endpoints that do depend on a specific kernel.
27
const kernel = get_existing_kernel(path);
28
if (kernel == null) {
29
if (endpoint == "signal") {
30
// It's not a serious problem to try to send a signal to a non-existent kernel. A no-op
31
// is completely reasonable, since you only send signals to kill or interrupt, and a non-existent
32
// kernel is already killed or interrupted. See https://github.com/sagemathinc/cocalc/issues/4420
33
return {};
34
}
35
throw Error(`api endpoint ${endpoint}: no kernel with path '${path}'`);
36
}
37
switch (endpoint) {
38
case "save_ipynb_file":
39
await kernel.save_ipynb_file();
40
return {};
41
42
case "signal":
43
kernel.signal(query.signal);
44
return {};
45
46
case "kernel_info":
47
return await kernel.kernel_info();
48
49
case "more_output":
50
return kernel.more_output(query.id);
51
52
case "complete":
53
return await kernel.complete(get_code_and_cursor_pos(query));
54
55
case "introspect":
56
const { code, cursor_pos } = get_code_and_cursor_pos(query);
57
let detail_level = 0;
58
if (query.level != null) {
59
try {
60
detail_level = parseInt(query.level);
61
if (detail_level < 0) {
62
detail_level = 0;
63
} else if (detail_level > 1) {
64
detail_level = 1;
65
}
66
} catch (err) {}
67
}
68
return await kernel.introspect({
69
code,
70
cursor_pos,
71
detail_level,
72
});
73
74
case "store":
75
const { key, value } = query;
76
if (value === undefined) {
77
// undefined when getting the value
78
return kernel.store.get(key);
79
} else if (value === null) {
80
// null is used for deleting the value
81
kernel.store.delete(key);
82
return {};
83
} else {
84
kernel.store.set(key, value);
85
return {};
86
}
87
88
case "comm":
89
return kernel.send_comm_message_to_kernel(query);
90
91
case "ipywidgets-get-buffer":
92
const { model_id, buffer_path } = query;
93
const buffer = kernel.ipywidgetsGetBuffer(model_id, buffer_path);
94
if (buffer == null) {
95
throw Error(
96
`no buffer for model=${model_id}, buffer_path=${JSON.stringify(
97
buffer_path,
98
)}`,
99
);
100
}
101
return { buffer64: bufferToBase64(buffer) };
102
default:
103
throw Error(`unknown endpoint "${endpoint}"`);
104
}
105
}
106
107
function get_code_and_cursor_pos(query: any): {
108
code: string;
109
cursor_pos: number;
110
} {
111
const code: string = query.code;
112
if (!code) {
113
throw Error("must specify code");
114
}
115
let cursor_pos: number;
116
if (query.cursor_pos != null) {
117
try {
118
cursor_pos = parseInt(query.cursor_pos);
119
} catch (error) {
120
cursor_pos = code.length;
121
}
122
} else {
123
cursor_pos = code.length;
124
}
125
126
return { code, cursor_pos };
127
}
128
129