Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ deciding whether it fits your use.
Grouped by area rather than listed change by change. The detail is in `TESTING.md` and the commit
history.

**Web server**

- The web server binds the loopback interface by default. It used to listen on every interface, so
any machine on the same network reached an API that has no authentication and that lists, reads,
writes and deletes in the library. `-Dstudio.host` and `-Dstudio.port` override the address, and
the frontend no longer names a port anywhere: it uses whatever origin served the page.
- A port already in use is reported instead of leaving a running process that serves nothing.

**Device and transport**

- The partition search waits for the OS to mount the device instead of giving up after ten seconds,
Expand Down Expand Up @@ -137,6 +145,20 @@ this exists on top of. Licence, attribution and disclaimers are unchanged and re
fork can be rebased on upstream if it becomes active again; until then the changes above are
maintained here.

## Code from other forks

Parts of this fork come from other people's forks rather than from upstream. They are listed here
because the licence alone does not say who did the work.

**Configurable listen host and port** — from [@kairoh](https://github.com/kairoh)'s fork, commit
[`74f53cc`](https://github.com/kairoh/studio/commit/74f53cc57b70734e015f0cd31f036ca57ff3ea47)
("Configurable listen host and port", 2 April 2022), which predates that fork's move to Quarkus and
so applied to the same Vert.x code this fork still runs. Taken from it: reading the host and port
from configuration instead of hard-coding them, deriving the CORS pattern and the browser URL from
the host, and serving the whole web UI from relative URLs so the frontend stops naming a port. Not
taken from it: binding to the loopback by default, and reporting a failed bind — that commit keeps
`listen(port)`, which still accepts connections on every interface. Both projects are MPL-2.0.

---

The rest of this file is the upstream README, kept as it was except where it would say something
Expand Down
3 changes: 2 additions & 1 deletion TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Nothing here touches a device. Fixtures are synthesised in code; no device data

| Suite | Tests |
| --- | --- |
| Java, standard | **282**, 14 skipped — the opt-in FAT32 classes, and two link cases each of which only one platform can set up |
| Java, standard | **287**, 14 skipped — the opt-in FAT32 classes, and two link cases each of which only one platform can set up |
| Java, with `-Dstudio.test.fat32.root=<volume>` | last measured at **172** before the C6d-5 additions; not re-measured since, because it needs the volume mounted |
| JavaScript | **57** |

Expand Down Expand Up @@ -121,6 +121,7 @@ counts.
| When a conversion is proven to match its source, and every reason it is not | `ConversionVerificationTest` | `web-ui` module; **specifications** — only MATCH removes a confirmation; a path outside the library is refused rather than answered |
| Where a library operation may reach: direct children only, links refused, nominal cases intact | `LibraryPathConfinementTest` | `web-ui` module; **specifications** — converted from characterization once the confinement existed. The symbolic-link case is Linux-only and the junction case Windows-only |
| A conversion releases its source and its temporary even when the reader or writer throws | `ConversionStreamLifecycleTest` | `web-ui` module; **specifications** — asserts the consequence by deleting the work folder, so the three failure cases are Windows-only |
| Which interfaces the web server accepts connections on, and what an occupied port does | `ServerBindingTest` | `web-ui` module; **specifications** — deploys the real `MainVerticle` with `env=dev` and every path pointed at a temporary directory, so no device and no network are touched. The two cases needing a non-loopback address are skipped on a machine that has none. Deliberately silent on the log message and on the browser, which have no seam |

Web UI (`web-ui/javascript`, run by yarn):

Expand Down
5 changes: 5 additions & 0 deletions web-ui/javascript/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# `yarn start` serves the app from :3000 while the Java backend listens on :8080. The `proxy` entry
# in package.json forwards /api and /locales, but the CRA dev server does not proxy the SockJS
# connection, so the event bus is pointed straight at the backend here. In a packaged build this
# variable is unset and the origin the page was served from is used instead.
REACT_APP_EVENTBUS_ORIGIN=http://localhost:8080
1 change: 1 addition & 0 deletions web-ui/javascript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"author": "Marian MULLER REBEYROL",
"license": "MPL-2.0",
"private": true,
"proxy": "http://localhost:8080",
"dependencies": {
"@emotion/core": "^10.0.22",
"@emotion/styled": "^10.0.23",
Expand Down
2 changes: 1 addition & 1 deletion web-ui/javascript/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<head>
<meta charset="utf-8"/>
<title>STUdio - Story Teller Unleashed</title>
<link rel="icon" type="image/png" href="http://localhost:8080/favicon.png" />
<link rel="icon" type="image/png" href="/favicon.png" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
Expand Down
8 changes: 7 additions & 1 deletion web-ui/javascript/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,14 @@ class App extends React.Component {
// from inside a setState callback, one React tick later; the underlying client fires onopen
// at most once and only if it is already assigned, so a socket that opened during that tick
// left the application with no device handlers at all for the lifetime of the page.
//
// Derived from the page's own origin rather than relative, unlike the `fetch` calls in
// `services/`: sockjs-client rejects a URL with no host and no protocol (sockjs.js, "The
// URL '...' is invalid"), so '/eventbus' would throw at construction. The origin still
// follows whatever host and port served the page, which is the point.
console.log("Setting up vert.x event bus channel...");
const channel = createEventBusChannel('http://localhost:8080/eventbus', {
const eventBusOrigin = process.env.REACT_APP_EVENTBUS_ORIGIN || window.location.origin;
const channel = createEventBusChannel(eventBusOrigin + '/eventbus', {
onStateChange: state => this.onChannelStateChange(state)
});

Expand Down
2 changes: 1 addition & 1 deletion web-ui/javascript/src/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ i18n
.init({
fallbackLng: 'en',
backend: {
loadPath: 'http://localhost:8080/locales/{{lng}}/{{ns}}.json',
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
Expand Down
12 changes: 6 additions & 6 deletions web-ui/javascript/src/services/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
import {handleJsonOrError} from "../utils/fetch";

export const fetchDeviceInfos = () => {
return fetch('http://localhost:8080/api/device/infos')
return fetch('/api/device/infos')
.then(handleJsonOrError);
};

export const fetchDevicePacks = () => {
return fetch('http://localhost:8080/api/device/packs')
return fetch('/api/device/packs')
.then(handleJsonOrError);
};

export const addFromLibrary = (uuid, path) => {
return fetch('http://localhost:8080/api/device/addFromLibrary', {
return fetch('/api/device/addFromLibrary', {
method: "POST",
headers: { "Content-Type" : "application/json" },
body: JSON.stringify({uuid, path})
Expand All @@ -26,7 +26,7 @@ export const addFromLibrary = (uuid, path) => {
};

export const removeFromDevice = (uuid) => {
return fetch('http://localhost:8080/api/device/removeFromDevice', {
return fetch('/api/device/removeFromDevice', {
method: "POST",
headers: { "Content-Type" : "application/json" },
body: JSON.stringify({uuid})
Expand All @@ -35,7 +35,7 @@ export const removeFromDevice = (uuid) => {
};

export const reorderPacks = (uuids) => {
return fetch('http://localhost:8080/api/device/reorder', {
return fetch('/api/device/reorder', {
method: "POST",
headers: { "Content-Type" : "application/json" },
body: JSON.stringify({uuids})
Expand All @@ -44,7 +44,7 @@ export const reorderPacks = (uuids) => {
};

export const addToLibrary = (uuid, driver) => {
return fetch('http://localhost:8080/api/device/addToLibrary', {
return fetch('/api/device/addToLibrary', {
method: "POST",
headers: { "Content-Type" : "application/json" },
body: JSON.stringify({uuid, driver})
Expand Down
6 changes: 3 additions & 3 deletions web-ui/javascript/src/services/evergreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
import {handleJsonOrError} from "../utils/fetch";

export const fetchEvergreenInfos = () => {
return fetch('http://localhost:8080/api/evergreen/infos')
return fetch('/api/evergreen/infos')
.then(handleJsonOrError);
};

export const fetchEvergreenLatestRelease = () => {
return fetch('http://localhost:8080/api/evergreen/latest')
return fetch('/api/evergreen/latest')
.then(handleJsonOrError);
};

export const fetchEvergreenAnnounce = () => {
return fetch('http://localhost:8080/api/evergreen/announce')
return fetch('/api/evergreen/announce')
.then(handleJsonOrError);
};
14 changes: 7 additions & 7 deletions web-ui/javascript/src/services/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
import {handleJsonOrError} from "../utils/fetch";

export const fetchLibraryInfos = () => {
return fetch('http://localhost:8080/api/library/infos')
return fetch('/api/library/infos')
.then(handleJsonOrError);
};

export const fetchLibraryPacks = () => {
return fetch('http://localhost:8080/api/library/packs')
return fetch('/api/library/packs')
.then(handleJsonOrError);
};

export const downloadFromLibrary = async (uuid, path) => {
return await fetch('http://localhost:8080/api/library/download', {
return await fetch('/api/library/download', {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({uuid, path})
Expand All @@ -34,7 +34,7 @@ export const uploadToLibrary = async (uuid, path, packData, progressHandler) =>
console.log('xhr upload complete: ' + JSON.parse(xhr.responseText));
resolve(JSON.parse(xhr.responseText));
};
xhr.open('post', 'http://localhost:8080/api/library/upload', true);
xhr.open('post', '/api/library/upload', true);
let formData = new FormData();
formData.append("uuid", uuid);
formData.append("path", path);
Expand All @@ -44,7 +44,7 @@ export const uploadToLibrary = async (uuid, path, packData, progressHandler) =>
};

export const convertInLibrary = async (uuid, path, format, allowEnriched) => {
return await fetch('http://localhost:8080/api/library/convert', {
return await fetch('/api/library/convert', {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({uuid, path, format, allowEnriched})
Expand All @@ -53,7 +53,7 @@ export const convertInLibrary = async (uuid, path, format, allowEnriched) => {
};

export const removeFromLibrary = (path) => {
return fetch('http://localhost:8080/api/library/remove', {
return fetch('/api/library/remove', {
method: "POST",
headers: { "Content-Type" : "application/json" },
body: JSON.stringify({path})
Expand All @@ -69,7 +69,7 @@ export const removeFromLibrary = (path) => {
* this side has nothing to decide and nothing to get wrong.
*/
export const verifyConversion = async (sourcePath, convertedPath) => {
return await fetch('http://localhost:8080/api/library/verify-conversion', {
return await fetch('/api/library/verify-conversion', {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({sourcePath, convertedPath})
Expand Down
34 changes: 30 additions & 4 deletions web-ui/src/main/java/studio/webui/MainVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,22 @@ public void start() {
}


// Making the host and port configurable, and deriving the CORS pattern and the browser URL
// from them, comes from kairoh's fork — commit 74f53cc, "Configurable listen host and
// port" (2022-04-02), MPL-2.0 like this file. See "Code from other forks" in the README.
//
// Listen address. The loopback default is deliberate: this is a desktop application whose
// UI is served to a browser on the same machine, and the API below is unauthenticated —
// it lists, reads, writes and deletes in the user's library, and drives the device. Bound
// to every interface, as `listen(8080)` did, any host on the same network segment reached
// it. Overriding the host is possible, but it is now an explicit choice.
String host = System.getProperty("studio.host", "127.0.0.1");
int port = Integer.parseInt(System.getProperty("studio.port", "8080"));

Router router = Router.router(vertx);

// Handle cross-origin calls
router.route().handler(CorsHandler.create("http://localhost:.*")
router.route().handler(CorsHandler.create("http://" + host + ":.*")
.allowedMethods(Set.of(
HttpMethod.GET,
HttpMethod.POST
Expand Down Expand Up @@ -97,16 +109,30 @@ public void start() {
errorHandler.handle(ctx);
});

// Start HTTP server
vertx.createHttpServer().requestHandler(router).listen(8080);
// Start HTTP server. The handler is not optional: `listen()` without one discards the
// failure, and a port already in use then leaves a running process that serves nothing
// and says nothing. The browser is opened only once the socket is actually bound.
String url = "http://" + host + ":" + port;
vertx.createHttpServer().requestHandler(router).listen(port, host, ar -> {
if (ar.failed()) {
LOGGER.error("Failed to listen on " + host + ":" + port
+ " - the port may already be in use. Set -Dstudio.port to another one.",
ar.cause());
return;
}
LOGGER.info("Listening on " + url);
openInBrowser(url);
});
}

private void openInBrowser(String url) {
// Automatically open URL in browser, unless instructed otherwise
String openBrowser = System.getProperty("studio.open", "true");
if (Boolean.valueOf(openBrowser)) {
LOGGER.info("Opening URL in default browser...");
if (Desktop.isDesktopSupported()) {
try {
Desktop.getDesktop().browse(new URI("http://localhost:8080"));
Desktop.getDesktop().browse(new URI(url));
} catch (Exception e) {
LOGGER.error("Failed to open URL in default browser", e);
}
Expand Down
Loading
Loading