jupyterpack 0.2.0

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.
Files changed (60) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +79 -0
  3. package/lib/document/iframePanel.d.ts +7 -0
  4. package/lib/document/iframePanel.js +20 -0
  5. package/lib/document/jupyterpackDocWidget.d.ts +9 -0
  6. package/lib/document/jupyterpackDocWidget.js +16 -0
  7. package/lib/document/plugin.d.ts +2 -0
  8. package/lib/document/plugin.js +31 -0
  9. package/lib/document/widgetFactory.d.ts +23 -0
  10. package/lib/document/widgetFactory.js +60 -0
  11. package/lib/index.d.ts +2 -0
  12. package/lib/index.js +3 -0
  13. package/lib/pythonWidget/connectionManager.d.ts +18 -0
  14. package/lib/pythonWidget/connectionManager.js +27 -0
  15. package/lib/pythonWidget/kernelExecutor.d.ts +27 -0
  16. package/lib/pythonWidget/kernelExecutor.js +104 -0
  17. package/lib/pythonWidget/pythonWidget.d.ts +13 -0
  18. package/lib/pythonWidget/pythonWidget.js +22 -0
  19. package/lib/pythonWidget/pythonWidgetModel.d.ts +29 -0
  20. package/lib/pythonWidget/pythonWidgetModel.js +75 -0
  21. package/lib/sandpackWidget/sandpackFilesModel.d.ts +27 -0
  22. package/lib/sandpackWidget/sandpackFilesModel.js +114 -0
  23. package/lib/sandpackWidget/sandpackPanel.d.ts +16 -0
  24. package/lib/sandpackWidget/sandpackPanel.js +52 -0
  25. package/lib/swConnection/comm_manager.d.ts +6 -0
  26. package/lib/swConnection/comm_manager.js +46 -0
  27. package/lib/swConnection/connection_manager.d.ts +18 -0
  28. package/lib/swConnection/connection_manager.js +27 -0
  29. package/lib/swConnection/index.d.ts +3 -0
  30. package/lib/swConnection/index.js +68 -0
  31. package/lib/swConnection/sw.d.ts +1 -0
  32. package/lib/swConnection/sw.js +49 -0
  33. package/lib/token.d.ts +3 -0
  34. package/lib/token.js +2 -0
  35. package/lib/tools.d.ts +2 -0
  36. package/lib/tools.js +17 -0
  37. package/lib/type.d.ts +38 -0
  38. package/lib/type.js +9 -0
  39. package/package.json +199 -0
  40. package/src/document/iframePanel.ts +25 -0
  41. package/src/document/jupyterpackDocWidget.ts +19 -0
  42. package/src/document/plugin.ts +44 -0
  43. package/src/document/widgetFactory.ts +79 -0
  44. package/src/index.ts +4 -0
  45. package/src/pythonWidget/connectionManager.ts +43 -0
  46. package/src/pythonWidget/kernelExecutor.ts +140 -0
  47. package/src/pythonWidget/pythonWidget.ts +34 -0
  48. package/src/pythonWidget/pythonWidgetModel.ts +107 -0
  49. package/src/sandpackWidget/sandpackFilesModel.ts +141 -0
  50. package/src/sandpackWidget/sandpackPanel.ts +82 -0
  51. package/src/swConnection/comm_manager.ts +53 -0
  52. package/src/swConnection/connection_manager.ts +43 -0
  53. package/src/swConnection/index.ts +93 -0
  54. package/src/swConnection/sw.ts +57 -0
  55. package/src/token.ts +6 -0
  56. package/src/tools.ts +18 -0
  57. package/src/type.ts +44 -0
  58. package/style/base.css +51 -0
  59. package/style/index.css +1 -0
  60. package/style/index.js +1 -0
@@ -0,0 +1,114 @@
1
+ import { Signal } from '@lumino/signaling';
2
+ import { removePrefix } from '../tools';
3
+ export class SandpackFilesModel {
4
+ constructor(options) {
5
+ this._fileChanged = new Signal(this);
6
+ this._contentManager = options.contentsManager;
7
+ this._path = options.path;
8
+ this._contentManager.fileChanged.connect(this._onFileChanged, this);
9
+ }
10
+ async getAllFiles() {
11
+ if (!this._allFiles) {
12
+ const files = await this._contentManager.get(this._path, {
13
+ content: true
14
+ });
15
+ this._allFiles = await this.flattenDirectory(files);
16
+ }
17
+ return this._allFiles;
18
+ }
19
+ get fileChanged() {
20
+ return this._fileChanged;
21
+ }
22
+ async flattenDirectory(dirContent) {
23
+ const flatDict = {};
24
+ const content = dirContent.content;
25
+ for (const item of content) {
26
+ if (item.type === 'file') {
27
+ const pathWithoutRoot = this._removeRoot(item.path);
28
+ let itemContent = item;
29
+ if (!itemContent.content) {
30
+ itemContent = await this._getContent(item.path);
31
+ }
32
+ if (itemContent.mimetype === 'application/json' &&
33
+ typeof itemContent.content !== 'string') {
34
+ flatDict[pathWithoutRoot] = {
35
+ code: JSON.stringify(itemContent.content)
36
+ };
37
+ }
38
+ else {
39
+ flatDict[pathWithoutRoot] = { code: itemContent.content };
40
+ }
41
+ }
42
+ else if (item.type === 'directory') {
43
+ // If it's a directory, recursively flatten its content
44
+ const nestedContent = await this._getContent(item.path);
45
+ const nestedDict = await this.flattenDirectory(nestedContent);
46
+ Object.assign(flatDict, nestedDict);
47
+ }
48
+ }
49
+ return flatDict;
50
+ }
51
+ async _onFileChanged(sender, args) {
52
+ var _a, _b, _c, _d, _e;
53
+ switch (args.type) {
54
+ case 'save': {
55
+ if ((_a = args.newValue) === null || _a === void 0 ? void 0 : _a.path) {
56
+ const newContent = await this._getContent(args.newValue.path);
57
+ const pathWithoutRoot = this._removeRoot(args.newValue.path);
58
+ if (this._allFiles) {
59
+ this._allFiles[pathWithoutRoot] = {
60
+ code: newContent.content
61
+ };
62
+ }
63
+ }
64
+ break;
65
+ }
66
+ case 'delete': {
67
+ if ((_b = args.oldValue) === null || _b === void 0 ? void 0 : _b.path) {
68
+ const pathWithoutRoot = this._removeRoot(args.oldValue.path);
69
+ if (this._allFiles) {
70
+ delete this._allFiles[pathWithoutRoot];
71
+ }
72
+ }
73
+ break;
74
+ }
75
+ case 'rename': {
76
+ if (((_c = args.oldValue) === null || _c === void 0 ? void 0 : _c.path) && ((_d = args.newValue) === null || _d === void 0 ? void 0 : _d.path)) {
77
+ const oldPathWithoutRoot = this._removeRoot(args.oldValue.path);
78
+ const newPathWithoutRoot = this._removeRoot(args.newValue.path);
79
+ if (this._allFiles) {
80
+ this._allFiles[newPathWithoutRoot] =
81
+ this._allFiles[oldPathWithoutRoot];
82
+ delete this._allFiles[newPathWithoutRoot];
83
+ }
84
+ }
85
+ break;
86
+ }
87
+ case 'new': {
88
+ if ((_e = args.newValue) === null || _e === void 0 ? void 0 : _e.path) {
89
+ const newContent = await this._getContent(args.newValue.path);
90
+ const pathWithoutRoot = this._removeRoot(args.newValue.path);
91
+ if (this._allFiles) {
92
+ this._allFiles[pathWithoutRoot] = {
93
+ code: newContent.content
94
+ };
95
+ }
96
+ }
97
+ break;
98
+ }
99
+ default:
100
+ break;
101
+ }
102
+ if (this._allFiles) {
103
+ this._fileChanged.emit({
104
+ allFiles: this._allFiles
105
+ });
106
+ }
107
+ }
108
+ _removeRoot(path) {
109
+ return removePrefix(path, this._path);
110
+ }
111
+ _getContent(path) {
112
+ return this._contentManager.get(path, { content: true });
113
+ }
114
+ }
@@ -0,0 +1,16 @@
1
+ import { SandpackClient } from '@codesandbox/sandpack-client';
2
+ import { DocumentRegistry } from '@jupyterlab/docregistry';
3
+ import { Contents } from '@jupyterlab/services';
4
+ import { IFramePanel } from '../document/iframePanel';
5
+ import { SandpackFilesModel } from './sandpackFilesModel';
6
+ export declare class SandpackPanel extends IFramePanel {
7
+ constructor(options: {
8
+ context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
9
+ contentsManager: Contents.IManager;
10
+ });
11
+ init(localPath: string): Promise<void>;
12
+ connectSignals(filesModel: SandpackFilesModel, sandpackClient: SandpackClient): Promise<void>;
13
+ private _onFileChanged;
14
+ private _spClient?;
15
+ private _contentsManager;
16
+ }
@@ -0,0 +1,52 @@
1
+ import { loadSandpackClient } from '@codesandbox/sandpack-client';
2
+ import { IFramePanel } from '../document/iframePanel';
3
+ import { SandpackFilesModel } from './sandpackFilesModel';
4
+ export class SandpackPanel extends IFramePanel {
5
+ constructor(options) {
6
+ super();
7
+ this._contentsManager = options.contentsManager;
8
+ options.context.ready.then(async () => {
9
+ await this.init(options.context.localPath);
10
+ });
11
+ }
12
+ async init(localPath) {
13
+ const currentDir = localPath.split('/').slice(0, -1).join('/');
14
+ const filesModel = new SandpackFilesModel({
15
+ contentsManager: this._contentsManager,
16
+ path: currentDir
17
+ });
18
+ const allFiles = await filesModel.getAllFiles();
19
+ const options = {
20
+ showLoadingScreen: true,
21
+ showOpenInCodeSandbox: false
22
+ };
23
+ this._spClient = await loadSandpackClient(this._iframe, {
24
+ files: allFiles
25
+ }, options);
26
+ await this.connectSignals(filesModel, this._spClient);
27
+ }
28
+ async connectSignals(filesModel, sandpackClient) {
29
+ filesModel.fileChanged.connect(this._onFileChanged, this);
30
+ sandpackClient.listen(msg => {
31
+ switch (msg.type) {
32
+ case 'start': {
33
+ this.toggleSpinner(true);
34
+ break;
35
+ }
36
+ case 'success': {
37
+ this.toggleSpinner(false);
38
+ break;
39
+ }
40
+ default:
41
+ break;
42
+ }
43
+ });
44
+ }
45
+ _onFileChanged(sender, { allFiles }) {
46
+ if (this._spClient) {
47
+ this._spClient.updateSandbox({
48
+ files: allFiles
49
+ });
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,6 @@
1
+ export declare class CommManager {
2
+ constructor();
3
+ registerComm(instanceId: string, port: MessagePort): void;
4
+ generateResponse(request: Request): Promise<Response>;
5
+ private _commIds;
6
+ }
@@ -0,0 +1,46 @@
1
+ import { wrap, transfer } from 'comlink';
2
+ export class CommManager {
3
+ constructor() {
4
+ this._commIds = new Map();
5
+ }
6
+ registerComm(instanceId, port) {
7
+ const comm = wrap(port);
8
+ this._commIds.set(instanceId, comm);
9
+ }
10
+ async generateResponse(request) {
11
+ const url = new URL(request.url);
12
+ const urlPath = url.pathname;
13
+ const method = request.method;
14
+ const requestHeaders = {};
15
+ for (const pair of request.headers.entries()) {
16
+ if (!pair[0].startsWith('sec-ch-ua')) {
17
+ requestHeaders[pair[0]] = pair[1];
18
+ }
19
+ }
20
+ const params = url.searchParams.toString();
21
+ const pathAfterExtensionName = urlPath.split('/jupyterpack/static')[1];
22
+ const pathList = pathAfterExtensionName.split('/').filter(Boolean);
23
+ const instanceId = pathList[0];
24
+ const kernelClientId = pathList[2];
25
+ const comm = this._commIds.get(instanceId);
26
+ if (!comm) {
27
+ throw new Error('Missing comm');
28
+ }
29
+ const requestBody = request.body ? await request.arrayBuffer() : undefined;
30
+ const data = await comm.generateResponse({
31
+ kernelClientId,
32
+ urlPath,
33
+ method,
34
+ headers: requestHeaders,
35
+ requestBody: requestBody
36
+ ? transfer(requestBody, [requestBody])
37
+ : undefined,
38
+ params
39
+ });
40
+ if (data) {
41
+ const { headers, content, status_code } = data;
42
+ return new Response(content, { status: status_code, headers });
43
+ }
44
+ return await fetch(url, { method });
45
+ }
46
+ }
@@ -0,0 +1,18 @@
1
+ import { IConnectionManager, IDict, IKernelExecutor } from '../type';
2
+ export declare class ConnectionManager implements IConnectionManager {
3
+ instanceId: string;
4
+ constructor(instanceId: string);
5
+ registerConnection(kernelExecutor: IKernelExecutor): Promise<{
6
+ instanceId: string;
7
+ kernelClientId: string;
8
+ }>;
9
+ generateResponse(options: {
10
+ kernelClientId: string;
11
+ urlPath: string;
12
+ method: string;
13
+ headers: IDict;
14
+ requestBody?: ArrayBuffer;
15
+ params?: string;
16
+ }): Promise<IDict | null>;
17
+ private _kernelExecutors;
18
+ }
@@ -0,0 +1,27 @@
1
+ import { UUID } from '@lumino/coreutils';
2
+ export class ConnectionManager {
3
+ constructor(instanceId) {
4
+ this.instanceId = instanceId;
5
+ this._kernelExecutors = new Map();
6
+ }
7
+ async registerConnection(kernelExecutor) {
8
+ const uuid = UUID.uuid4();
9
+ this._kernelExecutors.set(uuid, kernelExecutor);
10
+ return { instanceId: this.instanceId, kernelClientId: uuid };
11
+ }
12
+ async generateResponse(options) {
13
+ const { urlPath, kernelClientId, method, params, requestBody, headers } = options;
14
+ const executor = this._kernelExecutors.get(kernelClientId);
15
+ if (!executor) {
16
+ return null;
17
+ }
18
+ const response = await executor.getResponse({
19
+ urlPath,
20
+ method,
21
+ params,
22
+ headers,
23
+ requestBody
24
+ });
25
+ return response;
26
+ }
27
+ }
@@ -0,0 +1,3 @@
1
+ import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
+ import { IConnectionManager } from '../type';
3
+ export declare const swPlugin: JupyterFrontEndPlugin<IConnectionManager>;
@@ -0,0 +1,68 @@
1
+ import { PageConfig, URLExt } from '@jupyterlab/coreutils';
2
+ import { UUID } from '@lumino/coreutils';
3
+ import { expose } from 'comlink';
4
+ import { IConnectionManagerToken } from '../token';
5
+ import { MessageAction } from '../type';
6
+ import { ConnectionManager } from './connection_manager';
7
+ const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
8
+ const SCOPE = `${fullLabextensionsUrl}/jupyterpack/static`;
9
+ async function initServiceWorker() {
10
+ if (!('serviceWorker' in navigator)) {
11
+ console.error('Cannot start extension without service worker');
12
+ return;
13
+ }
14
+ const fullWorkerUrl = `${SCOPE}/service-worker.js`;
15
+ try {
16
+ const reg = await navigator.serviceWorker.register(fullWorkerUrl);
17
+ if (!reg) {
18
+ console.error('Missing service worker registration');
19
+ return;
20
+ }
21
+ await reg.update();
22
+ if (reg.installing) {
23
+ const sw = reg.installing || reg.waiting;
24
+ sw.onstatechange = () => {
25
+ if (sw.state === 'installed') {
26
+ window.location.reload();
27
+ }
28
+ };
29
+ }
30
+ if (reg.active) {
31
+ return reg.active;
32
+ }
33
+ console.log('Service worker newly registered', await navigator.serviceWorker.getRegistration(fullWorkerUrl));
34
+ return reg.active;
35
+ }
36
+ catch (e) {
37
+ console.error('Failed to register service worker', e);
38
+ return;
39
+ }
40
+ }
41
+ function createPingFrame() {
42
+ const iframe = document.createElement('iframe');
43
+ iframe.style.display = 'none';
44
+ iframe.src = URLExt.join(SCOPE, '__jupyterpack__', 'ping.html');
45
+ document.body.appendChild(iframe);
46
+ }
47
+ export const swPlugin = {
48
+ id: 'jupyterpack:service-worker-plugin',
49
+ description: 'jupyterpack service worker plugin',
50
+ autoStart: true,
51
+ provides: IConnectionManagerToken,
52
+ activate: async (app) => {
53
+ console.log('Activating jupyterpack service worker');
54
+ const serviceWorker = await initServiceWorker();
55
+ if (!serviceWorker) {
56
+ throw new Error('Failed to register the Service Worker, please make sure to use a browser that supports this feature.');
57
+ }
58
+ const instanceId = UUID.uuid4();
59
+ const { port1: mainToServiceWorker, port2: serviceWorkerToMain } = new MessageChannel();
60
+ const connectionManager = new ConnectionManager(instanceId);
61
+ expose(connectionManager, mainToServiceWorker);
62
+ serviceWorker.postMessage({ type: MessageAction.INIT, data: { instanceId } }, [serviceWorkerToMain]);
63
+ setTimeout(() => {
64
+ createPingFrame();
65
+ }, 10000);
66
+ return connectionManager;
67
+ }
68
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ // import { expose } from 'comlink';
2
+ import { MessageAction } from '../type';
3
+ import { CommManager } from './comm_manager';
4
+ const COMM_MANAGER = new CommManager();
5
+ /**
6
+ * Install event listeners
7
+ */
8
+ self.addEventListener('install', onInstall);
9
+ self.addEventListener('activate', onActivate);
10
+ self.addEventListener('fetch', onFetch);
11
+ self.addEventListener('message', onMessage);
12
+ /**
13
+ * Handle installation
14
+ */
15
+ async function onInstall(event) {
16
+ await self.skipWaiting();
17
+ }
18
+ /**
19
+ * Handle activation.
20
+ */
21
+ async function onActivate(event) {
22
+ event.waitUntil(self.clients.claim());
23
+ }
24
+ /**
25
+ * Handle fetching a single resource.
26
+ */
27
+ async function onFetch(event) {
28
+ const url = event.request.url;
29
+ if (url.endsWith('__jupyterpack__/ping')) {
30
+ return event.respondWith(new Response('pong'));
31
+ }
32
+ if (url.endsWith('__jupyterpack__/ping.html')) {
33
+ return;
34
+ }
35
+ event.respondWith(COMM_MANAGER.generateResponse(event.request));
36
+ }
37
+ function onMessage(msg) {
38
+ const { type, data } = msg.data;
39
+ switch (type) {
40
+ case MessageAction.INIT: {
41
+ const { instanceId } = data;
42
+ const serviceWorkerToMain = msg.ports[0];
43
+ COMM_MANAGER.registerComm(instanceId, serviceWorkerToMain);
44
+ break;
45
+ }
46
+ default:
47
+ break;
48
+ }
49
+ }
package/lib/token.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ import { IConnectionManager } from './type';
3
+ export declare const IConnectionManagerToken: Token<IConnectionManager>;
package/lib/token.js ADDED
@@ -0,0 +1,2 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ export const IConnectionManagerToken = new Token('jupyterpack:connection-manager');
package/lib/tools.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function removePrefix(path: string, prefix: string): string;
2
+ export declare function arrayBufferToBase64(buffer: ArrayBuffer): string;
package/lib/tools.js ADDED
@@ -0,0 +1,17 @@
1
+ export function removePrefix(path, prefix) {
2
+ if (path.startsWith(prefix)) {
3
+ return path.slice(prefix.length);
4
+ }
5
+ // If the prefix doesn't match, return the original path
6
+ return path;
7
+ }
8
+ export function arrayBufferToBase64(buffer) {
9
+ let binary = '';
10
+ const bytes = new Uint8Array(buffer);
11
+ const chunkSize = 32768; // process in chunks for large buffers
12
+ for (let i = 0; i < bytes.length; i += chunkSize) {
13
+ const chunk = bytes.subarray(i, i + chunkSize);
14
+ binary += String.fromCharCode(...chunk);
15
+ }
16
+ return btoa(binary);
17
+ }
package/lib/type.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { KernelMessage } from '@jupyterlab/services';
2
+ import { IDisposable } from '@lumino/disposable';
3
+ export interface IDict<T = any> {
4
+ [key: string]: T;
5
+ }
6
+ export declare enum JupyterPackFramework {
7
+ REACT = "react",
8
+ DASH = "dash"
9
+ }
10
+ export interface IJupyterPackFileFormat {
11
+ entry: string;
12
+ framework: JupyterPackFramework;
13
+ name?: string;
14
+ metadata?: IDict;
15
+ }
16
+ export declare enum MessageAction {
17
+ INIT = "INIT"
18
+ }
19
+ export interface IKernelExecutorParams {
20
+ method: string;
21
+ urlPath: string;
22
+ headers: IDict;
23
+ params?: string;
24
+ requestBody?: ArrayBuffer;
25
+ }
26
+ export interface IKernelExecutor extends IDisposable {
27
+ getResponse(options: IKernelExecutorParams): Promise<IDict>;
28
+ executeCode(code: KernelMessage.IExecuteRequestMsg['content']): Promise<string>;
29
+ }
30
+ export interface IConnectionManager {
31
+ registerConnection(kernelExecutor: IKernelExecutor): Promise<{
32
+ instanceId: string;
33
+ kernelClientId: string;
34
+ }>;
35
+ generateResponse(option: {
36
+ kernelClientId: string;
37
+ } & IKernelExecutorParams): Promise<IDict | null>;
38
+ }
package/lib/type.js ADDED
@@ -0,0 +1,9 @@
1
+ export var JupyterPackFramework;
2
+ (function (JupyterPackFramework) {
3
+ JupyterPackFramework["REACT"] = "react";
4
+ JupyterPackFramework["DASH"] = "dash";
5
+ })(JupyterPackFramework || (JupyterPackFramework = {}));
6
+ export var MessageAction;
7
+ (function (MessageAction) {
8
+ MessageAction["INIT"] = "INIT";
9
+ })(MessageAction || (MessageAction = {}));