diff --git a/lib/code-manager.ts b/lib/code-manager.ts index 657e01238..9715d5202 100644 --- a/lib/code-manager.ts +++ b/lib/code-manager.ts @@ -498,3 +498,7 @@ function adjustCellFoldRange(editor: TextEditor, range: Range) { new Point(endRow, endWidth) ); } + +export function getEscapeBlankRowsEndRow(editor: TextEditor, end: Point) { + return end.row === editor.getLastBufferRow() ? end.row : end.row - 1; +} diff --git a/lib/kernel-manager.ts b/lib/kernel-manager.ts index 5edd63c28..6a9d9a92d 100644 --- a/lib/kernel-manager.ts +++ b/lib/kernel-manager.ts @@ -137,7 +137,7 @@ export class KernelManager { }); } - async updateKernelSpecs(grammar: Grammar | null | undefined) { + async updateKernelSpecs(grammar?: Grammar | null | undefined) { const kernelSpecs = await this.update(); if (kernelSpecs.length === 0) { diff --git a/lib/kernel-transport.ts b/lib/kernel-transport.ts index a33d95d3e..b5fc8073c 100644 --- a/lib/kernel-transport.ts +++ b/lib/kernel-transport.ts @@ -2,6 +2,7 @@ import { Grammar } from "atom"; import { observable, action } from "mobx"; import { log } from "./utils"; import type { KernelspecMetadata } from "@nteract/types"; +import type { Kernel } from "@jupyterlab/services"; export type ResultsCallback = ( message: any, @@ -18,7 +19,7 @@ export default class KernelTransport { inspector = { bundle: {}, }; - kernelSpec: KernelspecMetadata; + kernelSpec: Kernel.ISpecModel | KernelspecMetadata; grammar: Grammar; language: string; displayName: string; @@ -26,7 +27,10 @@ export default class KernelTransport { // still `KernelTransport` is better to have `gatewayName` property for code simplicity in the other parts of code gatewayName: string | null | undefined; - constructor(kernelSpec: KernelspecMetadata, grammar: Grammar) { + constructor( + kernelSpec: Kernel.ISpecModel | KernelspecMetadata, + grammar: Grammar + ) { this.kernelSpec = kernelSpec; this.grammar = grammar; this.language = kernelSpec.language.toLowerCase(); diff --git a/lib/kernel.ts b/lib/kernel.ts index fb7849976..07287f29d 100644 --- a/lib/kernel.ts +++ b/lib/kernel.ts @@ -20,6 +20,7 @@ import type { import InputView from "./input-view"; import KernelTransport from "./kernel-transport"; import type { ResultsCallback } from "./kernel-transport"; +import type { Kernel as JupyterlabKernel } from "@jupyterlab/services"; import type { Message } from "./hydrogen"; import type { KernelspecMetadata } from "@nteract/types"; @@ -215,7 +216,7 @@ export default class Kernel { this.middleware = [delegateToTransport]; } - get kernelSpec(): KernelspecMetadata { + get kernelSpec(): JupyterlabKernel.ISpecModel | KernelspecMetadata { return this.transport.kernelSpec; } @@ -305,7 +306,7 @@ export default class Kernel { this.firstMiddlewareAdapter.shutdown(); } - restart(onRestarted: ((...args: Array) => any) | null | undefined) { + restart(onRestarted?: ((...args: Array) => any) | null | undefined) { this.firstMiddlewareAdapter.restart(onRestarted); this.setExecutionCount(0); this.setLastExecutionTime("No execution"); diff --git a/lib/main.ts b/lib/main.ts index 5fe7f4e54..d10fa511d 100644 --- a/lib/main.ts +++ b/lib/main.ts @@ -22,7 +22,7 @@ import KernelPicker from "./kernel-picker"; import WSKernelPicker from "./ws-kernel-picker"; import ExistingKernelPicker from "./existing-kernel-picker"; import HydrogenProvider from "./plugin-api/hydrogen-provider"; -import store from "./store"; +import store, { Store, StoreLike } from "./store"; import kernelManager from "./kernel-manager"; import services from "./services"; import * as commands from "./commands"; @@ -85,67 +85,72 @@ export function activate() { }) ); store.subscriptions.add( - atom.commands.add("atom-text-editor:not([mini])", { - "hydrogen:run": () => run(), - "hydrogen:run-all": () => runAll(), - "hydrogen:run-all-above": () => runAllAbove(), - "hydrogen:run-and-move-down": () => run(true), - "hydrogen:run-cell": () => runCell(), - "hydrogen:run-cell-and-move-down": () => runCell(true), - "hydrogen:toggle-watches": () => atom.workspace.toggle(WATCHES_URI), - "hydrogen:toggle-output-area": () => commands.toggleOutputMode(), - "hydrogen:toggle-kernel-monitor": async () => { - const lastItem = atom.workspace.getActivePaneItem(); - const lastPane = atom.workspace.paneForItem(lastItem); - await atom.workspace.toggle(KERNEL_MONITOR_URI); - if (lastPane) { - lastPane.activate(); - } - }, - "hydrogen:start-local-kernel": () => startZMQKernel(), - "hydrogen:connect-to-remote-kernel": () => connectToWSKernel(), - "hydrogen:connect-to-existing-kernel": () => connectToExistingKernel(), - "hydrogen:add-watch": () => { - if (store.kernel) { - store.kernel.watchesStore.addWatchFromEditor(store.editor); - openOrShowDock(WATCHES_URI); - } - }, - "hydrogen:remove-watch": () => { - if (store.kernel) { - store.kernel.watchesStore.removeWatch(); - openOrShowDock(WATCHES_URI); - } - }, - "hydrogen:update-kernels": () => kernelManager.updateKernelSpecs(), - "hydrogen:toggle-inspector": () => commands.toggleInspector(store), - "hydrogen:interrupt-kernel": () => - handleKernelCommand( - { - command: "interrupt-kernel", - }, - store - ), - "hydrogen:restart-kernel": () => - handleKernelCommand( - { - command: "restart-kernel", - }, - store - ), - "hydrogen:shutdown-kernel": () => - handleKernelCommand( - { - command: "shutdown-kernel", - }, - store - ), - "hydrogen:clear-result": () => result.clearResult(store), - "hydrogen:export-notebook": () => exportNotebook(), - "hydrogen:fold-current-cell": () => foldCurrentCell(), - "hydrogen:fold-all-but-current-cell": () => foldAllButCurrentCell(), - "hydrogen:clear-results": () => result.clearResults(store), - }) + atom.commands.add<"atom-text-editor:not([mini])">( + "atom-text-editor:not([mini])", + { + "hydrogen:run": () => run(), + "hydrogen:run-all": () => runAll(), + "hydrogen:run-all-above": () => runAllAbove(), + "hydrogen:run-and-move-down": () => run(true), + "hydrogen:run-cell": () => runCell(), + "hydrogen:run-cell-and-move-down": () => runCell(true), + "hydrogen:toggle-watches": () => atom.workspace.toggle(WATCHES_URI), + "hydrogen:toggle-output-area": () => commands.toggleOutputMode(), + "hydrogen:toggle-kernel-monitor": async () => { + const lastItem = atom.workspace.getActivePaneItem(); + const lastPane = atom.workspace.paneForItem(lastItem); + await atom.workspace.toggle(KERNEL_MONITOR_URI); + if (lastPane) { + lastPane.activate(); + } + }, + "hydrogen:start-local-kernel": () => startZMQKernel(), + "hydrogen:connect-to-remote-kernel": () => connectToWSKernel(), + "hydrogen:connect-to-existing-kernel": () => connectToExistingKernel(), + "hydrogen:add-watch": () => { + if (store.kernel) { + store.kernel.watchesStore.addWatchFromEditor(store.editor); + openOrShowDock(WATCHES_URI); + } + }, + "hydrogen:remove-watch": () => { + if (store.kernel) { + store.kernel.watchesStore.removeWatch(); + openOrShowDock(WATCHES_URI); + } + }, + "hydrogen:update-kernels": async () => { + await kernelManager.updateKernelSpecs(); + }, + "hydrogen:toggle-inspector": () => commands.toggleInspector(store), + "hydrogen:interrupt-kernel": () => + handleKernelCommand( + { + command: "interrupt-kernel", + }, + store + ), + "hydrogen:restart-kernel": () => + handleKernelCommand( + { + command: "restart-kernel", + }, + store + ), + "hydrogen:shutdown-kernel": () => + handleKernelCommand( + { + command: "shutdown-kernel", + }, + store + ), + "hydrogen:clear-result": () => result.clearResult(store), + "hydrogen:export-notebook": () => exportNotebook(), + "hydrogen:fold-current-cell": () => foldCurrentCell(), + "hydrogen:fold-all-but-current-cell": () => foldAllButCurrentCell(), + "hydrogen:clear-results": () => result.clearResults(store), + } + ) ); store.subscriptions.add( atom.commands.add("atom-workspace", { @@ -211,6 +216,9 @@ export function activate() { case KERNEL_MONITOR_URI: return new KernelMonitorPane(store); + default: { + return; + } } }) ); @@ -278,23 +286,19 @@ function connectToExistingKernel() { existingKernelPicker.toggle(); } +interface KernelCommand { + command: string; + payload?: KernelspecMetadata | null | undefined; +} + function handleKernelCommand( - { - command, - payload, - }: { - command: string; - payload: KernelspecMetadata | null | undefined; - }, - { - kernel, - markers, - }: { - kernel: Kernel | null | undefined; - markers: MarkerStore | null | undefined; - } + { command, payload }: KernelCommand, // TODO payload is not used! + { kernel, markers }: Store | StoreLike ) { - log("handleKernelCommand:", arguments); + log("handleKernelCommand:", [ + { command, payload }, + { kernel, markers }, + ]); if (!kernel) { const message = "No running kernel for grammar or editor found"; @@ -403,7 +407,7 @@ function _runAll( const row = codeManager.escapeBlankRows( editor, start.row, - end.row == editor.getLastBufferRow() ? end.row : end.row - 1 + codeManager.getEscapeBlankRowsEndRow(editor, end) ); const cellType = codeManager.getMetadataForRow(editor, start); const code = @@ -457,7 +461,7 @@ function _runAllAbove(editor: TextEditor, kernel: Kernel) { const row = codeManager.escapeBlankRows( editor, start.row, - end.row == editor.getLastBufferRow() ? end.row : end.row - 1 + codeManager.getEscapeBlankRowsEndRow(editor, end) ); const cellType = codeManager.getMetadataForRow(editor, start); @@ -496,7 +500,7 @@ function runCell(moveDown: boolean = false) { const row = codeManager.escapeBlankRows( editor, start.row, - end.row == editor.getLastBufferRow() ? end.row : end.row - 1 + codeManager.getEscapeBlankRowsEndRow(editor, end) ); const cellType = codeManager.getMetadataForRow(editor, start); const code = diff --git a/lib/store/index.ts b/lib/store/index.ts index fd0676af4..adfe457ae 100644 --- a/lib/store/index.ts +++ b/lib/store/index.ts @@ -331,3 +331,8 @@ const store = new Store(); export default store; // For debugging window.hydrogen_store = store; + +export interface StoreLike { + kernel?: Kernel | null | undefined; + markers?: MarkerStore | null | undefined; +} diff --git a/lib/utils.tsx b/lib/utils.tsx index 184e73eff..d6d5b800b 100644 --- a/lib/utils.tsx +++ b/lib/utils.tsx @@ -115,8 +115,10 @@ export function msgSpecV4toV5(message: Message) { message.content.text = message.content.data; } break; + default: { + // no conversion needed + } } - return message; } const markupGrammars = new Set([ @@ -317,3 +319,11 @@ export function setPreviouslyFocusedElement(obj: { obj.previouslyFocusedElement = activeElement; } } + +/** Make the properties of a type Writable */ +export type Writeable = { -readonly [P in keyof T]: T[P] }; + +/** Make the properties of aexport type Writable recursively */ +export type DeepWriteable = { + -readonly [P in keyof T]: DeepWriteable; +}; diff --git a/lib/ws-kernel-picker.ts b/lib/ws-kernel-picker.ts index 392387504..b5140cbb6 100644 --- a/lib/ws-kernel-picker.ts +++ b/lib/ws-kernel-picker.ts @@ -4,7 +4,7 @@ import _ from "lodash"; import tildify from "tildify"; import { v4 } from "uuid"; import ws from "ws"; -import xhr from "xmlhttprequest"; +import { XMLHttpRequest as NodeXMLHttpRequest } from "@aminya/xmlhttprequest"; import { URL } from "url"; import { Kernel, Session, ServerConnection } from "@jupyterlab/services"; import Config from "./config"; @@ -12,11 +12,42 @@ import WSKernel from "./ws-kernel"; import InputView from "./input-view"; import store from "./store"; import type { KernelspecMetadata } from "@nteract/types"; -import { setPreviouslyFocusedElement } from "./utils"; +import { setPreviouslyFocusedElement, DeepWriteable } from "./utils"; + +type SelectListItem = any; + +export type KernelGatewayOptions = Parameters< + typeof ServerConnection["makeSettings"] +>[0]; + +// Based on the config documentation +// TODO verify this +export type MinimalServerConnectionSettings = Pick< + KernelGatewayOptions, + "baseUrl" +>; + +export interface KernelGateway { + name: string; + options: KernelGatewayOptions; +} + +export interface SessionInfoWithModel { + model: Kernel.IModel; + options: Parameters[1]; +} + +export interface SessionInfoWithoutModel { + name?: string; + kernelSpecs: Kernel.ISpecModel[]; + options: Parameters[0]; + // no model + model?: never | null | undefined; +} class CustomListView { - onConfirmed: ((...args: Array) => any) | null | undefined = null; - onCancelled: ((...args: Array) => any) | null | undefined = null; + onConfirmed: (item: SelectListItem) => void | null | undefined = null; + onCancelled: () => void | null | undefined = null; previouslyFocusedElement: HTMLElement | null | undefined; selectListView: SelectListView; panel: Panel | null | undefined; @@ -26,13 +57,13 @@ class CustomListView { this.selectListView = new SelectListView({ itemsClassList: ["mark-active"], items: [], - filterKeyForItem: (item) => item.name, - elementForItem: (item) => { + filterKeyForItem: (item: SelectListItem) => item.name, + elementForItem: (item: SelectListItem) => { const element = document.createElement("li"); element.textContent = item.name; return element; }, - didConfirmSelection: (item) => { + didConfirmSelection: (item: SelectListItem) => { if (this.onConfirmed) { this.onConfirmed(item); } @@ -87,7 +118,11 @@ export default class WSKernelPicker { this.listView = new CustomListView(); } - async toggle(_kernelSpecFilter: (kernelSpec: Kernel.ISpecModel) => boolean) { + async toggle( + _kernelSpecFilter: ( + kernelSpec: Kernel.ISpecModel | KernelspecMetadata + ) => boolean + ) { setPreviouslyFocusedElement(this.listView); this._kernelSpecFilter = _kernelSpecFilter; const gateways = Config.getJson("gateways") || []; @@ -148,7 +183,7 @@ export default class WSKernelPicker { return response; } - async promptForCookie(options: any) { + async promptForCookie(options: DeepWriteable) { const cookie = await this.promptForText("Cookie:"); if (cookie === null) { @@ -162,17 +197,17 @@ export default class WSKernelPicker { options.requestHeaders.Cookie = cookie; options.xhrFactory = () => { - const request = new xhr.XMLHttpRequest(); + const request = new NodeXMLHttpRequest(); // Disable protections against setting the Cookie header request.setDisableHeaderCheck(true); - return request; + return request as XMLHttpRequest; // TODO fix the types }; options.wsFactory = (url, protocol) => { // Authentication requires requests to appear to be same-origin const parsedUrl = new URL(url); - if (parsedUrl.protocol == "wss:") { + if (parsedUrl.protocol === "wss:") { parsedUrl.protocol = "https:"; } else { parsedUrl.protocol = "http:"; @@ -193,7 +228,7 @@ export default class WSKernelPicker { return true; } - async promptForToken(options: any) { + async promptForToken(options: DeepWriteable) { const token = await this.promptForText("Token:"); if (token === null) { @@ -204,7 +239,7 @@ export default class WSKernelPicker { return true; } - async promptForCredentials(options: any) { + async promptForCredentials(options: DeepWriteable) { await this.listView.selectListView.update({ items: [ { @@ -244,7 +279,7 @@ export default class WSKernelPicker { return false; } - async onGateway(gatewayInfo: any) { + async onGateway(gatewayInfo: KernelGateway) { this.listView.onConfirmed = null; await this.listView.selectListView.update({ items: [], @@ -252,13 +287,11 @@ export default class WSKernelPicker { loadingMessage: "Loading sessions...", emptyMessage: "No sessions available", }); - const gatewayOptions = Object.assign( - { - xhrFactory: () => new xhr.XMLHttpRequest(), - wsFactory: (url, protocol) => new ws(url, protocol), - }, - gatewayInfo.options - ); + const gatewayOptions = { + xhrFactory: () => new NodeXMLHttpRequest() as XMLHttpRequest, // TODO fix the types + wsFactory: (url, protocol) => new ws(url, protocol), + ...gatewayInfo.options, + }; let serverSettings = ServerConnection.makeSettings(gatewayOptions); let specModels: Kernel.ISpecModels | undefined; try { @@ -309,12 +342,12 @@ export default class WSKernelPicker { return name ? kernelNames.includes(name) : true; }); const items = sessionModels.map((model) => { - let name; + let name: string; if (model.path) { name = tildify(model.path); - } else if (model.notebook && model.notebook?.path) { - name = tildify(model.notebook?.path); // TODO fix the types + } else if (model.notebook?.path) { + name = tildify(model.notebook!.path); // TODO fix the types } else { name = `Session ${model.id}`; } @@ -356,51 +389,77 @@ export default class WSKernelPicker { } } - async onSession(gatewayName: string, sessionInfo: any) { - if (!sessionInfo.model) { - if (!sessionInfo.name) { - await this.listView.selectListView.update({ - items: [], - errorMessage: "This gateway does not support listing sessions", - loadingMessage: null, - infoMessage: null, - }); - } + onSession( + gatewayName: string, + sessionInfo: SessionInfoWithModel | SessionInfoWithoutModel + ) { + const model = sessionInfo.model; + if (model === null || model === undefined) { + // model not provided + return this.onSessionWitouthModel( + gatewayName, + sessionInfo as SessionInfoWithoutModel + ); + } else { + // with model + return this.onSessionWithModel( + gatewayName, + sessionInfo as SessionInfoWithModel + ); + } + } - const items = _.map(sessionInfo.kernelSpecs, (spec) => { - const options = { - serverSettings: sessionInfo.options, - kernelName: spec.name, - path: this._path, - }; - return { - name: spec.display_name, - options, - }; - }); + async onSessionWithModel( + gatewayName: string, + sessionInfo: SessionInfoWithModel + ) { + this.onSessionChosen( + gatewayName, + await Session.connectTo(sessionInfo.model.id, sessionInfo.options) + ); + } - this.listView.onConfirmed = this.startSession.bind(this, gatewayName); + async onSessionWitouthModel( + gatewayName: string, + sessionInfo: SessionInfoWithoutModel + ) { + if (!sessionInfo.name) { await this.listView.selectListView.update({ - items, - emptyMessage: "No kernel specs available", - infoMessage: "Select a session", + items: [], + errorMessage: "This gateway does not support listing sessions", loadingMessage: null, + infoMessage: null, }); - } else { - this.onSessionChosen( - gatewayName, - await Session.connectTo(sessionInfo.model.id, sessionInfo.options) - ); } + + const items = _.map(sessionInfo.kernelSpecs, (spec) => { + const options = { + serverSettings: sessionInfo.options, + kernelName: spec.name, + path: this._path, + }; + return { + name: spec.display_name, + options, + }; + }); + + this.listView.onConfirmed = this.startSession.bind(this, gatewayName); + await this.listView.selectListView.update({ + items, + emptyMessage: "No kernel specs available", + infoMessage: "Select a session", + loadingMessage: null, + }); } - startSession(gatewayName: string, sessionInfo: any) { + startSession(gatewayName: string, sessionInfo: SessionInfoWithoutModel) { Session.startNew(sessionInfo.options).then( this.onSessionChosen.bind(this, gatewayName) ); } - async onSessionChosen(gatewayName: string, session: any) { + async onSessionChosen(gatewayName: string, session: Session.ISession) { this.listView.cancel(); const kernelSpec = await session.kernel.getSpec(); if (!store.grammar) { diff --git a/lib/ws-kernel.ts b/lib/ws-kernel.ts index e90b45f1c..7eb25bec8 100644 --- a/lib/ws-kernel.ts +++ b/lib/ws-kernel.ts @@ -3,7 +3,7 @@ import KernelTransport from "./kernel-transport"; import type { ResultsCallback } from "./kernel-transport"; import InputView from "./input-view"; import { log, js_idx_to_char_idx } from "./utils"; -import type { Session } from "@jupyterlab/services"; +import type { Session, Kernel } from "@jupyterlab/services"; import type { Message } from "./hydrogen"; import type { KernelspecMetadata } from "@nteract/types"; @@ -12,7 +12,7 @@ export default class WSKernel extends KernelTransport { constructor( gatewayName: string, - kernelSpec: KernelspecMetadata, + kernelSpec: Kernel.ISpecModel | KernelspecMetadata, grammar: Grammar, session: Session.ISession ) { @@ -34,7 +34,7 @@ export default class WSKernel extends KernelTransport { await (this.session?.shutdown() ?? this.session.kernel?.shutdown()); } - restart(onRestarted: ((...args: Array) => any) | null | undefined) { + restart(onRestarted: () => void | null | undefined) { const future = this.session.kernel.restart(); future.then(() => { if (onRestarted) { diff --git a/lib/zmq-kernel.ts b/lib/zmq-kernel.ts index e5c9c7523..1eeeaa8b8 100644 --- a/lib/zmq-kernel.ts +++ b/lib/zmq-kernel.ts @@ -177,7 +177,7 @@ export default class ZMQKernel extends KernelTransport { _socketShutdown(restart: boolean | null | undefined = false) { const requestId = `shutdown_${v4()}`; - const message = this._createMessage("shutdown_request", requestId); + const message = _createMessage("shutdown_request", requestId); message.content = { restart, @@ -220,7 +220,7 @@ export default class ZMQKernel extends KernelTransport { log("ZMQKernel.execute:", code); const requestId = `execute_${v4()}`; - const message = this._createMessage("execute_request", requestId); + const message = _createMessage("execute_request", requestId); message.content = { code, @@ -237,7 +237,7 @@ export default class ZMQKernel extends KernelTransport { log("ZMQKernel.complete:", code); const requestId = `complete_${v4()}`; - const message = this._createMessage("complete_request", requestId); + const message = _createMessage("complete_request", requestId); message.content = { code, @@ -253,7 +253,7 @@ export default class ZMQKernel extends KernelTransport { log("ZMQKernel.inspect:", code, cursorPos); const requestId = `inspect_${v4()}`; - const message = this._createMessage("inspect_request", requestId); + const message = _createMessage("inspect_request", requestId); message.content = { code, @@ -267,7 +267,7 @@ export default class ZMQKernel extends KernelTransport { inputReply(input: string) { const requestId = `input_reply_${v4()}`; - const message = this._createMessage("input_reply", requestId); + const message = _createMessage("input_reply", requestId); message.content = { value: input, @@ -278,7 +278,7 @@ export default class ZMQKernel extends KernelTransport { onShellMessage(message: Message) { log("shell message:", message); - if (!this._isValidMessage(message)) { + if (!_isValidMessage(message)) { return; } @@ -297,7 +297,7 @@ export default class ZMQKernel extends KernelTransport { onStdinMessage(message: Message) { log("stdin message:", message); - if (!this._isValidMessage(message)) { + if (!_isValidMessage(message)) { return; } @@ -318,7 +318,7 @@ export default class ZMQKernel extends KernelTransport { onIOMessage(message: Message) { log("IO message:", message); - if (!this._isValidMessage(message)) { + if (!_isValidMessage(message)) { return; } @@ -341,56 +341,6 @@ export default class ZMQKernel extends KernelTransport { } } - _isValidMessage(message: Message) { - if (!message) { - log("Invalid message: null"); - return false; - } - - if (!message.content) { - log("Invalid message: Missing content"); - return false; - } - - if (message.content.execution_state === "starting") { - // Kernels send a starting status message with an empty parent_header - log("Dropped starting status IO message"); - return false; - } - - if (!message.parent_header) { - log("Invalid message: Missing parent_header"); - return false; - } - - if (!message.parent_header.msg_id) { - log("Invalid message: Missing parent_header.msg_id"); - return false; - } - - if (!message.parent_header.msg_type) { - log("Invalid message: Missing parent_header.msg_type"); - return false; - } - - if (!message.header) { - log("Invalid message: Missing header"); - return false; - } - - if (!message.header.msg_id) { - log("Invalid message: Missing header.msg_id"); - return false; - } - - if (!message.header.msg_type) { - log("Invalid message: Missing header.msg_type"); - return false; - } - - return true; - } - destroy() { log("ZMQKernel: destroy:", this); this.shutdown(); @@ -403,30 +353,80 @@ export default class ZMQKernel extends KernelTransport { this.stdinSocket.close(); super.destroy(); } +} + +function _isValidMessage(message: Message) { + if (!message) { + log("Invalid message: null"); + return false; + } - _getUsername() { - return ( - process.env.LOGNAME || - process.env.USER || - process.env.LNAME || - process.env.USERNAME - ); + if (!message.content) { + log("Invalid message: Missing content"); + return false; } - _createMessage(msgType: string, msgId: string = v4()) { - const message = { - header: { - username: this._getUsername(), - session: "00000000-0000-0000-0000-000000000000", - msg_type: msgType, - msg_id: msgId, - date: new Date(), - version: "5.0", - }, - metadata: {}, - parent_header: {}, - content: {}, - }; - return message; + if (message.content.execution_state === "starting") { + // Kernels send a starting status message with an empty parent_header + log("Dropped starting status IO message"); + return false; + } + + if (!message.parent_header) { + log("Invalid message: Missing parent_header"); + return false; } + + if (!message.parent_header.msg_id) { + log("Invalid message: Missing parent_header.msg_id"); + return false; + } + + if (!message.parent_header.msg_type) { + log("Invalid message: Missing parent_header.msg_type"); + return false; + } + + if (!message.header) { + log("Invalid message: Missing header"); + return false; + } + + if (!message.header.msg_id) { + log("Invalid message: Missing header.msg_id"); + return false; + } + + if (!message.header.msg_type) { + log("Invalid message: Missing header.msg_type"); + return false; + } + + return true; +} + +function _getUsername() { + return ( + process.env.LOGNAME || + process.env.USER || + process.env.LNAME || + process.env.USERNAME + ); +} + +function _createMessage(msgType: string, msgId: string = v4()) { + const message = { + header: { + username: _getUsername(), + session: "00000000-0000-0000-0000-000000000000", + msg_type: msgType, + msg_id: msgId, + date: new Date(), + version: "5.0", + }, + metadata: {}, + parent_header: {}, + content: {}, + }; + return message; } diff --git a/package.json b/package.json index c31361c22..a248110ff 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "build.test": "npm run build && npm run test", "test": "echo 'test without rebuilding' && atom --test spec", "clean": "shx rm -rf dist", - "dev": "npm run clean && npm run build -- --watch", + "dev": "npm run clean && tsc -p lib/tsconfig.json --watch", "build": "npm run clean && tsc -p lib/tsconfig.json || echo done", "build:services-docs": "markdox lib/services/index.js -o lib/services/README.md", "build:plugin-docs": "markdox lib/plugin-api/hydrogen-provider.js lib/plugin-api/hydrogen-kernel.js -o docs/PluginAPI.md", @@ -57,6 +57,7 @@ "atom": ">=1.28.0 <2.0.0" }, "dependencies": { + "@aminya/xmlhttprequest": "^1.8.2", "@babel/runtime-corejs2": "^7.0.0", "@jupyterlab/services": "^0.52.0", "@nteract/commutable": "^7.1.4", @@ -83,8 +84,7 @@ "styled-components": "^5.0.1", "tildify": "^2.0.0", "uuid": "^8.0.0", - "ws": "^7.0.0", - "xmlhttprequest": "^1.8.0" + "ws": "^7.0.0" }, "consumedServices": { "autocomplete.watchEditor": { @@ -125,14 +125,14 @@ "@types/styled-components": "^5.1.9", "@types/uuid": "^8.3.0", "@types/ws": "^7.4.1", - "atom-jasmine3-test-runner": "^5.2.2", + "atom-jasmine3-test-runner": "^5.2.5", "build-commit": "^0.1.4", "electron": "^6", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.6", - "eslint-config-atomic": "^1.14.0", + "eslint-config-atomic": "^1.14.4", "markdox": "^0.1.10", - "prettier-config-atomic": "^2.0.1", + "prettier-config-atomic": "^2.0.5", "react-test-renderer": "^16.0.0", "shx": "^0.3.3", "typescript": "^4.2.4" diff --git a/spec/components/display-spec.js b/spec/components/display-spec.jsx similarity index 100% rename from spec/components/display-spec.js rename to spec/components/display-spec.jsx diff --git a/spec/components/kernel-monitor-spec.js b/spec/components/kernel-monitor-spec.jsx similarity index 100% rename from spec/components/kernel-monitor-spec.js rename to spec/components/kernel-monitor-spec.jsx diff --git a/spec/components/output-area-spec.js b/spec/components/output-area-spec.jsx similarity index 100% rename from spec/components/output-area-spec.js rename to spec/components/output-area-spec.jsx diff --git a/spec/components/result-view/history-spec.js b/spec/components/result-view/history-spec.jsx similarity index 100% rename from spec/components/result-view/history-spec.js rename to spec/components/result-view/history-spec.jsx diff --git a/spec/components/result-view/plotly-spec.js b/spec/components/result-view/plotly-spec.jsx similarity index 100% rename from spec/components/result-view/plotly-spec.js rename to spec/components/result-view/plotly-spec.jsx diff --git a/spec/components/result-view/result-view-spec.js b/spec/components/result-view/result-view-spec.jsx similarity index 100% rename from spec/components/result-view/result-view-spec.js rename to spec/components/result-view/result-view-spec.jsx diff --git a/spec/services/consumed/status-bar-spec.js b/spec/services/consumed/status-bar-spec.jsx similarity index 100% rename from spec/services/consumed/status-bar-spec.js rename to spec/services/consumed/status-bar-spec.jsx