mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-09-16 14:51:37 +02:00
Make the web dev server work from another machine (#665)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
48889b6550
commit
a822cc93d4
@@ -29,6 +29,23 @@ const iconsDir = normalizePath(
|
|||||||
*/
|
*/
|
||||||
const yaakTarget = process.env.YAAK_TARGET === "web" ? "web" : "desktop";
|
const yaakTarget = process.env.YAAK_TARGET === "web" ? "web" : "desktop";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where `yaak-web` is listening, taken from the same variable that put it there.
|
||||||
|
*
|
||||||
|
* A wildcard bind is an instruction about what the server accepts, not an address
|
||||||
|
* to dial, so it becomes loopback here — the dev server and the send server share
|
||||||
|
* a machine.
|
||||||
|
*/
|
||||||
|
function sendServerUrl(): string {
|
||||||
|
const bind = process.env.YAAK_WEB_BIND?.trim();
|
||||||
|
if (!bind) return "http://127.0.0.1:9227";
|
||||||
|
const port = bind.slice(bind.lastIndexOf(":") + 1);
|
||||||
|
const host = bind.slice(0, bind.lastIndexOf(":"));
|
||||||
|
const dialable =
|
||||||
|
!host || host === "0.0.0.0" || host === "[::]" || host === "::" ? "127.0.0.1" : host;
|
||||||
|
return `http://${dialable}:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(async () => {
|
export default defineConfig(async () => {
|
||||||
return {
|
return {
|
||||||
@@ -94,8 +111,28 @@ export default defineConfig(async () => {
|
|||||||
},
|
},
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
server: {
|
server: {
|
||||||
|
// `HOST` names the interface, as it does most places: unset leaves Vite on
|
||||||
|
// loopback, `0.0.0.0` exposes it for reaching the dev server from another
|
||||||
|
// device.
|
||||||
|
host: process.env.HOST,
|
||||||
|
// Vite refuses a `Host` it does not recognise, which stops a page on a name the
|
||||||
|
// attacker controls from rebinding that name here and driving `/v1` as its own
|
||||||
|
// origin. Addresses are allowed already; only names need listing, so reaching
|
||||||
|
// this as `dev-box.example` means naming it. Deliberately not widened to "any
|
||||||
|
// host when exposed": the proxy below leads to an unauthenticated sender.
|
||||||
|
allowedHosts: process.env.ALLOWED_HOSTS?.split(",")
|
||||||
|
.map((h) => h.trim())
|
||||||
|
.filter(Boolean),
|
||||||
port: parseInt(process.env.YAAK_CLIENT_DEV_PORT ?? process.env.YAAK_DEV_PORT ?? "1420", 10),
|
port: parseInt(process.env.YAAK_CLIENT_DEV_PORT ?? process.env.YAAK_DEV_PORT ?? "1420", 10),
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
|
// A web dev server is one origin, the way the built app is: `/v1` is passed
|
||||||
|
// through to `yaak-web` rather than the tab being told to call it directly.
|
||||||
|
// That is one address to open instead of two, no CORS in the loop, and a
|
||||||
|
// dev build that sends exactly the way a production build does.
|
||||||
|
proxy:
|
||||||
|
yaakTarget === "web"
|
||||||
|
? { "/v1": { target: sendServerUrl(), changeOrigin: true } }
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
envPrefix: ["VITE_", "TAURI_"],
|
envPrefix: ["VITE_", "TAURI_"],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ docker run -p 8080:8080 \
|
|||||||
anything strangers can reach — see [What it refuses](#what-it-refuses-and-why).
|
anything strangers can reach — see [What it refuses](#what-it-refuses-and-why).
|
||||||
Turn it on for an instance on your own network, where calling the API on the
|
Turn it on for an instance on your own network, where calling the API on the
|
||||||
next machine is the whole point. Note that "private" is relative to the
|
next machine is the whole point. Note that "private" is relative to the
|
||||||
*container*: `127.0.0.1` is the container itself, and reaching the Docker
|
_container_: `127.0.0.1` is the container itself, and reaching the Docker
|
||||||
host means `host.docker.internal` (or `--network host`).
|
host means `host.docker.internal` (or `--network host`).
|
||||||
- **`YAAK_WEB_RATE_LIMIT_PER_MINUTE`** defaults to 120 sends per client IP,
|
- **`YAAK_WEB_RATE_LIMIT_PER_MINUTE`** defaults to 120 sends per client IP,
|
||||||
which suits a public instance and not a team of your own; `0` disables it.
|
which suits a public instance and not a team of your own; `0` disables it.
|
||||||
@@ -72,9 +72,15 @@ cargo run -p yaak-web
|
|||||||
YAAK_TARGET=web npm run dev --workspace @yaakapp/yaak-client
|
YAAK_TARGET=web npm run dev --workspace @yaakapp/yaak-client
|
||||||
```
|
```
|
||||||
|
|
||||||
A dev build looks for the server at `http://127.0.0.1:9227` (the Vite server is a
|
The dev server passes `/v1` through to this binary, so a dev build sends to its
|
||||||
different origin and serves no `/v1`); a production build sends to its own
|
own origin exactly like a production build does — one address to open, and no
|
||||||
origin unless `VITE_YAAK_WEB_URL` was set when it was built.
|
CORS in the loop. `YAAK_WEB_BIND` moves this server and the dev server follows
|
||||||
|
it. A production build also sends to its own origin, unless `VITE_YAAK_WEB_URL`
|
||||||
|
was set when it was built.
|
||||||
|
|
||||||
|
Reaching the dev server from another machine is `HOST=0.0.0.0`. Vite allows
|
||||||
|
addresses but not names, so opening it as a hostname also needs
|
||||||
|
`ALLOWED_HOSTS=that-name`.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -82,7 +88,7 @@ Every flag has a `YAAK_WEB_*` environment variable, so a container needs no
|
|||||||
arguments; `--help` lists them all.
|
arguments; `--help` lists them all.
|
||||||
|
|
||||||
| Flag | Default | What |
|
| Flag | Default | What |
|
||||||
| --- | --- | --- |
|
| -------------------------- | ---------------- | --------------------------------------------------------------------------------------------- |
|
||||||
| `--serve` | off | Also serve a built web client from this directory, on the same origin. |
|
| `--serve` | off | Also serve a built web client from this directory, on the same origin. |
|
||||||
| `--bind` | `127.0.0.1:9227` | Listen address. The image sets `0.0.0.0:8080`. |
|
| `--bind` | `127.0.0.1:9227` | Listen address. The image sets `0.0.0.0:8080`. |
|
||||||
| `--allow-private-networks` | off | Allow sends to loopback, private and link-local addresses. |
|
| `--allow-private-networks` | off | Allow sends to loopback, private and link-local addresses. |
|
||||||
@@ -194,7 +200,7 @@ The reply is `application/x-ndjson`, one JSON frame per line, in the order thing
|
|||||||
happened:
|
happened:
|
||||||
|
|
||||||
| `type` | When | Carries |
|
| `type` | When | Carries |
|
||||||
| --- | --- | --- |
|
| ---------- | ----------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||||
| `event` | as the engine produces them | one timeline event, in the desktop's `http_response_event.event` shape |
|
| `event` | as the engine produces them | one timeline event, in the desktop's `http_response_event.event` shape |
|
||||||
| `response` | once, when the final hop's headers arrive | status, all headers, request headers as sent, remote address, HTTP version, timing |
|
| `response` | once, when the final hop's headers arrive | status, all headers, request headers as sent, remote address, HTTP version, timing |
|
||||||
| `body` | as the body is read | a decompressed chunk, base64 |
|
| `body` | as the body is read | a decompressed chunk, base64 |
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ model layer — `yaak-models`, SQLite included — runs compiled to wasm inside
|
|||||||
worker the tab talks to, so a browser stores exactly what a desktop install
|
worker the tab talks to, so a browser stores exactly what a desktop install
|
||||||
stores, migrations and all.
|
stores, migrations and all.
|
||||||
|
|
||||||
Select it at build time and run the frontend alone:
|
Select it at build time. `vp run web:dev` does that and starts the send server
|
||||||
|
alongside the frontend:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
YAAK_TARGET=web npm run dev --workspace @yaakapp/yaak-client
|
vp run web:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
The flag resolves `@yaakapp-internal/platform` to `../index.web.ts`, which
|
The flag resolves `@yaakapp-internal/platform` to `../index.web.ts`, which
|
||||||
@@ -31,7 +32,7 @@ tab (index.ts, commands.ts) ──MessagePort──▶ worker.ts ──▶ @yaak
|
|||||||
```
|
```
|
||||||
|
|
||||||
| File | What it is |
|
| File | What it is |
|
||||||
| --- | --- |
|
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `index.ts` | The `Platform` implementation. |
|
| `index.ts` | The `Platform` implementation. |
|
||||||
| `commands.ts` | The command table: model commands forward to the worker; the rest is fixed answers and refusals-with-a-reason. |
|
| `commands.ts` | The command table: model commands forward to the worker; the rest is fixed answers and refusals-with-a-reason. |
|
||||||
| `connection.ts` | A tab's end of the wire: request/response over a `MessagePort`, event delivery, and the tab's identity (`label`). |
|
| `connection.ts` | A tab's end of the wire: request/response over a `MessagePort`, event delivery, and the tab's identity (`label`). |
|
||||||
@@ -70,7 +71,7 @@ Behaviours worth knowing before changing anything:
|
|||||||
store's echo handling is unchanged.
|
store's echo handling is unchanged.
|
||||||
- **Cascade rules, duplicate naming, id generation, serde defaults, and the
|
- **Cascade rules, duplicate naming, id generation, serde defaults, and the
|
||||||
lazy first-run bootstrap are all the Rust code's.** Nothing about what a
|
lazy first-run bootstrap are all the Rust code's.** Nothing about what a
|
||||||
model *is* is decided in TypeScript.
|
model _is_ is decided in TypeScript.
|
||||||
- **Persistence is `relaxed-idb`**: SQLite pages live in IndexedDB, writes land
|
- **Persistence is `relaxed-idb`**: SQLite pages live in IndexedDB, writes land
|
||||||
in memory and flush shortly after. A tab closing mid-flush loses at most the
|
in memory and flush shortly after. A tab closing mid-flush loses at most the
|
||||||
last few writes.
|
last few writes.
|
||||||
@@ -83,7 +84,7 @@ Behaviours worth knowing before changing anything:
|
|||||||
### Implemented (32)
|
### Implemented (32)
|
||||||
|
|
||||||
| Group | Commands |
|
| Group | Commands |
|
||||||
| --- | --- |
|
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Models | `models_workspace_models`, `models_upsert`, `models_delete`, `models_duplicate`, `models_get_settings`, `models_get_graphql_introspection`, `models_upsert_graphql_introspection`, `models_grpc_events`, `models_websocket_events` |
|
| Models | `models_workspace_models`, `models_upsert`, `models_delete`, `models_duplicate`, `models_get_settings`, `models_get_graphql_introspection`, `models_upsert_graphql_introspection`, `models_grpc_events`, `models_websocket_events` |
|
||||||
| Sending | `cmd_send_http_request` (through the Yaak server; see below) |
|
| Sending | `cmd_send_http_request` (through the Yaak server; see below) |
|
||||||
| App | `cmd_metadata`, `cmd_get_workspace_meta`, `cmd_default_headers`, `cmd_get_themes`, `cmd_check_for_updates`, `cmd_dismiss_notification`, `cmd_plugin_init_errors` |
|
| App | `cmd_metadata`, `cmd_get_workspace_meta`, `cmd_default_headers`, `cmd_get_themes`, `cmd_check_for_updates`, `cmd_dismiss_notification`, `cmd_plugin_init_errors` |
|
||||||
@@ -113,7 +114,7 @@ Each returns an `UnsupportedCommandError` carrying `cmd`, a user-facing
|
|||||||
into a toast.
|
into a toast.
|
||||||
|
|
||||||
| Reason | Commands |
|
| Reason | Commands |
|
||||||
| --- | --- |
|
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Sending, the parts not wired yet | `cmd_send_ephemeral_request`, `cmd_delete_send_history`, `cmd_delete_all_http_responses`, `cmd_import_url` |
|
| Sending, the parts not wired yet | `cmd_send_ephemeral_request`, `cmd_delete_send_history`, `cmd_delete_all_http_responses`, `cmd_import_url` |
|
||||||
| No plugin runtime | `cmd_reload_plugins`, `cmd_plugin_info`, `cmd_plugins_search`, `cmd_plugins_install`, `cmd_plugins_install_from_directory`, `cmd_plugins_uninstall`, `cmd_plugins_updates`, `cmd_plugins_update_all`, `cmd_template_function_config`, `cmd_template_tokens_to_string`, `cmd_call_http_request_action`, `cmd_call_websocket_request_action`, `cmd_call_grpc_request_action`, `cmd_call_workspace_action`, `cmd_call_folder_action`, `cmd_call_http_authentication_action`, `cmd_curl_to_request`, `cmd_format_graphql` |
|
| No plugin runtime | `cmd_reload_plugins`, `cmd_plugin_info`, `cmd_plugins_search`, `cmd_plugins_install`, `cmd_plugins_install_from_directory`, `cmd_plugins_uninstall`, `cmd_plugins_updates`, `cmd_plugins_update_all`, `cmd_template_function_config`, `cmd_template_tokens_to_string`, `cmd_call_http_request_action`, `cmd_call_websocket_request_action`, `cmd_call_grpc_request_action`, `cmd_call_workspace_action`, `cmd_call_folder_action`, `cmd_call_http_authentication_action`, `cmd_curl_to_request`, `cmd_format_graphql` |
|
||||||
| No filesystem | `cmd_import_data`, `cmd_export_data`, `cmd_save_response`, `cmd_save_base64_to_binary` |
|
| No filesystem | `cmd_import_data`, `cmd_export_data`, `cmd_save_response`, `cmd_save_base64_to_binary` |
|
||||||
@@ -137,13 +138,13 @@ should be able to see which.
|
|||||||
Reported honestly, so callers gate on the question rather than on the host:
|
Reported honestly, so callers gate on the question rather than on the host:
|
||||||
|
|
||||||
| True | False |
|
| True | False |
|
||||||
| --- | --- |
|
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `httpSending`, `timeline`, `cookieJar` | `grpc`, `websocket`, `git`, `sync`, `tlsOptions`, `localFiles`, `multiWindow`, `windowChrome`, `interfaceZoom`, `plugins`, `encryption`, `updater`, `clipboardRead`, `systemFonts`, `license` |
|
| `httpSending`, `timeline`, `cookieJar` | `grpc`, `websocket`, `git`, `sync`, `tlsOptions`, `localFiles`, `multiWindow`, `windowChrome`, `interfaceZoom`, `plugins`, `encryption`, `updater`, `clipboardRead`, `systemFonts`, `license` |
|
||||||
|
|
||||||
`interfaceZoom: false` leaves Cmd/Ctrl `+`, `-` and `0` to the browser instead
|
`interfaceZoom: false` leaves Cmd/Ctrl `+`, `-` and `0` to the browser instead
|
||||||
of swallowing them, and drops those three rows from the hotkeys screen.
|
of swallowing them, and drops those three rows from the hotkeys screen.
|
||||||
|
|
||||||
`multiWindow: false` means the host cannot open a *second window* on demand —
|
`multiWindow: false` means the host cannot open a _second window_ on demand —
|
||||||
what `cmd_new_child_window` does for Settings and workspace switching. It is not
|
what `cmd_new_child_window` does for Settings and workspace switching. It is not
|
||||||
a claim that nothing else is looking: other tabs may well be open on the same
|
a claim that nothing else is looking: other tabs may well be open on the same
|
||||||
worker, and it pushes every write to all of them regardless.
|
worker, and it pushes every write to all of them regardless.
|
||||||
@@ -159,7 +160,7 @@ for the desktop's window label. The worker fans each write out to every
|
|||||||
connected tab, and the receiving tab's store applies or ignores it exactly as a
|
connected tab, and the receiving tab's store applies or ignores it exactly as a
|
||||||
desktop window would.
|
desktop window would.
|
||||||
|
|
||||||
The label is deliberately *not* kept in `sessionStorage`: duplicating a tab
|
The label is deliberately _not_ kept in `sessionStorage`: duplicating a tab
|
||||||
copies session storage, and two tabs sharing one identity would each mistake the
|
copies session storage, and two tabs sharing one identity would each mistake the
|
||||||
other's writes for an echo of their own and drop them.
|
other's writes for an echo of their own and drop them.
|
||||||
|
|
||||||
@@ -207,7 +208,7 @@ network half of a send runs on a small stateless server,
|
|||||||
|
|
||||||
**What sends today:** any saved request whose templates are variables and whose
|
**What sends today:** any saved request whose templates are variables and whose
|
||||||
authentication is none, or an inline header. Sending a request that needs a
|
authentication is none, or an inline header. Sending a request that needs a
|
||||||
template *function* (`${[ timestamp() ]}`) or an authentication plugin (bearer,
|
template _function_ (`${[ timestamp() ]}`) or an authentication plugin (bearer,
|
||||||
basic, OAuth, …) is refused before anything leaves the tab, with a message naming
|
basic, OAuth, …) is refused before anything leaves the tab, with a message naming
|
||||||
what it needs; those light up when plugins run in the browser. Requests with a
|
what it needs; those light up when plugins run in the browser. Requests with a
|
||||||
file body or multipart file fields are refused by the server (it has no access to
|
file body or multipart file fields are refused by the server (it has no access to
|
||||||
@@ -217,11 +218,11 @@ private ranges outright — that is what the desktop app is for. A self-hosted
|
|||||||
server on your own network can be started with `--allow-private-networks`, which
|
server on your own network can be started with `--allow-private-networks`, which
|
||||||
is the one case where those addresses are the user's to reach.
|
is the one case where those addresses are the user's to reach.
|
||||||
|
|
||||||
**Where the tab sends** (`server.ts`): a production build posts to `/v1/http/send`
|
**Where the tab sends** (`server.ts`): its own origin, in development as well as
|
||||||
on its own origin, because the server can serve the app itself
|
production, so there is no address to learn and no CORS in the loop. A
|
||||||
(`yaak-web --serve dist/apps/yaak-client`, which is what the
|
production build is served by the sender itself (`yaak-web --serve
|
||||||
`ghcr.io/mountain-loop/yaak-web` image runs) — same origin, so no CORS and
|
dist/apps/yaak-client`, which is what the `ghcr.io/mountain-loop/yaak-web`
|
||||||
nothing to configure. A dev build falls back to `http://127.0.0.1:9227`, since
|
image runs). In development the Vite server passes `/v1` through to `yaak-web`
|
||||||
the Vite server is a different origin and serves no `/v1`; run one with
|
instead, following `YAAK_WEB_BIND` to find it — `vp run web:dev` starts both.
|
||||||
`cargo run -p yaak-web`. `VITE_YAAK_WEB_URL` overrides both, for a
|
`VITE_YAAK_WEB_URL` overrides this, for a deployment that keeps the app and the
|
||||||
deployment that keeps the app and the server apart.
|
server apart.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Unsubscribe } from "../types";
|
import type { Unsubscribe } from "../types";
|
||||||
|
import { randomId } from "./ids";
|
||||||
import { type FromWorker, type ToWorker, WORKER_NAME } from "./protocol";
|
import { type FromWorker, type ToWorker, WORKER_NAME } from "./protocol";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +46,7 @@ export class WorkerConnection {
|
|||||||
* and two tabs claiming one identity would each drop the other's writes as
|
* and two tabs claiming one identity would each drop the other's writes as
|
||||||
* echoes.
|
* echoes.
|
||||||
*/
|
*/
|
||||||
readonly label = `tab_${crypto.randomUUID().slice(0, 8)}`;
|
readonly label = `tab_${randomId(4)}`;
|
||||||
|
|
||||||
/** True once the worker has said anything at all. */
|
/** True once the worker has said anything at all. */
|
||||||
private heard = false;
|
private heard = false;
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Random ids that do not need a secure context.
|
||||||
|
*
|
||||||
|
* `crypto.randomUUID` exists only on HTTPS and localhost, so a tab opened at
|
||||||
|
* `http://some-host:1424` — a self-hosted instance reached from another machine —
|
||||||
|
* throws on it before anything renders. `crypto.getRandomValues` carries no such
|
||||||
|
* restriction, and nothing here needs UUID semantics: these ids identify a tab and
|
||||||
|
* a stream within one page, and only have to not collide with each other.
|
||||||
|
*/
|
||||||
|
export function randomId(bytes = 8): string {
|
||||||
|
const buf = new Uint8Array(bytes);
|
||||||
|
crypto.getRandomValues(buf);
|
||||||
|
return Array.from(buf, (b) => b.toString(16).padStart(2, "0")).join("");
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import type {
|
|||||||
import { commandSupport, runCommand } from "./commands";
|
import { commandSupport, runCommand } from "./commands";
|
||||||
import { WorkerConnection } from "./connection";
|
import { WorkerConnection } from "./connection";
|
||||||
import { unsupported } from "./errors";
|
import { unsupported } from "./errors";
|
||||||
|
import { randomId } from "./ids";
|
||||||
import { serverBaseUrl } from "./server";
|
import { serverBaseUrl } from "./server";
|
||||||
import { requestPersistence } from "./storage";
|
import { requestPersistence } from "./storage";
|
||||||
|
|
||||||
@@ -184,10 +185,18 @@ export function createWebPlatform(): Platform {
|
|||||||
clipboard: {
|
clipboard: {
|
||||||
writeText: (text) => navigator.clipboard.writeText(text),
|
writeText: (text) => navigator.clipboard.writeText(text),
|
||||||
readText: async () => {
|
readText: async () => {
|
||||||
throw unsupported("clipboard.readText", "Paste instead — Yaak in a browser can't read the clipboard on its own", "clipboardRead");
|
throw unsupported(
|
||||||
|
"clipboard.readText",
|
||||||
|
"Paste instead — Yaak in a browser can't read the clipboard on its own",
|
||||||
|
"clipboardRead",
|
||||||
|
);
|
||||||
},
|
},
|
||||||
clear: async () => {
|
clear: async () => {
|
||||||
throw unsupported("clipboard.clear", "Yaak in a browser can't modify the clipboard", "clipboardRead");
|
throw unsupported(
|
||||||
|
"clipboard.clear",
|
||||||
|
"Yaak in a browser can't modify the clipboard",
|
||||||
|
"clipboardRead",
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -200,7 +209,11 @@ export function createWebPlatform(): Platform {
|
|||||||
|
|
||||||
files: {
|
files: {
|
||||||
readDir: async () => {
|
readDir: async () => {
|
||||||
throw unsupported("files.readDir", "A browser tab can't browse your filesystem", "localFiles");
|
throw unsupported(
|
||||||
|
"files.readDir",
|
||||||
|
"A browser tab can't browse your filesystem",
|
||||||
|
"localFiles",
|
||||||
|
);
|
||||||
},
|
},
|
||||||
readText: async () => {
|
readText: async () => {
|
||||||
throw unsupported("files.readText", "A browser tab can't read local files", "localFiles");
|
throw unsupported("files.readText", "A browser tab can't read local files", "localFiles");
|
||||||
@@ -243,7 +256,7 @@ export function createWebPlatform(): Platform {
|
|||||||
): Promise<RpcStreamHandle<T>> {
|
): Promise<RpcStreamHandle<T>> {
|
||||||
// Same shape as the desktop — subscribe first, then dispatch — so that a
|
// Same shape as the desktop — subscribe first, then dispatch — so that a
|
||||||
// command which grows the ability to stream here needs no caller changes.
|
// command which grows the ability to stream here needs no caller changes.
|
||||||
const streamId = crypto.randomUUID();
|
const streamId = randomId();
|
||||||
const unlisten = db.listen(`stream_${streamId}`, (p) => onMessage(p as M));
|
const unlisten = db.listen(`stream_${streamId}`, (p) => onMessage(p as M));
|
||||||
try {
|
try {
|
||||||
const result = (await runCommand(cmd, { ...payload, streamId }, db)) as T;
|
const result = (await runCommand(cmd, { ...payload, streamId }, db)) as T;
|
||||||
@@ -268,7 +281,11 @@ export function createWebPlatform(): Platform {
|
|||||||
},
|
},
|
||||||
|
|
||||||
revealItemInDir: async () => {
|
revealItemInDir: async () => {
|
||||||
throw unsupported("revealItemInDir", "A browser tab can't open your file manager", "localFiles");
|
throw unsupported(
|
||||||
|
"revealItemInDir",
|
||||||
|
"A browser tab can't open your file manager",
|
||||||
|
"localFiles",
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
osType: detectOsType,
|
osType: detectOsType,
|
||||||
|
|||||||
@@ -16,16 +16,16 @@ import type { Frame } from "@yaakapp-internal/web";
|
|||||||
* (`yaak-web --serve`), and then a send is a request to a path on the
|
* (`yaak-web --serve`), and then a send is a request to a path on the
|
||||||
* page's own origin — no CORS, and nothing for a self-hoster to configure.
|
* page's own origin — no CORS, and nothing for a self-hoster to configure.
|
||||||
*
|
*
|
||||||
* `VITE_YAAK_WEB_URL` overrides it at build time, for a deployment that
|
* `VITE_YAAK_WEB_URL` overrides it at build time, for a deployment that keeps the
|
||||||
* keeps the two apart. The dev server is one of those: it serves the app on its
|
* two apart. Nothing else needs to: the dev server passes `/v1` through to
|
||||||
* own origin and knows nothing about `/v1`, so a dev build falls back to a server
|
* `yaak-web` (see the client's vite config), so a dev build and a production
|
||||||
* running locally (`cargo run -p yaak-web`).
|
* build are both talking to their own origin, and neither has an address to
|
||||||
|
* learn. A tab opened from another machine therefore works unchanged.
|
||||||
*/
|
*/
|
||||||
export function serverBaseUrl(): string {
|
export function serverBaseUrl(): string {
|
||||||
const env = (import.meta as unknown as { env?: Record<string, string | undefined> }).env;
|
const env = (import.meta as unknown as { env?: Record<string, string | undefined> }).env;
|
||||||
const configured = env?.VITE_YAAK_WEB_URL?.trim();
|
const configured = env?.VITE_YAAK_WEB_URL?.trim();
|
||||||
if (configured) return configured.replace(/\/+$/, "");
|
return configured ? configured.replace(/\/+$/, "") : "";
|
||||||
return env?.DEV ? "http://127.0.0.1:9227" : "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serverSendUrl(): string {
|
export function serverSendUrl(): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user