Path: blob/master/src/packages/project/conat/api/index.ts
1450 views
/*12DEVELOPMENT:34How to do development (so in a dev project doing cc-in-cc dev).560. From the browser, terminate this api server running in the project:78await cc.client.conat_client.projectApi(cc.current()).system.terminate({service:'api'})9101. Create a file project-env.sh as explained in projects/conat/README.md, which defines these environment variables (your values will be different):1112export COCALC_PROJECT_ID="00847397-d6a8-4cb0-96a8-6ef64ac3e6cf"13export COCALC_USERNAME=`echo $COCALC_PROJECT_ID | tr -d '-'`14export HOME="/projects/6b851643-360e-435e-b87e-f9a6ab64a8b1/cocalc/src/data/projects/$COCALC_PROJECT_ID"15export DATA=$HOME/.smc1617# CRITICAL: make sure to create and set an api key! Otherwise you will be blocked:18export API_KEY=sk-OUwxAN8d0n7Ecd4800005519export COMPUTE_SERVER_ID=02021# optional for more logging22export DEBUG=cocalc:*23export DEBUG_CONSOLE=yes2425If API_KEY is a project-wide API key, then you can change26COCALC_PROJECT_ID however you want and don't have to worry27about whether the project is running or the project secret28key changing when the project is restarted.29302. Then do this:3132$ . project-env.sh33$ node34...35> require("@cocalc/project/conat/api/index").init()3637You can then easily be able to grab some state, e.g., by writing this in any cocalc code,38rebuilding and restarting:3940global.x = {...}4142Remember, 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.43443. Use the browser to see the project is on the conat network and works:4546a = cc.client.conat_client.projectApi({project_id:'81e0c408-ac65-4114-bad5-5f4b6539bd0e'});47await a.system.ping();48await a.system.exec({command:'echo $COCALC_PROJECT_ID'});4950*/5152import { type ProjectApi } from "@cocalc/conat/project/api";53import { connectToConat } from "@cocalc/project/conat/connection";54import { getSubject } from "../names";55import { terminate as terminateOpenFiles } from "@cocalc/project/conat/open-files";56import { close as closeListings } from "@cocalc/project/conat/listings";57import { project_id } from "@cocalc/project/data";58import { close as closeFilesRead } from "@cocalc/project/conat/files/read";59import { close as closeFilesWrite } from "@cocalc/project/conat/files/write";60import { getLogger } from "@cocalc/project/logger";6162const logger = getLogger("conat:api");6364export function init() {65serve();66}6768let terminate = false;69async function serve() {70logger.debug("serve: create project conat api service");71const cn = connectToConat();72const subject = getSubject({ service: "api" });73// @ts-ignore74const name = `project-${project_id}`;75logger.debug(`serve: creating api service ${name}`);76const api = await cn.subscribe(subject);77logger.debug(`serve: subscribed to subject='${subject}'`);78await listen(api, subject);79}8081async function listen(api, subject) {82for await (const mesg of api) {83if (terminate) {84return;85}86(async () => {87try {88await handleMessage(api, subject, mesg);89} catch (err) {90logger.debug(`WARNING: issue handling a message -- ${err}`);91}92})();93}94}9596async function handleMessage(api, subject, mesg) {97const request = mesg.data ?? ({} as any);98// logger.debug("got message", request);99if (request.name == "system.terminate") {100// TODO: should be part of handleApiRequest below, but done differently because101// one case halts this loop102const { service } = request.args[0] ?? {};103if (service == "open-files") {104terminateOpenFiles();105mesg.respond({ status: "terminated", service });106return;107} else if (service == "listings") {108closeListings();109mesg.respond({ status: "terminated", service });110return;111} else if (service == "files:read") {112await closeFilesRead();113mesg.respond({ status: "terminated", service });114return;115} else if (service == "files:write") {116await closeFilesWrite();117mesg.respond({ status: "terminated", service });118return;119} else if (service == "api") {120// special hook so admin can terminate handling. This is useful for development.121terminate = true;122console.warn("TERMINATING listening on ", subject);123logger.debug("TERMINATING listening on ", subject);124mesg.respond({ status: "terminated", service });125api.stop();126return;127} else {128mesg.respond({ error: `Unknown service ${service}` });129}130} else {131handleApiRequest(request, mesg);132}133}134135async function handleApiRequest(request, mesg) {136let resp;137const { name, args } = request as any;138if (name == "ping") {139resp = "pong";140} else {141try {142// logger.debug("handling project.api request:", { name });143resp = (await getResponse({ name, args })) ?? null;144} catch (err) {145logger.debug(`project.api request err = ${err}`, { name });146resp = { error: `${err}` };147}148}149mesg.respond(resp);150}151152import * as system from "./system";153import * as editor from "./editor";154import * as sync from "./sync";155156export const projectApi: ProjectApi = {157system,158editor,159sync,160};161162async function getResponse({ name, args }) {163const [group, functionName] = name.split(".");164const f = projectApi[group]?.[functionName];165if (f == null) {166throw Error(`unknown function '${name}'`);167}168return await f(...args);169}170171172