forked from jupyter-server/jupyter_server
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Stability Kernel Status banner websocket (jupyter-server#232)
* when websocket closes, reopen a new one * stabilize kernel blocked websocket too * handle websockets closed on purpose * add new telemetry listener to consolidate some logic * create separate websockets for each telemetry event * turn telemetry listener into singleton and single websocket * add a test for telemetry listerner * add test for reconnecting when a window is not hidden * skip test that came from cookiecutter
- Loading branch information
Showing
5 changed files
with
191 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { PageConfig, URLExt } from '@jupyterlab/coreutils'; | ||
|
||
/* | ||
* TelemetryListener | ||
* | ||
* Singleton class that opens a websocket that listens to the | ||
* `/subscribe` endpoint, attaches callbacks to specific events | ||
* that will be broadcast across this channel, and fires these callbacks | ||
* functions when the event occurs. | ||
*/ | ||
export class TelemetryListener { | ||
private static instance: TelemetryListener; | ||
callbacks: Record<string, Function> = {}; | ||
url: string; | ||
|
||
private constructor() { | ||
this.url = | ||
PageConfig.getOption('studioSubscribeURL') || | ||
URLExt.join(PageConfig.getWsUrl(), 'subscribe'); | ||
} | ||
/** | ||
* Get an instance of the singleton. If an instance doesn't | ||
* already exists, create new one. | ||
* @returns TelemetryListener | ||
*/ | ||
static getInstance(): TelemetryListener { | ||
if (!TelemetryListener.instance) { | ||
TelemetryListener.instance = new TelemetryListener(); | ||
} | ||
return TelemetryListener.instance; | ||
} | ||
/** | ||
* Connect a (long-lived) websocket to the | ||
* `/subscribe` endpoint. | ||
*/ | ||
public connect() { | ||
if (document.hidden) { | ||
setTimeout(() => { | ||
this.connect(); | ||
}, 1000); | ||
return; | ||
} | ||
const ws = new WebSocket(this.url); | ||
let listener = this; | ||
ws.onclose = function (event: CloseEvent) { | ||
// If the websocket is closed on purpose, | ||
// don't create a new one. | ||
if (event.code === 1000) { | ||
return; | ||
} | ||
/* istanbul ignore next */ | ||
setTimeout(() => { | ||
listener.connect(); | ||
}, 1000); | ||
}; | ||
for (let name in this.callbacks) { | ||
let callback = this.callbacks[name]; | ||
ws.addEventListener('message', event => { | ||
callback(event); | ||
}); | ||
} | ||
} | ||
/** | ||
* Attach a callback to an event that will be triggered when | ||
* that event comes across the `/subscribe` endpoint. | ||
* | ||
* @param {string} eventId - Telemtry event ID that should trigger this callback | ||
* @param {Function} callback - The function to execute when a trigger happens. | ||
*/ | ||
addCallback(eventId: string, callback: Function) { | ||
const listener = function (event: MessageEvent) { | ||
const data = JSON.parse(event.data); | ||
if (data.__schema__ == eventId) { | ||
callback(data); | ||
} | ||
}; | ||
this.callbacks[eventId] = listener; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import WS from 'jest-websocket-mock'; | ||
|
||
import { TelemetryListener } from '../src/telemetrylistener'; | ||
import { PageConfig } from '@jupyterlab/coreutils'; | ||
import { sleep } from '@jupyterlab/testutils'; | ||
|
||
describe('telemetry listener', () => { | ||
it('should test that the telemetry listener websocket listens to the /subscribe endpoint', () => { | ||
const url = 'ws://localhost:5555'; | ||
PageConfig.setOption('studioSubscribeURL', url); | ||
const server = new WS(url, { jsonProtocol: true }); | ||
|
||
let triggered = false; | ||
|
||
/* Define a dummy callback function to verify it gets called */ | ||
const callback = function () { | ||
triggered = true; | ||
}; | ||
|
||
let listener = TelemetryListener.getInstance(); | ||
listener.addCallback('test-event', callback); | ||
listener.connect(); | ||
|
||
server.send({ __schema__: 'test-event' }); | ||
expect(triggered).toEqual(true); | ||
|
||
server.close(); | ||
WS.clean(); | ||
}); | ||
|
||
it('should test that the telemetry listener reconnects when the window is not hidden', async () => { | ||
Object.defineProperty(document, 'hidden', { | ||
value: true, | ||
configurable: true | ||
}); | ||
|
||
const url = 'ws://localhost:5555'; | ||
PageConfig.setOption('studioSubscribeURL', url); | ||
|
||
const server = new WS(url, { jsonProtocol: true }); | ||
let listener = TelemetryListener.getInstance(); | ||
|
||
let triggered = false; | ||
listener.addCallback('test-event', () => { | ||
triggered = true; | ||
}); | ||
listener.connect(); | ||
server.send({ __schema__: 'test-event' }); | ||
expect(triggered).toEqual(false); | ||
|
||
Object.defineProperty(document, 'hidden', { | ||
value: false, | ||
configurable: true | ||
}); | ||
await sleep(2000); | ||
|
||
server.send({ __schema__: 'test-event' }); | ||
expect(triggered).toEqual(true); | ||
|
||
server.close(); | ||
WS.clean(); | ||
}); | ||
}); |