@stlite/desktop 0.21.1 → 0.21.3

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/README.md CHANGED
@@ -1 +1,60 @@
1
1
  # `@stlite/desktop`
2
+
3
+ Convert your [Streamlit](https://streamlit.io/) application into a desktop app with [stlite](https://github.com/whitphx/stlite) runtime, a [Pyodide](https://pyodide.org/)-based Wasm-port of Streamlit.
4
+
5
+ ## How to create a Streamlit desktop app
6
+
7
+ 1. Create the following `package.json` file to start a new NPM project. Edit the `name` field.
8
+ ```json
9
+ {
10
+ "name": "xxx",
11
+ "version": "0.1.0",
12
+ "main": "./build/electron/main.js",
13
+ "scripts": {
14
+ "dump": "dump-stlite-desktop-artifacts",
15
+ "serve": "NODE_ENV=\"production\" electron .",
16
+ "pack": "electron-builder --dir",
17
+ "dist": "electron-builder",
18
+ "postinstall": "electron-builder install-app-deps"
19
+ },
20
+ "build": {
21
+ "files": ["build/**/*"],
22
+ "directories": {
23
+ "buildResources": "assets"
24
+ }
25
+ },
26
+ "devDependencies": {
27
+ "@stlite/desktop": "^0.21.0",
28
+ "electron": "^22.0.0",
29
+ "electron-builder": "^23.6.0"
30
+ }
31
+ }
32
+ ```
33
+ 2. Run `npm install` or `yarn install`.
34
+ 3. Create `streamlit_app` directory.
35
+ - Any directory name can be used here.
36
+ 4. Create `streamlit_app/streamlit_app.py`.
37
+ - Change the directory name if you used a different name in the previous step.
38
+ 5. Write your Streamlit app code in `streamlit_app/streamlit_app.py`.
39
+ - The file name `streamlit_app.py` is not configurable now.
40
+ 6. Optionally, you can add more contents in the directory, including `pages/*.py` for multi-page apps, any data files, and so on.
41
+ 7. Run `npm run dump streamlit_app` or `yarn dump streamlit_app`. The command argument `streamlit_app` is the directory name of the Streamlit app you have created in the previous steps. Change it if you used a different name.
42
+ - If installing some packages is needed, pass the package names following the directory name like `npm run dump streamlit_app <PackageName1> ... <PackageNameN>`.
43
+ - The `-r` option like the `pip` command is also available to specify the text files listing the package names to install like `npm run dump streamlit_app -- -r requirements.txt` (NPM) or `yarn dump streamlit_app -r requirements.txt` (Yarn). Note that if you are using NPM, you need to add `--` before options such as `-r` in the `run` command ([ref](https://stackoverflow.com/questions/43046885/what-does-do-when-running-an-npm-command)).
44
+ - This `dump` command creates `./build` directory. It includes
45
+ - The stlite bare app files.
46
+ - `streamlit_app` directory copied from the one you created in the previous steps.
47
+ - `site-packages-snapshot.tar.gz` that includes the installed package files.
48
+ 8. Run `npm run serve` or `yarn serve` for preview.
49
+ - This command is just a wrapper of `electron` command as you can see at the `"scripts"` field in the `package.json`. It launches Electron and starts the app with `./build/electron/main.js`, which is specified at the `"main"` field in the `package.json`.
50
+ 9. Run `npm run dist` or `yarn dist` for packaging.
51
+ - This command bundles the `./build` directory created in the step above into application files (`.app`, `.exe`, `.dmg` etc.) in the `./dist` directory. To customize the built app, e.g. setting the icon, follow the [`electron-builder`](https://www.electron.build/) instructions.
52
+
53
+ ## Use the latest version of Electron
54
+
55
+ To make your app secure, be sure to use the latest version of Electron.
56
+ This is [announced](https://www.electronjs.org/docs/latest/tutorial/security#16-use-a-current-version-of-electron) as one of the security best practices in the Electron document too.
57
+
58
+ ## Limitations
59
+
60
+ - Navigation to external resources like `st.markdown("[link](https://streamlit.io/)")` does not work for security. See https://github.com/whitphx/stlite/pull/445 and let us know if you have use cases where you have to use such external links.
@@ -18,31 +18,48 @@ const createWindow = () => {
18
18
  height: 720,
19
19
  webPreferences: {
20
20
  preload: path.join(__dirname, "preload.js"),
21
+ sandbox: true, // https://www.electronjs.org/docs/latest/tutorial/security#4-enable-process-sandboxing
21
22
  },
22
23
  });
24
+ const indexUrl = electron_1.app.isPackaged || process.env.NODE_ENV === "production"
25
+ ? "file:///index.html"
26
+ : "http://localhost:3000/";
27
+ // Check the IPC sender in every callback below,
28
+ // following the security best practice, "17. Validate the sender of all IPC messages."
29
+ // https://www.electronjs.org/docs/latest/tutorial/security#17-validate-the-sender-of-all-ipc-messages
30
+ const isValidIpcSender = (frame) => {
31
+ return frame.url === indexUrl;
32
+ };
23
33
  electron_1.ipcMain.handle("readSitePackagesSnapshot", (ev) => {
34
+ if (!isValidIpcSender(ev.senderFrame)) {
35
+ throw new Error("Invalid IPC sender");
36
+ }
24
37
  // This archive file has to be created by ./bin/dump_snapshot.ts
25
38
  const archiveFilePath = path.resolve(__dirname, "../site-packages-snapshot.tar.gz");
26
39
  return fsPromises.readFile(archiveFilePath);
27
40
  });
28
41
  electron_1.ipcMain.handle("readStreamlitAppDirectory", async (ev) => {
42
+ if (!isValidIpcSender(ev.senderFrame)) {
43
+ throw new Error("Invalid IPC sender");
44
+ }
29
45
  const streamlitAppDir = path.resolve(__dirname, "../streamlit_app");
30
46
  return (0, file_1.walkRead)(streamlitAppDir);
31
47
  });
32
- if (electron_1.app.isPackaged || process.env.NODE_ENV === "production") {
33
- // Use .loadURL() with an absolute URL based on "/" instead of .loadFile()
34
- // because absolute URLs with the file:// scheme will be resolved
35
- // to absolute file paths based on the special handler
36
- // registered through `interceptFileProtocol` below.
37
- mainWindow.loadURL("file:///index.html");
38
- }
39
- else {
40
- mainWindow.loadURL("http://localhost:3000");
41
- }
48
+ // Even when the entrypoint is a local file like the production build,
49
+ // we use .loadURL() with an absolute URL with the `file://` schema
50
+ // instead of passing a file path to .loadFile()
51
+ // because absolute URLs with the file:// scheme will be resolved
52
+ // to absolute file paths based on the special handler
53
+ // registered through `interceptFileProtocol` below.
54
+ mainWindow.loadURL(indexUrl);
42
55
  if (!electron_1.app.isPackaged) {
43
56
  mainWindow.webContents.openDevTools();
44
57
  }
45
58
  };
59
+ // Enable process sandboxing globally (https://www.electronjs.org/docs/latest/tutorial/sandbox#enabling-the-sandbox-globally),
60
+ // following the security best practice, "4. Enable process sandboxing."
61
+ // https://www.electronjs.org/docs/latest/tutorial/security#4-enable-process-sandboxing
62
+ electron_1.app.enableSandbox();
46
63
  // This method will be called when Electron has finished
47
64
  // initialization and is ready to create browser windows.
48
65
  // Some APIs can only be used after this event occurs.
@@ -77,3 +94,36 @@ electron_1.app.on("window-all-closed", () => {
77
94
  if (process.platform !== "darwin")
78
95
  electron_1.app.quit();
79
96
  });
97
+ electron_1.app.on("web-contents-created", (event, contents) => {
98
+ // Intercepts webView creation events and forbid all,
99
+ // following the security best practice, "12. Verify WebView options before creation."
100
+ // https://www.electronjs.org/docs/latest/tutorial/security#12-verify-webview-options-before-creation
101
+ contents.on("will-attach-webview", (event, webPreferences, params) => {
102
+ // Cancels all webView creation request
103
+ event.preventDefault();
104
+ });
105
+ // Intercepts navigation and forbid all,
106
+ // following the security best practice, "13. Disable or limit navigation."
107
+ // https://www.electronjs.org/docs/latest/tutorial/security#13-disable-or-limit-navigation
108
+ contents.on("will-navigate", (event, navigationUrl) => {
109
+ console.debug("will-navigate", navigationUrl);
110
+ event.preventDefault();
111
+ });
112
+ // Limit new windows creation,
113
+ // following the security best practice, "14. Disable or limit creation of new windows."
114
+ // https://www.electronjs.org/docs/latest/tutorial/security#14-disable-or-limit-creation-of-new-windows
115
+ contents.setWindowOpenHandler(({ url }) => {
116
+ console.error("Opening a new window is not allowed.");
117
+ // TODO: Implement `isSafeForExternalOpen()` below with a configurable allowed list.
118
+ // We'll ask the operating system
119
+ // to open this event's url in the default browser.
120
+ // DON'T pass an arbitrary URL to `shell.openExternal()` here
121
+ // as advised at "15. Do not use shell.openExternal with untrusted content."
122
+ // if (isSafeForExternalOpen(url)) {
123
+ // setImmediate(() => {
124
+ // shell.openExternal(url)
125
+ // })
126
+ // }
127
+ return { action: "deny" };
128
+ });
129
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stlite/desktop",
3
- "version": "0.21.1",
3
+ "version": "0.21.3",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "/",
6
6
  "main": "./build/electron/main.js",
@@ -57,8 +57,8 @@
57
57
  },
58
58
  "devDependencies": {
59
59
  "@craco/craco": "^6.1.2",
60
- "@stlite/common": "^0.21.1",
61
- "@stlite/kernel": "^0.21.1",
60
+ "@stlite/common": "^0.21.3",
61
+ "@stlite/kernel": "^0.21.3",
62
62
  "@testing-library/react": "^11.2.7",
63
63
  "@testing-library/user-event": "^13.1.9",
64
64
  "@types/jest": "^26.0.19",
@@ -66,8 +66,8 @@
66
66
  "@types/react": "^17.0.7",
67
67
  "@types/react-dom": "^17.0.5",
68
68
  "@types/yargs": "^17.0.13",
69
- "electron": "20.2.0",
70
- "electron-builder": "^23.3.3",
69
+ "electron": "^22.0.0",
70
+ "electron-builder": "^23.6.0",
71
71
  "electron-reload": "^2.0.0-alpha.1",
72
72
  "esbuild": "^0.16.6",
73
73
  "raw-loader": "^4.0.2",