@fails-components/jupyter-launcher 0.0.1-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js ADDED
@@ -0,0 +1,631 @@
1
+ import { ILabStatus } from '@jupyterlab/application';
2
+ import { IDocumentManager } from '@jupyterlab/docmanager';
3
+ import { ServerConnection } from '@jupyterlab/services';
4
+ import { PageConfig, URLExt } from '@jupyterlab/coreutils';
5
+ import { INotebookShell } from '@jupyter-notebook/application';
6
+ import { NotebookActions } from '@jupyterlab/notebook';
7
+ import { Token } from '@lumino/coreutils';
8
+ import { Signal } from '@lumino/signaling';
9
+ export const IFailsLauncherInfo = new Token('@fails-components/jupyter-fails:IFailsLauncherInfo', 'A service to communicate with FAILS.');
10
+ let screenShotPatchInstalled = false;
11
+ const installScreenShotPatches = () => {
12
+ if (screenShotPatchInstalled) {
13
+ return;
14
+ }
15
+ // Monkey patch the HTMLCanvasObject
16
+ const oldGetContext = HTMLCanvasElement.prototype.getContext;
17
+ // @ts-expect-error type do not matter
18
+ HTMLCanvasElement.prototype.getContext = function (contexttype, contextAttributes) {
19
+ if (contexttype === 'webgl' || contexttype === 'webgl2') {
20
+ const newcontext = { ...contextAttributes };
21
+ newcontext.preserveDrawingBuffer = true;
22
+ return oldGetContext.apply(this, [contexttype, newcontext]);
23
+ }
24
+ return oldGetContext.apply(this, [contexttype, contextAttributes]);
25
+ };
26
+ screenShotPatchInstalled = true;
27
+ };
28
+ // Monkey patch fetch, e.g. for GDPR compliance
29
+ let fetchPatchInstalled = false;
30
+ const installFetchPatches = ({ allowedSites = undefined, proxySites = undefined, proxyURL }) => {
31
+ if (fetchPatchInstalled) {
32
+ return;
33
+ }
34
+ const allowedOrigins = [location.origin, ...(allowedSites || [])];
35
+ const allowedOriginsString = allowedOrigins
36
+ .map(el => "'" + new URL(el).origin + "'")
37
+ .join(',');
38
+ const proxySitesString = (proxySites || [])
39
+ .map(el => "'" + new URL(el).origin + "'")
40
+ .join(',');
41
+ // Monkey patch fetch
42
+ const oldFetch = globalThis.fetch;
43
+ globalThis.fetch = async (url, options = {}) => {
44
+ const urlObj = url instanceof URL
45
+ ? url
46
+ : new URL(url instanceof Request ? url.url : url, location.href);
47
+ if (allowedOrigins.includes(urlObj.origin)) {
48
+ return oldFetch(url instanceof Request ? url : urlObj, options);
49
+ }
50
+ if (proxySites && proxySites.includes(urlObj.origin)) {
51
+ // rewrite the URL and response
52
+ const resURL = proxyURL + urlObj.hostname + urlObj.pathname;
53
+ if (url instanceof Request) {
54
+ const request = url;
55
+ const newRequest = new Request(resURL, {
56
+ method: request.method,
57
+ headers: request.headers,
58
+ body: request.body,
59
+ credentials: request.credentials,
60
+ mode: request.mode,
61
+ integrity: request.integrity,
62
+ keepalive: request.keepalive,
63
+ referrerPolicy: request.referrerPolicy,
64
+ cache: request.cache,
65
+ redirect: request.redirect,
66
+ referrer: request.referrer,
67
+ signal: request.signal
68
+ });
69
+ return oldFetch(newRequest, options);
70
+ }
71
+ else {
72
+ urlObj.href = resURL;
73
+ return oldFetch(urlObj, options);
74
+ }
75
+ }
76
+ console.log('alien fetch URL:', urlObj.href);
77
+ return new Response('Blocked domain, access forbidden', {
78
+ status: 403,
79
+ statusText: 'Forbidden',
80
+ headers: { 'Content-Type': 'text/plain' }
81
+ });
82
+ };
83
+ const oldImportScripts = globalThis.importScripts;
84
+ globalThis.importScripts = (...args) => {
85
+ const newargs = args.map(url => {
86
+ const urlObj = new URL(url, location.href);
87
+ if (allowedOrigins.includes(urlObj.origin)) {
88
+ return url;
89
+ }
90
+ if (proxySites && proxySites.includes(urlObj.origin)) {
91
+ return proxyURL + urlObj.hostname + urlObj.pathname;
92
+ }
93
+ throw new Error('Script is from blocked URL');
94
+ });
95
+ return oldImportScripts(...newargs);
96
+ };
97
+ const oldWorker = Worker;
98
+ const NewWorker = function (script, options) {
99
+ const scriptURL = script instanceof URL ? script : new URL(script, location.href);
100
+ if (!allowedOrigins.includes(scriptURL.origin)) {
101
+ console.log('Creating worker from blocked origin:', scriptURL.origin);
102
+ return;
103
+ }
104
+ console.log('Tap into creating worker:', scriptURL.href);
105
+ const injectPrefix = `(function() {
106
+ const allowedOrigins = [ ${allowedOriginsString} ];
107
+ const proxySites = [ ${proxySitesString} ];
108
+ const proxyURL = '${proxyURL}';
109
+ const oldFetch = globalThis.fetch;
110
+ globalThis.fetch = async function(url, options = {}) {
111
+ const urlObj = url instanceof URL ? url : new URL(url instanceof Request ? url.url : url, location.href);
112
+ if (allowedOrigins.includes(urlObj.origin)) {
113
+ return oldFetch(url instanceof Request ? url : urlObj, options);
114
+ }
115
+ if (proxySites && proxySites.includes(urlObj.origin)) {
116
+ // rewrite the URL and response
117
+ const resURL = proxyURL + urlObj.hostname + urlObj.pathname;
118
+ console.log('proxy url debug', resURL, urlObj.href);
119
+ if (url instanceof Request) {
120
+ const request = url;
121
+ const newRequest = new Request(resURL, {
122
+ method: request.method,
123
+ headers: request.headers,
124
+ body: request.body,
125
+ credentials: request.credentials,
126
+ mode: request.mode,
127
+ integrity: request.integrity,
128
+ keepalive: request.keepalive,
129
+ referrerPolicy: request.referrerPolicy,
130
+ cache: request.cache,
131
+ redirect: request.redirect,
132
+ referrer: request.referrer,
133
+ signal: request.signal
134
+ });
135
+ console.log('proxy request debug', newRequest, request);
136
+ return oldFetch(newRequest, options);
137
+ } else {
138
+ urlObj.href = resURL;
139
+ return oldFetch(urlObj, options);
140
+ }
141
+ }
142
+ console.log('alien fetch URL worker:', urlObj.href);
143
+ return new Response('Blocked domain, access forbidden',{
144
+ status: 403,
145
+ statusText: 'Forbidden',
146
+ headers: { 'Content-Type': 'text/plain' }
147
+ });
148
+ };
149
+ const oldImportScripts = globalThis.importScripts;
150
+ globalThis.importScripts = (...args) => {
151
+ const newargs = args.map(url => {
152
+ const urlObj = new URL(url, location.href);
153
+ if (allowedOrigins.includes(urlObj.origin)) {
154
+ return url;
155
+ }
156
+ if (proxySites && proxySites.includes(urlObj.origin)) {
157
+ return proxyURL + urlObj.hostname + urlObj.pathname;
158
+ }
159
+ throw new Error('Script is from blocked URL');
160
+ });
161
+ return oldImportScripts(...newargs);
162
+ };
163
+ Object.defineProperty(globalThis, 'location', {
164
+ value: new URL('${scriptURL.href}'),
165
+ writable: false,
166
+ configurable: false,
167
+ enumerable: true,
168
+ });
169
+ })();`;
170
+ const inject = injectPrefix +
171
+ ((options === null || options === void 0 ? void 0 : options.type) === 'module'
172
+ ? `
173
+ import('${scriptURL.href}').catch(error => {
174
+ console.error('Can not load module patching Worker:', error);
175
+ });`
176
+ : `
177
+ importScripts('${scriptURL.href}')
178
+ `);
179
+ const blob = new Blob([inject], { type: 'application/javascript' });
180
+ const url = URL.createObjectURL(blob);
181
+ const newWorker = new oldWorker(url, options);
182
+ newWorker.addEventListener('message', () => {
183
+ URL.revokeObjectURL(url);
184
+ }, { once: true });
185
+ return newWorker;
186
+ };
187
+ NewWorker.prototype = oldWorker.prototype;
188
+ NewWorker.prototype.constructor = NewWorker;
189
+ globalThis.Worker = NewWorker;
190
+ fetchPatchInstalled = true;
191
+ };
192
+ class FailsLauncherInfo {
193
+ constructor(options) {
194
+ var _a, _b;
195
+ this._inLectureChanged = new Signal(this);
196
+ this._selectedAppidChanged = new Signal(this);
197
+ this._remoteUpdateMessageArrived = new Signal(this);
198
+ this._appletSizes = {};
199
+ this._appletSizesChanged = new Signal(this);
200
+ this._appletSizesProxy = new Proxy(this._appletSizes, {
201
+ get: (target, property) => {
202
+ if (typeof property !== 'symbol') {
203
+ return target[property];
204
+ }
205
+ },
206
+ set: (target, property, value) => {
207
+ if (typeof property !== 'symbol') {
208
+ if (target[property] !== value) {
209
+ target[property] = value;
210
+ this._appletSizesChanged.emit(target);
211
+ }
212
+ return true;
213
+ }
214
+ return false;
215
+ }
216
+ });
217
+ this._inLecture = (_a = options === null || options === void 0 ? void 0 : options.inLecture) !== null && _a !== void 0 ? _a : false;
218
+ this._selectedAppid = (_b = options === null || options === void 0 ? void 0 : options.selectedAppid) !== null && _b !== void 0 ? _b : undefined;
219
+ }
220
+ get inLectureChanged() {
221
+ return this._inLectureChanged;
222
+ }
223
+ get inLecture() {
224
+ return this._inLecture;
225
+ }
226
+ set inLecture(value) {
227
+ if (value === this._inLecture) {
228
+ return;
229
+ }
230
+ this._inLecture = value;
231
+ if (value === false && this._selectedAppid) {
232
+ this._selectedAppid = undefined;
233
+ this._selectedAppidChanged.emit(undefined);
234
+ }
235
+ this._inLectureChanged.emit(value);
236
+ }
237
+ get selectedAppid() {
238
+ return this._selectedAppid;
239
+ }
240
+ get selectedAppidChanged() {
241
+ return this._selectedAppidChanged;
242
+ }
243
+ set selectedAppid(appid) {
244
+ if (appid === this._selectedAppid) {
245
+ return;
246
+ }
247
+ this._selectedAppid = appid;
248
+ if (!this._inLecture && typeof appid !== 'undefined') {
249
+ this._inLecture = true;
250
+ this._inLectureChanged.emit(true);
251
+ }
252
+ else if (this._inLecture && typeof appid === 'undefined') {
253
+ this._inLecture = false;
254
+ this._inLectureChanged.emit(false);
255
+ }
256
+ this._selectedAppidChanged.emit(appid);
257
+ }
258
+ get appletSizes() {
259
+ return this._appletSizesProxy;
260
+ }
261
+ get appletSizesChanged() {
262
+ return this._appletSizesChanged;
263
+ }
264
+ get updateMessageArrived() {
265
+ return this._updateMessageArrived;
266
+ }
267
+ set updateMessageArrived(updateMessageArrived) {
268
+ this._updateMessageArrived = updateMessageArrived;
269
+ }
270
+ get remoteUpdateMessageArrived() {
271
+ return this._remoteUpdateMessageArrived;
272
+ }
273
+ receiveRemoteUpdateMessage(message) {
274
+ this._remoteUpdateMessageArrived.emit(message);
275
+ }
276
+ }
277
+ function activateFailsLauncher(app, docManager, status, shell) {
278
+ // parts taken from repl-extension
279
+ const { /* commands, */ serviceManager, started } = app;
280
+ Promise.all([started, serviceManager.ready]).then(async () => {
281
+ /* commands.execute('notebook:create-new', {
282
+ kernelId: undefined,
283
+ kernelName: undefined
284
+ }); */
285
+ // TODO select kernel and replace with content
286
+ });
287
+ // TODO steer with messages
288
+ const { docRegistry } = app;
289
+ const failsLauncherInfo = new FailsLauncherInfo();
290
+ let currentDocWidget;
291
+ const serverSettings = app.serviceManager.serverSettings;
292
+ const licensesUrl = URLExt.join(PageConfig.getBaseUrl(), PageConfig.getOption('licensesUrl')) +
293
+ '/';
294
+ // Install Messagehandler
295
+ if (!window.failsCallbacks) {
296
+ window.failsCallbacks = {};
297
+ }
298
+ let senderOrigin;
299
+ const _failsCallbacks = window.failsCallbacks;
300
+ _failsCallbacks.postMessageToFails = (message, transfer) => {
301
+ if (typeof senderOrigin !== 'undefined') {
302
+ window.parent.postMessage(message, senderOrigin, transfer);
303
+ }
304
+ };
305
+ status.dirtySignal.connect((sender, dirty) => {
306
+ _failsCallbacks.postMessageToFails({
307
+ task: 'docDirty',
308
+ dirty
309
+ });
310
+ });
311
+ failsLauncherInfo.reportMetadata = metadata => {
312
+ _failsCallbacks.postMessageToFails({
313
+ task: 'reportMetadata',
314
+ metadata
315
+ });
316
+ };
317
+ failsLauncherInfo.inLectureChanged.connect((sender, inLecture) => {
318
+ if (shell !== null) {
319
+ shell.menu.setHidden(inLecture);
320
+ }
321
+ });
322
+ failsLauncherInfo.appletSizesChanged.connect((sender, appletSizes) => {
323
+ _failsCallbacks.postMessageToFails({
324
+ task: 'reportFailsAppletSizes',
325
+ appletSizes
326
+ });
327
+ });
328
+ let interceptorActivated = false;
329
+ const sendInterceptorUpdate = (sender, message) => {
330
+ _failsCallbacks.postMessageToFails({
331
+ task: 'sendInterceptorUpdate',
332
+ ...message
333
+ });
334
+ };
335
+ window.addEventListener('message', (event) => {
336
+ var _a;
337
+ // TODO identify the embedding page.
338
+ if (typeof senderOrigin === 'undefined') {
339
+ senderOrigin = event.origin;
340
+ }
341
+ // handle FAILS control messages
342
+ switch (event.data.type) {
343
+ case 'loadJupyter':
344
+ {
345
+ const loadJupyterInfo = event.data;
346
+ failsLauncherInfo.inLecture =
347
+ (_a = loadJupyterInfo.inLecture) !== null && _a !== void 0 ? _a : failsLauncherInfo.inLecture;
348
+ docManager.autosave = false; // do not autosave
349
+ if (loadJupyterInfo.installScreenShotPatches) {
350
+ installScreenShotPatches();
351
+ }
352
+ if (loadJupyterInfo.installGDPRProxy) {
353
+ installFetchPatches(loadJupyterInfo.installGDPRProxy);
354
+ }
355
+ // TODO send fileData to contents together with filename, and wait for fullfillment
356
+ // may be use a promise for fullfillment, e.g. pass a resolve
357
+ // afterwards we load the file or new file into to the contexts
358
+ // we may also send license information
359
+ _failsCallbacks.callContents({
360
+ task: 'loadFile',
361
+ fileData: loadJupyterInfo.fileData,
362
+ fileName: loadJupyterInfo.fileName
363
+ })
364
+ .then(() => {
365
+ // ok the file is placed inside the file system now load it into the app
366
+ const kernel = {
367
+ name: loadJupyterInfo.kernelName || 'python' // 'xpython' for xeus
368
+ };
369
+ const defaultFactory = docRegistry.defaultWidgetFactory(loadJupyterInfo.fileName).name;
370
+ const factory = defaultFactory;
371
+ currentDocWidget = docManager.open(loadJupyterInfo.fileName, factory, kernel, {
372
+ ref: '_noref'
373
+ });
374
+ if (loadJupyterInfo.appid) {
375
+ failsLauncherInfo.selectedAppid = loadJupyterInfo.appid;
376
+ }
377
+ let rerunAfterKernelStart = loadJupyterInfo.rerunAtStartup;
378
+ if (typeof currentDocWidget !== 'undefined') {
379
+ const notebookPanel = currentDocWidget;
380
+ notebookPanel.sessionContext.statusChanged.connect((context, status) => {
381
+ _failsCallbacks.postMessageToFails({
382
+ task: 'reportKernelStatus',
383
+ status
384
+ });
385
+ if (status === 'idle' && rerunAfterKernelStart) {
386
+ console.log('Run all cells after startup');
387
+ const { context, content } = notebookPanel;
388
+ const cells = content.widgets;
389
+ NotebookActions.runCells(content, cells, context.sessionContext)
390
+ .then(() => {
391
+ console.log('Run all cells after startup finished');
392
+ })
393
+ .catch(error => {
394
+ console.log('Run all cells after startup error', error);
395
+ });
396
+ rerunAfterKernelStart = false;
397
+ }
398
+ });
399
+ }
400
+ })
401
+ .catch(error => {
402
+ console.log('Problem task load file', error);
403
+ });
404
+ }
405
+ break;
406
+ case 'saveJupyter':
407
+ {
408
+ const saveJupyter = event.data;
409
+ if (typeof currentDocWidget === 'undefined') {
410
+ _failsCallbacks.postMessageToFails({
411
+ requestId: event.data.requestId,
412
+ task: 'saveJupyter',
413
+ error: 'No document loaded'
414
+ });
415
+ break;
416
+ }
417
+ const context = docManager.contextForWidget(currentDocWidget);
418
+ if (typeof context === 'undefined') {
419
+ _failsCallbacks.postMessageToFails({
420
+ requestId: event.data.requestId,
421
+ task: 'saveJupyter',
422
+ error: 'No document context'
423
+ });
424
+ break;
425
+ }
426
+ context
427
+ .save()
428
+ .then(() => {
429
+ // ok it was save to our virtual disk
430
+ return _failsCallbacks.callContents({
431
+ task: 'savedFile',
432
+ fileName: saveJupyter.fileName
433
+ });
434
+ })
435
+ .then(({ fileData }) => {
436
+ _failsCallbacks.postMessageToFails({
437
+ requestId: event.data.requestId,
438
+ task: 'saveJupyter',
439
+ fileData
440
+ });
441
+ })
442
+ .catch((error) => {
443
+ _failsCallbacks.postMessageToFails({
444
+ requestId: event.data.requestId,
445
+ task: 'saveJupyter',
446
+ error: error.toString()
447
+ });
448
+ });
449
+ }
450
+ break;
451
+ case 'activateApp':
452
+ {
453
+ const activateApp = event.data;
454
+ if (activateApp.inLecture) {
455
+ failsLauncherInfo.selectedAppid = activateApp.appid;
456
+ }
457
+ else {
458
+ failsLauncherInfo.inLecture = false;
459
+ }
460
+ _failsCallbacks.postMessageToFails({
461
+ requestId: event.data.requestId,
462
+ task: 'activateApp'
463
+ });
464
+ }
465
+ break;
466
+ case 'screenshotApp':
467
+ {
468
+ const screenshotApp = event.data;
469
+ const notebookPanel = currentDocWidget;
470
+ if (!(typeof notebookPanel['takeAppScreenshot'] === 'function')) {
471
+ _failsCallbacks.postMessageToFails({
472
+ requestId: event.data.requestId,
473
+ task: 'screenshotApp',
474
+ error: 'Take App Screenshot unsupported'
475
+ });
476
+ }
477
+ const screenShotTaker = notebookPanel;
478
+ screenShotTaker
479
+ .takeAppScreenshot({ dpi: screenshotApp.dpi })
480
+ .then(async (screenshot) => {
481
+ var _a, _b;
482
+ if (screenshot) {
483
+ const data = await screenshot.arrayBuffer();
484
+ (_a = _failsCallbacks.postMessageToFails) === null || _a === void 0 ? void 0 : _a.call(_failsCallbacks, {
485
+ requestId: event.data.requestId,
486
+ task: 'screenshotApp',
487
+ screenshot: { data, type: screenshot.type }
488
+ }, [data]); // TODO add transferable
489
+ }
490
+ else {
491
+ (_b = _failsCallbacks.postMessageToFails) === null || _b === void 0 ? void 0 : _b.call(_failsCallbacks, {
492
+ requestId: event.data.requestId,
493
+ task: 'screenshotApp',
494
+ error: 'Screenshot failed?'
495
+ });
496
+ }
497
+ })
498
+ .catch((error) => {
499
+ console.log('Screenshot error', error);
500
+ _failsCallbacks.postMessageToFails({
501
+ requestId: event.data.requestId,
502
+ task: 'screenshotApp',
503
+ error: error.toString()
504
+ });
505
+ });
506
+ }
507
+ break;
508
+ case 'activateInterceptor':
509
+ {
510
+ const activateInterceptor = event.data;
511
+ if (interceptorActivated !== activateInterceptor.activate &&
512
+ failsLauncherInfo.updateMessageArrived) {
513
+ if (!interceptorActivated) {
514
+ failsLauncherInfo.updateMessageArrived.connect(sendInterceptorUpdate);
515
+ interceptorActivated = true;
516
+ }
517
+ else {
518
+ failsLauncherInfo.updateMessageArrived.disconnect(sendInterceptorUpdate);
519
+ interceptorActivated = false;
520
+ }
521
+ }
522
+ _failsCallbacks.postMessageToFails({
523
+ requestId: event.data.requestId,
524
+ task: 'activateInterceptor'
525
+ });
526
+ }
527
+ break;
528
+ case 'receiveInterceptorUpdate':
529
+ {
530
+ const receiveInterceptorUpdate = event.data;
531
+ const { path, mime, state } = receiveInterceptorUpdate;
532
+ const launcherInfo = failsLauncherInfo;
533
+ launcherInfo.receiveRemoteUpdateMessage({ path, mime, state });
534
+ _failsCallbacks.postMessageToFails({
535
+ requestId: event.data.requestId,
536
+ task: 'receiveInterceptorUpdate'
537
+ });
538
+ }
539
+ break;
540
+ case 'restartKernelAndRerunCells':
541
+ {
542
+ if (typeof currentDocWidget === 'undefined') {
543
+ _failsCallbacks.postMessageToFails({
544
+ requestId: event.data.requestId,
545
+ task: 'restartKernelAndRerunCell',
546
+ error: 'No document loaded'
547
+ });
548
+ break;
549
+ }
550
+ const notebookPanel = currentDocWidget;
551
+ const { context, content } = notebookPanel;
552
+ const cells = content.widgets;
553
+ console.log('rerun kernel hook');
554
+ notebookPanel.sessionContext
555
+ .restartKernel()
556
+ .then(async () => {
557
+ await NotebookActions.runCells(content, cells, context.sessionContext);
558
+ _failsCallbacks.postMessageToFails({
559
+ requestId: event.data.requestId,
560
+ task: 'restartKernelAndRerunCell',
561
+ success: true
562
+ });
563
+ })
564
+ .catch((error) => {
565
+ _failsCallbacks.postMessageToFails({
566
+ requestId: event.data.requestId,
567
+ task: 'restartKernelAndRerunCell',
568
+ error: error.toString()
569
+ });
570
+ });
571
+ }
572
+ break;
573
+ case 'getLicenses':
574
+ {
575
+ ServerConnection.makeRequest(licensesUrl, {}, serverSettings)
576
+ .then(async (response) => {
577
+ const json = await response.json();
578
+ _failsCallbacks.postMessageToFails({
579
+ requestId: event.data.requestId,
580
+ task: 'getLicenses',
581
+ licenses: json
582
+ });
583
+ })
584
+ .catch(error => {
585
+ _failsCallbacks.postMessageToFails({
586
+ requestId: event.data.requestId,
587
+ task: 'getLicenses',
588
+ error: error.toString()
589
+ });
590
+ });
591
+ }
592
+ break;
593
+ }
594
+ });
595
+ app.started.then(async () => {
596
+ if (window.parent) {
597
+ window.parent.postMessage({
598
+ task: 'appLoaded'
599
+ }, '*' // this is relatively safe, as we only say we are ready
600
+ );
601
+ }
602
+ if (shell) {
603
+ // we have a notebook
604
+ shell.collapseTop();
605
+ if (failsLauncherInfo.inLecture) {
606
+ const menuWrapper = shell['_menuWrapper'];
607
+ menuWrapper.hide();
608
+ // const main = shell['_main'] as SplitViewNotebookPanel;
609
+ // main.toolbar.hide();
610
+ }
611
+ }
612
+ });
613
+ return failsLauncherInfo;
614
+ }
615
+ const failsLauncher = {
616
+ id: '@fails-components/jupyter-fails:launcher',
617
+ description: 'Configures the notebooks application over messages',
618
+ autoStart: true,
619
+ activate: activateFailsLauncher,
620
+ provides: IFailsLauncherInfo,
621
+ requires: [IDocumentManager, ILabStatus],
622
+ optional: [INotebookShell]
623
+ };
624
+ /**
625
+ * Initialization data for the @fails-components/jupyter-launcher extension.
626
+ */
627
+ const plugins = [
628
+ // all JupyterFrontEndPlugins
629
+ failsLauncher
630
+ ];
631
+ export default plugins;