@browsersync/bslive 0.0.7 → 0.0.9
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/Cargo.toml +30 -2
- package/bin.js +3 -2
- package/bslive/Cargo.toml +16 -1
- package/bslive/build.rs +1 -1
- package/bslive/src/lib.rs +120 -4
- package/bsnext/Cargo.toml +21 -0
- package/bsnext/src/main.rs +110 -0
- package/crates/bsnext_client/Cargo.toml +8 -0
- package/crates/bsnext_client/build.rs +53 -0
- package/crates/bsnext_client/dist/index.js +6493 -0
- package/crates/bsnext_client/generated/dto.ts +116 -0
- package/crates/bsnext_client/generated/schema.ts +187 -0
- package/crates/bsnext_client/index.html +14 -0
- package/crates/bsnext_client/package-lock.json +2059 -0
- package/crates/bsnext_client/package.json +25 -0
- package/crates/bsnext_client/src/lib.rs +1 -0
- package/crates/bsnext_client/style.css +3 -0
- package/crates/bsnext_client/ts/console.ts +25 -0
- package/crates/bsnext_client/ts/index.ts +73 -0
- package/crates/bsnext_client/tsconfig.json +16 -0
- package/crates/bsnext_core/Cargo.toml +43 -0
- package/crates/bsnext_core/src/dir_loader.rs +230 -0
- package/crates/bsnext_core/src/dto.rs +281 -0
- package/crates/bsnext_core/src/handlers/mod.rs +1 -0
- package/crates/bsnext_core/src/handlers/proxy.rs +95 -0
- package/crates/bsnext_core/src/lib.rs +11 -0
- package/crates/bsnext_core/src/meta/mod.rs +5 -0
- package/crates/bsnext_core/src/not_found/mod.rs +2 -0
- package/crates/bsnext_core/src/not_found/not_found.html +20 -0
- package/crates/bsnext_core/src/not_found/not_found_service.rs +41 -0
- package/crates/bsnext_core/src/not_found/route_list.rs +49 -0
- package/crates/bsnext_core/src/panic_handler.rs +32 -0
- package/crates/bsnext_core/src/raw_loader.rs +226 -0
- package/crates/bsnext_core/src/server/actor.rs +92 -0
- package/crates/bsnext_core/src/server/error.rs +41 -0
- package/crates/bsnext_core/src/server/handler_change.rs +85 -0
- package/crates/bsnext_core/src/server/handler_listen.rs +157 -0
- package/crates/bsnext_core/src/server/handler_patch.rs +42 -0
- package/crates/bsnext_core/src/server/handler_routes_updated.rs +27 -0
- package/crates/bsnext_core/src/server/handler_stop.rs +38 -0
- package/crates/bsnext_core/src/server/mod.rs +10 -0
- package/crates/bsnext_core/src/server/router/mod.rs +112 -0
- package/crates/bsnext_core/src/server/router/tests.rs +204 -0
- package/crates/bsnext_core/src/server/signals.rs +11 -0
- package/crates/bsnext_core/src/server/state.rs +19 -0
- package/crates/bsnext_core/src/servers_supervisor/actor.rs +199 -0
- package/crates/bsnext_core/src/servers_supervisor/file_changed_handler.rs +41 -0
- package/crates/bsnext_core/src/servers_supervisor/get_servers_handler.rs +23 -0
- package/crates/bsnext_core/src/servers_supervisor/input_changed_handler.rs +21 -0
- package/crates/bsnext_core/src/servers_supervisor/mod.rs +6 -0
- package/crates/bsnext_core/src/servers_supervisor/start_handler.rs +82 -0
- package/crates/bsnext_core/src/servers_supervisor/stop_handler.rs +26 -0
- package/crates/bsnext_core/src/ws/mod.rs +164 -0
- package/crates/bsnext_example/Cargo.toml +10 -0
- package/crates/bsnext_example/src/basic.rs +51 -0
- package/crates/bsnext_example/src/lib.rs +26 -0
- package/crates/bsnext_example/src/lit.rs +37 -0
- package/crates/bsnext_example/src/md.rs +18 -0
- package/crates/bsnext_fs/Cargo.toml +22 -0
- package/crates/bsnext_fs/src/actor.rs +122 -0
- package/crates/bsnext_fs/src/buffered_debounce.rs +166 -0
- package/crates/bsnext_fs/src/filter.rs +30 -0
- package/crates/bsnext_fs/src/inner_fs_event_handler.rs +94 -0
- package/crates/bsnext_fs/src/lib.rs +141 -0
- package/crates/bsnext_fs/src/remove_path_handler.rs +46 -0
- package/crates/bsnext_fs/src/stop_handler.rs +15 -0
- package/crates/bsnext_fs/src/stream.rs +167 -0
- package/crates/bsnext_fs/src/test/mod.rs +213 -0
- package/crates/bsnext_fs/src/watch_path_handler.rs +67 -0
- package/crates/bsnext_fs/src/watcher.rs +348 -0
- package/crates/bsnext_input/Cargo.toml +22 -0
- package/crates/bsnext_input/src/input_test/mod.rs +151 -0
- package/crates/bsnext_input/src/lib.rs +153 -0
- package/crates/bsnext_input/src/md.rs +541 -0
- package/crates/bsnext_input/src/paths.rs +64 -0
- package/crates/bsnext_input/src/route.rs +185 -0
- package/crates/bsnext_input/src/route_manifest.rs +186 -0
- package/crates/bsnext_input/src/server_config.rs +88 -0
- package/crates/bsnext_input/src/target.rs +7 -0
- package/crates/bsnext_input/src/watch_opt_test/mod.rs +68 -0
- package/crates/bsnext_input/src/yml.rs +1 -0
- package/crates/bsnext_output/Cargo.toml +16 -0
- package/crates/bsnext_output/src/json.rs +11 -0
- package/crates/bsnext_output/src/lib.rs +25 -0
- package/crates/bsnext_output/src/pretty.rs +147 -0
- package/crates/bsnext_resp/Cargo.toml +13 -0
- package/crates/bsnext_resp/src/js/snippet.html +1 -0
- package/crates/bsnext_resp/src/js/ws.js +1 -0
- package/crates/bsnext_resp/src/lib.rs +79 -0
- package/crates/bsnext_system/Cargo.toml +29 -0
- package/crates/bsnext_system/src/args.rs +43 -0
- package/crates/bsnext_system/src/lib.rs +227 -0
- package/crates/bsnext_system/src/monitor.rs +241 -0
- package/crates/bsnext_system/src/monitor_any_watchables.rs +127 -0
- package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test-2.snap +11 -0
- package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test.snap +29 -0
- package/crates/bsnext_system/src/start_kind/start_from_example.rs +49 -0
- package/crates/bsnext_system/src/start_kind/start_from_inputs.rs +67 -0
- package/crates/bsnext_system/src/start_kind/start_from_paths.rs +51 -0
- package/crates/bsnext_system/src/start_kind.rs +56 -0
- package/crates/bsnext_system/src/startup.rs +51 -0
- package/crates/bsnext_tracing/Cargo.toml +10 -0
- package/crates/bsnext_tracing/src/lib.rs +89 -0
- package/examples/basic/input.yml +5 -0
- package/examples/basic/public/index.html +15 -0
- package/examples/basic/public/reset.css +52 -0
- package/examples/basic/public/script.js +1 -0
- package/examples/basic/public/styles.css +3 -0
- package/examples/kitchen-sink/a-file.md +1 -0
- package/examples/kitchen-sink/api/1.json +1 -0
- package/examples/kitchen-sink/api/2.json +3 -0
- package/examples/kitchen-sink/app.js +1 -0
- package/examples/kitchen-sink/input.html +185 -0
- package/examples/kitchen-sink/input.yml +133 -0
- package/examples/kitchen-sink/styles-2.css +3 -0
- package/examples/lit/index.html +21 -0
- package/examples/lit/input.yml +5 -0
- package/examples/lit/lit.js +82 -0
- package/examples/lit/package-lock.json +62 -0
- package/examples/lit/package.json +15 -0
- package/examples/md-single/frontmatter.md +35 -0
- package/examples/md-single/md-single.md +35 -0
- package/examples/openai/.nvm +1 -0
- package/examples/openai/build.mjs +21 -0
- package/examples/openai/index.html +13 -0
- package/examples/openai/input.yml +59 -0
- package/examples/openai/package-lock.json +720 -0
- package/examples/openai/package.json +21 -0
- package/examples/openai/src/index.js +21 -0
- package/examples/openai/sse/01.txt +8 -0
- package/examples/proxy-simple/input.yml +9 -0
- package/examples/single/input.yaml +26 -0
- package/index.d.ts +2 -0
- package/index.js +2 -1
- package/package.json +17 -17
- package/run.sh +6 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bsnext_client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"tsc": "tsc",
|
|
8
|
+
"build": "esbuild ts/index.ts --bundle --outdir=dist --format=esm",
|
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
10
|
+
"schema": "ts-to-zod generated/dto.ts generated/schema.ts"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"esbuild": "^0.20.2",
|
|
18
|
+
"livereload-js": "^4.0.2",
|
|
19
|
+
"rxjs": "^7.8.1",
|
|
20
|
+
"typescript": "^5.4.5",
|
|
21
|
+
"@types/node": "18.19.25",
|
|
22
|
+
"ts-to-zod": "^3.8.2",
|
|
23
|
+
"zod": "^3.22.4"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pub const DIR: &str = "dist";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {Observable, Subject} from "rxjs";
|
|
2
|
+
|
|
3
|
+
export enum Level {
|
|
4
|
+
Trace = "Trace",
|
|
5
|
+
Debug = "Debug",
|
|
6
|
+
Info = "Info",
|
|
7
|
+
Error = "Error",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ConsoleEvent {
|
|
11
|
+
level: Level,
|
|
12
|
+
text: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createLRConsoleObserver(): [Subject<ConsoleEvent>, Pick<typeof console, "log">] {
|
|
16
|
+
const subject = new Subject<ConsoleEvent>;
|
|
17
|
+
return [subject, {
|
|
18
|
+
log: function (...data: any[]): void {
|
|
19
|
+
subject.next({
|
|
20
|
+
level: Level.Debug,
|
|
21
|
+
text: data.join('\n')
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
}]
|
|
25
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import {Reloader} from "livereload-js/src/reloader.js";
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import {Timer} from "livereload-js/src/timer.js";
|
|
5
|
+
|
|
6
|
+
import {webSocket} from "rxjs/webSocket";
|
|
7
|
+
import {retry} from "rxjs";
|
|
8
|
+
import {clientEventSchema} from "../generated/schema.js";
|
|
9
|
+
import {ChangeDTO, ClientEvent} from "../generated/dto";
|
|
10
|
+
import {createLRConsoleObserver, Level} from "./console";
|
|
11
|
+
|
|
12
|
+
const [consoleSubject, consoleApi] = createLRConsoleObserver();
|
|
13
|
+
|
|
14
|
+
const r = new Reloader(window, consoleApi, Timer);
|
|
15
|
+
const url = new URL(window.location.href);
|
|
16
|
+
url.protocol = url.protocol === 'http:' ? 'ws' : 'wss';
|
|
17
|
+
url.pathname = '/__bs_ws'
|
|
18
|
+
const socket = webSocket<ClientEvent>(url.href);
|
|
19
|
+
|
|
20
|
+
socket
|
|
21
|
+
.pipe(retry({delay: 5000}))
|
|
22
|
+
.subscribe(m => {
|
|
23
|
+
console.log(JSON.stringify(m, null, 2))
|
|
24
|
+
const parsed = clientEventSchema.parse(m);
|
|
25
|
+
switch (parsed.kind) {
|
|
26
|
+
case "Change": {
|
|
27
|
+
changedPath(parsed.payload);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
default: {
|
|
31
|
+
console.warn("unhandled client event")
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
function changedPath(change: ChangeDTO) {
|
|
37
|
+
switch (change.kind) {
|
|
38
|
+
case "FsMany": {
|
|
39
|
+
// todo: if this collection of events contains
|
|
40
|
+
// anything that will cause a refresh, just do it immediately
|
|
41
|
+
for (let changeDTO of change.payload) {
|
|
42
|
+
changedPath(changeDTO);
|
|
43
|
+
}
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
case "Fs": {
|
|
47
|
+
let path = change.payload.path;
|
|
48
|
+
r.reload(path, {
|
|
49
|
+
liveCSS: true,
|
|
50
|
+
liveImg: true,
|
|
51
|
+
reloadMissingCSS: false,
|
|
52
|
+
originalPath: '',
|
|
53
|
+
overrideURL: '',
|
|
54
|
+
serverURL: ``,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
consoleSubject.subscribe(evt => {
|
|
61
|
+
switch (evt.level) {
|
|
62
|
+
case Level.Trace:
|
|
63
|
+
break;
|
|
64
|
+
case Level.Debug:
|
|
65
|
+
console.log('[debug]', evt.text)
|
|
66
|
+
break;
|
|
67
|
+
case Level.Info:
|
|
68
|
+
break;
|
|
69
|
+
case Level.Error:
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES6",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"checkJs": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "bsnext_core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
|
|
6
|
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
7
|
+
|
|
8
|
+
[dependencies]
|
|
9
|
+
bsnext_input = { path = "../bsnext_input" }
|
|
10
|
+
bsnext_fs = { path = "../bsnext_fs" }
|
|
11
|
+
bsnext_resp = { path = "../bsnext_resp" }
|
|
12
|
+
bsnext_client = { path = "../bsnext_client" }
|
|
13
|
+
|
|
14
|
+
axum-server = { version = "0.6.0", features = ["tls-rustls"] }
|
|
15
|
+
axum-extra = { version = "0.9.3", features = ["typed-header"] }
|
|
16
|
+
hyper = { version = "1.0.0", features = ["full"] }
|
|
17
|
+
hyper-tls = { version = "0.6.0" }
|
|
18
|
+
hyper-util = { version = "0.1.1", features = ["client-legacy"] }
|
|
19
|
+
matchit = { version = "0.7.3" }
|
|
20
|
+
htmlescape = { version = "0.3.1" }
|
|
21
|
+
|
|
22
|
+
mime_guess = { workspace = true }
|
|
23
|
+
axum = { workspace = true }
|
|
24
|
+
clap = { workspace = true }
|
|
25
|
+
http-body-util = { workspace = true }
|
|
26
|
+
tokio = { workspace = true }
|
|
27
|
+
tokio-stream = { workspace = true }
|
|
28
|
+
tower = { workspace = true }
|
|
29
|
+
tower-http = { workspace = true }
|
|
30
|
+
tracing = { workspace = true }
|
|
31
|
+
actix = { workspace = true }
|
|
32
|
+
actix-rt = { workspace = true }
|
|
33
|
+
anyhow = { workspace = true }
|
|
34
|
+
futures = { workspace = true }
|
|
35
|
+
futures-util = { workspace = true }
|
|
36
|
+
serde = { workspace = true }
|
|
37
|
+
serde_yaml = { workspace = true }
|
|
38
|
+
thiserror = { workspace = true }
|
|
39
|
+
tracing-subscriber = { workspace = true }
|
|
40
|
+
serde_json = { workspace = true }
|
|
41
|
+
bytes = { workspace = true }
|
|
42
|
+
http = { workspace = true }
|
|
43
|
+
typeshare = { workspace = true }
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#![allow(clippy::redundant_pattern_matching)]
|
|
2
|
+
#![allow(clippy::single_match)]
|
|
3
|
+
use crate::handlers::proxy::ProxyConfig;
|
|
4
|
+
use crate::meta::MetaData;
|
|
5
|
+
use crate::raw_loader::add_route_layers;
|
|
6
|
+
use axum::body::Body;
|
|
7
|
+
use axum::extract::{Request, State};
|
|
8
|
+
|
|
9
|
+
use axum::middleware::Next;
|
|
10
|
+
use axum::response::{IntoResponse, Response};
|
|
11
|
+
use axum::{middleware, Extension, Router};
|
|
12
|
+
use bytes::Bytes;
|
|
13
|
+
use futures::channel::mpsc::unbounded;
|
|
14
|
+
use futures::SinkExt;
|
|
15
|
+
|
|
16
|
+
use crate::handlers::proxy;
|
|
17
|
+
use crate::server::state::ServerState;
|
|
18
|
+
use axum::routing::any;
|
|
19
|
+
use bsnext_input::route::{DirRoute, ProxyRoute, Route, RouteKind};
|
|
20
|
+
use bsnext_resp::{response_modifications_layer, RespMod};
|
|
21
|
+
use http::{HeaderValue, StatusCode};
|
|
22
|
+
use http_body_util::BodyExt;
|
|
23
|
+
use std::collections::HashMap;
|
|
24
|
+
use std::convert::Infallible;
|
|
25
|
+
use std::sync::Arc;
|
|
26
|
+
use std::time::Duration;
|
|
27
|
+
use tokio::fs::File;
|
|
28
|
+
use tokio::io::{AsyncWriteExt, BufWriter};
|
|
29
|
+
use tokio_stream::StreamExt;
|
|
30
|
+
use tower::{ServiceBuilder, ServiceExt};
|
|
31
|
+
use tower_http::decompression::DecompressionLayer;
|
|
32
|
+
use tower_http::services::ServeDir;
|
|
33
|
+
use tracing::{span, Instrument, Level};
|
|
34
|
+
|
|
35
|
+
pub async fn serve_dir_loader(
|
|
36
|
+
State(state): State<Arc<ServerState>>,
|
|
37
|
+
req: Request,
|
|
38
|
+
_next: Next,
|
|
39
|
+
) -> Response {
|
|
40
|
+
let _path = req.uri().path().to_owned();
|
|
41
|
+
let span = span!(parent: None, Level::INFO, "serve_dir_loader", path = req.uri().path());
|
|
42
|
+
let _guard = span.enter();
|
|
43
|
+
|
|
44
|
+
let accepts_html = RespMod::accepts_html(&req);
|
|
45
|
+
let routes = state.routes.read().instrument(span.clone()).await;
|
|
46
|
+
let mut app = Router::new();
|
|
47
|
+
let route_map = routes
|
|
48
|
+
.iter()
|
|
49
|
+
.filter(|r| matches!(&r.kind, RouteKind::Proxy(_) | RouteKind::Dir(_)))
|
|
50
|
+
.fold(HashMap::<&str, Vec<&Route>>::new(), |mut acc, route| {
|
|
51
|
+
acc.entry(route.path.as_str())
|
|
52
|
+
.and_modify(|acc| acc.push(route))
|
|
53
|
+
.or_insert(vec![route]);
|
|
54
|
+
acc
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
for (_, routes) in route_map.iter() {
|
|
58
|
+
if routes.len() > 1 {
|
|
59
|
+
tracing::error!(
|
|
60
|
+
"cannot register duplicate dir or proxy routes, only the first will take effect"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let route = routes.first().expect("guarded");
|
|
65
|
+
let mut router = match &route.kind {
|
|
66
|
+
RouteKind::Proxy(ProxyRoute { proxy }) => {
|
|
67
|
+
let response_handling = if accepts_html {
|
|
68
|
+
ResponseHandling::Modify
|
|
69
|
+
} else {
|
|
70
|
+
ResponseHandling::None
|
|
71
|
+
};
|
|
72
|
+
service_for_proxy(response_handling, route).layer(Extension(ProxyConfig {
|
|
73
|
+
target: proxy.to_owned(),
|
|
74
|
+
path: route.path.clone(),
|
|
75
|
+
}))
|
|
76
|
+
}
|
|
77
|
+
RouteKind::Dir(DirRoute { dir }) => service_for_dir(route, dir),
|
|
78
|
+
_ => unreachable!("{:?}", route.kind),
|
|
79
|
+
};
|
|
80
|
+
router = add_route_layers(router, route);
|
|
81
|
+
app = app.merge(router);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
drop(routes);
|
|
85
|
+
|
|
86
|
+
let r = app.oneshot(req).await;
|
|
87
|
+
|
|
88
|
+
r.into_response()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fn service_for_dir(route: &Route, dir: &str) -> Router {
|
|
92
|
+
Router::new()
|
|
93
|
+
.nest_service(route.path.as_str(), ServeDir::new(dir))
|
|
94
|
+
.layer(middleware::from_fn(tag_file))
|
|
95
|
+
.layer(middleware::from_fn(response_modifications_layer))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async fn tag_file(req: Request, next: Next) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
99
|
+
let (mut parts, body) = next.run(req).await.into_parts();
|
|
100
|
+
if parts.status.as_u16() == 200 {
|
|
101
|
+
parts.extensions.insert(MetaData::ServedFile);
|
|
102
|
+
}
|
|
103
|
+
Ok(Response::from_parts(parts, body))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
enum ResponseHandling {
|
|
107
|
+
Modify,
|
|
108
|
+
None,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn service_for_proxy(handling: ResponseHandling, route: &Route) -> Router {
|
|
112
|
+
match handling {
|
|
113
|
+
ResponseHandling::Modify => Router::new()
|
|
114
|
+
.nest_service(route.path.as_str(), any(proxy::proxy_handler))
|
|
115
|
+
.layer(
|
|
116
|
+
ServiceBuilder::new()
|
|
117
|
+
.layer(middleware::from_fn(response_modifications_layer))
|
|
118
|
+
.layer(DecompressionLayer::new()),
|
|
119
|
+
),
|
|
120
|
+
ResponseHandling::None => Router::new()
|
|
121
|
+
.nest_service(route.path.as_str(), any(proxy::proxy_handler))
|
|
122
|
+
.layer(middleware::from_fn(response_modifications_layer)),
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#[allow(dead_code)]
|
|
127
|
+
async fn print_request_response(
|
|
128
|
+
req: Request,
|
|
129
|
+
next: Next,
|
|
130
|
+
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
131
|
+
let (parts, body) = req.into_parts();
|
|
132
|
+
let bytes = buffer_and_print("request", body).await?;
|
|
133
|
+
let req = Request::from_parts(parts, Body::from(bytes));
|
|
134
|
+
|
|
135
|
+
let res = next.run(req).await;
|
|
136
|
+
let (parts, body) = res.into_parts();
|
|
137
|
+
|
|
138
|
+
let (mut sender, rec) = unbounded::<Bytes>();
|
|
139
|
+
let is_event_stream = parts.headers.get("content-type")
|
|
140
|
+
== Some(&HeaderValue::from_str("text/event-stream").unwrap());
|
|
141
|
+
|
|
142
|
+
tokio::spawn(async move {
|
|
143
|
+
let mut stream = body.into_data_stream();
|
|
144
|
+
let mut chunks: Vec<Bytes> = vec![];
|
|
145
|
+
while let Some(b) = stream.next().await {
|
|
146
|
+
tracing::info!("CHUNK");
|
|
147
|
+
match b {
|
|
148
|
+
Ok(bytes) => {
|
|
149
|
+
if is_event_stream {
|
|
150
|
+
// let _written = file.write(&bytes).await;
|
|
151
|
+
chunks.push(bytes.to_owned());
|
|
152
|
+
match sender.send(bytes.to_owned()).await {
|
|
153
|
+
Ok(_) => tracing::trace!("stripped chunk sent"),
|
|
154
|
+
Err(_) => tracing::error!("stripped chunk not sent"),
|
|
155
|
+
};
|
|
156
|
+
} else {
|
|
157
|
+
match sender.send(bytes.to_owned()).await {
|
|
158
|
+
Ok(_) => tracing::trace!("chunk sent"),
|
|
159
|
+
Err(_) => tracing::error!("not sent"),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
Err(e) => {
|
|
164
|
+
tracing::error!(?e, "error")
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if is_event_stream {
|
|
169
|
+
let path = std::path::Path::new("record").join("out4.yml");
|
|
170
|
+
let mut file = BufWriter::new(File::create(path).await.expect("file"));
|
|
171
|
+
let to_str = chunks
|
|
172
|
+
.into_iter()
|
|
173
|
+
.map(|x| std::str::from_utf8(&x).expect("bytes").to_owned())
|
|
174
|
+
.collect::<Vec<_>>();
|
|
175
|
+
let yml = serde_yaml::to_string(&to_str).expect("to yaml");
|
|
176
|
+
println!("yml={}", yml);
|
|
177
|
+
let _r = file.write(yml.as_bytes());
|
|
178
|
+
|
|
179
|
+
match file.flush().await {
|
|
180
|
+
Ok(_) => {}
|
|
181
|
+
Err(_) => {}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// let res = Response::from_parts(parts, body);
|
|
187
|
+
// while let Some(b) = res.body().next().await {
|
|
188
|
+
// dbg!(b);
|
|
189
|
+
// }
|
|
190
|
+
|
|
191
|
+
// let (parts, body) = res.into_parts();
|
|
192
|
+
// let bytes = buffer_and_print("response", body).await?;
|
|
193
|
+
// let l = lines
|
|
194
|
+
// .lines()
|
|
195
|
+
// .map(|l| l.to_owned())
|
|
196
|
+
// .map(|l| l.strip_prefix("data:").unwrap_or(&l).to_owned())
|
|
197
|
+
// .filter(|l| !l.trim().is_empty())
|
|
198
|
+
// .collect::<Vec<_>>();
|
|
199
|
+
|
|
200
|
+
let stream = rec
|
|
201
|
+
.throttle(Duration::from_millis(500))
|
|
202
|
+
.map(Ok::<_, Infallible>);
|
|
203
|
+
|
|
204
|
+
let res = Response::from_parts(parts, Body::from_stream(stream));
|
|
205
|
+
|
|
206
|
+
Ok(res)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#[allow(dead_code)]
|
|
210
|
+
async fn buffer_and_print<B>(direction: &str, body: B) -> Result<Bytes, (StatusCode, String)>
|
|
211
|
+
where
|
|
212
|
+
B: axum::body::HttpBody<Data = Bytes>,
|
|
213
|
+
B::Error: std::fmt::Display,
|
|
214
|
+
{
|
|
215
|
+
let bytes = match body.collect().await {
|
|
216
|
+
Ok(collected) => collected.to_bytes(),
|
|
217
|
+
Err(err) => {
|
|
218
|
+
return Err((
|
|
219
|
+
StatusCode::BAD_REQUEST,
|
|
220
|
+
format!("failed to read {direction} body: {err}"),
|
|
221
|
+
));
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
if let Ok(body) = std::str::from_utf8(&bytes) {
|
|
226
|
+
tracing::debug!("{direction} body = {body:?}");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
Ok(bytes)
|
|
230
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
use actix::MessageResponse;
|
|
2
|
+
use bsnext_input::server_config::Identity;
|
|
3
|
+
use bsnext_input::InputError;
|
|
4
|
+
use std::fmt::{Display, Formatter};
|
|
5
|
+
use std::path::Path;
|
|
6
|
+
|
|
7
|
+
use crate::server::handler_change::{Change, ChangeKind};
|
|
8
|
+
use bsnext_fs::Debounce;
|
|
9
|
+
use typeshare::typeshare;
|
|
10
|
+
|
|
11
|
+
#[typeshare]
|
|
12
|
+
#[derive(Debug, serde::Serialize)]
|
|
13
|
+
pub struct ServersStarted {
|
|
14
|
+
pub servers_resp: GetServersMessageResponse,
|
|
15
|
+
pub changeset: ServerChangeSet,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[typeshare]
|
|
19
|
+
#[derive(Debug, serde::Serialize)]
|
|
20
|
+
pub enum EventLevel {
|
|
21
|
+
#[serde(rename = "BSLIVE_EXTERNAL")]
|
|
22
|
+
External,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[typeshare]
|
|
26
|
+
#[derive(Debug, serde::Serialize)]
|
|
27
|
+
pub struct ExternalEvent {
|
|
28
|
+
pub level: EventLevel,
|
|
29
|
+
pub fields: ExternalEvents,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[typeshare]
|
|
33
|
+
#[derive(Debug, serde::Serialize)]
|
|
34
|
+
#[serde(tag = "kind", content = "payload")]
|
|
35
|
+
pub enum ExternalEvents {
|
|
36
|
+
ServersStarted(ServersStarted),
|
|
37
|
+
Watching(Watching),
|
|
38
|
+
WatchingStopped(StoppedWatching),
|
|
39
|
+
FileChanged(FileChanged),
|
|
40
|
+
FilesChanged(FilesChangedDTO),
|
|
41
|
+
InputFileChanged(FileChanged),
|
|
42
|
+
InputAccepted(InputAccepted),
|
|
43
|
+
StartupFailed(InputErrorDTO),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#[typeshare]
|
|
47
|
+
#[derive(Debug, serde::Serialize)]
|
|
48
|
+
pub struct InputAccepted {
|
|
49
|
+
pub path: String,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[typeshare]
|
|
53
|
+
#[derive(Debug, serde::Serialize)]
|
|
54
|
+
pub struct FileChanged {
|
|
55
|
+
pub path: String,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[typeshare]
|
|
59
|
+
#[derive(Debug, serde::Serialize)]
|
|
60
|
+
pub struct FilesChangedDTO {
|
|
61
|
+
pub paths: Vec<String>,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[typeshare]
|
|
65
|
+
#[derive(Debug, serde::Serialize)]
|
|
66
|
+
pub struct Watching {
|
|
67
|
+
pub paths: Vec<String>,
|
|
68
|
+
pub debounce: DebounceDTO,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[typeshare]
|
|
72
|
+
#[derive(Debug, serde::Serialize)]
|
|
73
|
+
pub struct StoppedWatching {
|
|
74
|
+
pub paths: Vec<String>,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
impl StoppedWatching {
|
|
78
|
+
pub fn from_path_buf(p: &Path) -> Self {
|
|
79
|
+
Self {
|
|
80
|
+
paths: vec![p.to_string_lossy().to_string()],
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
impl Display for Watching {
|
|
86
|
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
87
|
+
write!(f, "yo")
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#[typeshare]
|
|
92
|
+
#[derive(Debug, serde::Serialize)]
|
|
93
|
+
pub struct DebounceDTO {
|
|
94
|
+
kind: String,
|
|
95
|
+
ms: String,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
impl Display for DebounceDTO {
|
|
99
|
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
100
|
+
write!(f, "{}:{}ms", self.kind, self.ms)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl From<Debounce> for DebounceDTO {
|
|
105
|
+
fn from(value: Debounce) -> Self {
|
|
106
|
+
match value {
|
|
107
|
+
Debounce::Trailing { duration } => Self {
|
|
108
|
+
kind: "trailing".to_string(),
|
|
109
|
+
ms: duration.as_millis().to_string(),
|
|
110
|
+
},
|
|
111
|
+
Debounce::Buffered { duration } => Self {
|
|
112
|
+
kind: "buffered".to_string(),
|
|
113
|
+
ms: duration.as_millis().to_string(),
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
impl FileChanged {
|
|
120
|
+
pub fn from_path_buf(p: &Path) -> Self {
|
|
121
|
+
Self {
|
|
122
|
+
path: p.to_string_lossy().to_string(),
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
impl Watching {
|
|
128
|
+
pub fn from_path_buf(p: &Path, debounce: Debounce) -> Self {
|
|
129
|
+
Self {
|
|
130
|
+
paths: vec![p.to_string_lossy().to_string()],
|
|
131
|
+
debounce: debounce.into(),
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[typeshare]
|
|
137
|
+
#[derive(Debug, Clone, serde::Serialize)]
|
|
138
|
+
#[serde(tag = "kind", content = "payload")]
|
|
139
|
+
pub enum ServerChange {
|
|
140
|
+
Stopped { bind_address: String },
|
|
141
|
+
Started,
|
|
142
|
+
Patched,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#[typeshare]
|
|
146
|
+
#[derive(Debug, serde::Serialize)]
|
|
147
|
+
pub struct ServerChangeSetItem {
|
|
148
|
+
pub identity: IdentityDTO,
|
|
149
|
+
pub change: ServerChange,
|
|
150
|
+
}
|
|
151
|
+
#[typeshare]
|
|
152
|
+
#[derive(Debug, serde::Serialize)]
|
|
153
|
+
pub struct ServerChangeSet {
|
|
154
|
+
pub items: Vec<ServerChangeSetItem>,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#[typeshare::typeshare]
|
|
158
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, MessageResponse)]
|
|
159
|
+
pub struct GetServersMessageResponse {
|
|
160
|
+
pub servers: Vec<ServersDTO>,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[typeshare::typeshare]
|
|
164
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
165
|
+
pub struct ServersDTO {
|
|
166
|
+
pub identity: IdentityDTO,
|
|
167
|
+
pub socket_addr: String,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#[typeshare::typeshare]
|
|
171
|
+
#[derive(Debug, PartialEq, Hash, Eq, Clone, serde::Deserialize, serde::Serialize)]
|
|
172
|
+
#[serde(tag = "kind", content = "payload")]
|
|
173
|
+
pub enum IdentityDTO {
|
|
174
|
+
Both { name: String, bind_address: String },
|
|
175
|
+
Address { bind_address: String },
|
|
176
|
+
Named { name: String },
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
impl From<&Identity> for IdentityDTO {
|
|
180
|
+
fn from(value: &Identity) -> Self {
|
|
181
|
+
match value {
|
|
182
|
+
Identity::Both { name, bind_address } => IdentityDTO::Both {
|
|
183
|
+
name: name.to_owned(),
|
|
184
|
+
bind_address: bind_address.to_owned(),
|
|
185
|
+
},
|
|
186
|
+
Identity::Address { bind_address } => IdentityDTO::Address {
|
|
187
|
+
bind_address: bind_address.to_owned(),
|
|
188
|
+
},
|
|
189
|
+
Identity::Named { name } => IdentityDTO::Named {
|
|
190
|
+
name: name.to_owned(),
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[typeshare::typeshare]
|
|
197
|
+
#[derive(Debug, PartialEq, Hash, Eq, Clone, serde::Deserialize, serde::Serialize)]
|
|
198
|
+
#[serde(tag = "kind", content = "payload")]
|
|
199
|
+
pub enum InputErrorDTO {
|
|
200
|
+
MissingInputs(String),
|
|
201
|
+
InvalidInput(String),
|
|
202
|
+
NotFound(String),
|
|
203
|
+
InputWriteError(String),
|
|
204
|
+
PathError(String),
|
|
205
|
+
PortError(String),
|
|
206
|
+
DirError(String),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
impl From<&InputError> for InputErrorDTO {
|
|
210
|
+
fn from(value: &InputError) -> Self {
|
|
211
|
+
match value {
|
|
212
|
+
e @ InputError::MissingInputs => InputErrorDTO::MissingInputs(e.to_string()),
|
|
213
|
+
e @ InputError::InvalidInput(_) => InputErrorDTO::InvalidInput(e.to_string()),
|
|
214
|
+
e @ InputError::NotFound(_) => InputErrorDTO::NotFound(e.to_string()),
|
|
215
|
+
e @ InputError::InputWriteError(_) => InputErrorDTO::InputWriteError(e.to_string()),
|
|
216
|
+
e @ InputError::PathError(_) => InputErrorDTO::PathError(e.to_string()),
|
|
217
|
+
e @ InputError::PortError(_) => InputErrorDTO::PortError(e.to_string()),
|
|
218
|
+
e @ InputError::DirError(_) => InputErrorDTO::DirError(e.to_string()),
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#[typeshare::typeshare]
|
|
224
|
+
#[derive(Debug, Clone, serde::Serialize)]
|
|
225
|
+
#[serde(tag = "kind", content = "payload")]
|
|
226
|
+
pub enum ClientEvent {
|
|
227
|
+
Change(ChangeDTO),
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#[typeshare::typeshare]
|
|
231
|
+
#[derive(Debug, Clone, serde::Serialize)]
|
|
232
|
+
#[serde(tag = "kind", content = "payload")]
|
|
233
|
+
pub enum ChangeDTO {
|
|
234
|
+
Fs {
|
|
235
|
+
path: String,
|
|
236
|
+
change_kind: ChangeKind,
|
|
237
|
+
},
|
|
238
|
+
FsMany(Vec<ChangeDTO>),
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
impl From<&Change> for ChangeDTO {
|
|
242
|
+
fn from(value: &Change) -> Self {
|
|
243
|
+
match value {
|
|
244
|
+
Change::Fs { path, change_kind } => Self::Fs {
|
|
245
|
+
path: path.to_string_lossy().to_string(),
|
|
246
|
+
change_kind: change_kind.clone(),
|
|
247
|
+
},
|
|
248
|
+
Change::FsMany(changes) => Self::FsMany(
|
|
249
|
+
changes
|
|
250
|
+
.iter()
|
|
251
|
+
.map(|change| match change {
|
|
252
|
+
Change::Fs { path, change_kind } => Self::Fs {
|
|
253
|
+
path: path.to_string_lossy().to_string(),
|
|
254
|
+
change_kind: change_kind.clone(),
|
|
255
|
+
},
|
|
256
|
+
Change::FsMany(_) => unreachable!("recursive not supported"),
|
|
257
|
+
})
|
|
258
|
+
.collect(),
|
|
259
|
+
),
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#[cfg(test)]
|
|
265
|
+
mod test {
|
|
266
|
+
use super::*;
|
|
267
|
+
#[test]
|
|
268
|
+
fn test_serialize() -> anyhow::Result<()> {
|
|
269
|
+
let fs = Change::fs("./a.js");
|
|
270
|
+
let evt = ClientEvent::Change((&fs).into());
|
|
271
|
+
let _json = serde_json::to_string(&evt).unwrap();
|
|
272
|
+
Ok(())
|
|
273
|
+
}
|
|
274
|
+
#[test]
|
|
275
|
+
fn test_serialize_server_start() -> anyhow::Result<()> {
|
|
276
|
+
let fs = Change::fs("./a.js");
|
|
277
|
+
let evt = ClientEvent::Change((&fs).into());
|
|
278
|
+
let _json = serde_json::to_string(&evt).unwrap();
|
|
279
|
+
Ok(())
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pub mod proxy;
|