Skip to content

Commit

Permalink
Merge pull request #49 from BrowserSync/inject_config
Browse files Browse the repository at this point in the history
try to use injected config
  • Loading branch information
shakyShane authored Dec 26, 2024
2 parents 07fb05d + ec1a656 commit b388b47
Show file tree
Hide file tree
Showing 13 changed files with 212 additions and 121 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/bsnext_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde_json = { workspace = true }
bsnext_dto = { path = "../bsnext_dto" }
8 changes: 8 additions & 0 deletions crates/bsnext_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@ pub const DIR: &str = "dist";
pub const UI_CSS: &str = include_str!("../../../ui/dist/index.css");
pub const UI_JS: &str = include_str!("../../../ui/dist/index.js");
const UI_HTML: &str = include_str!("../../../ui/index.html");
pub const INJECT_JS: &str = include_str!("../../../inject/dist/index.js");
pub const REPLACE_STR: &str = "window.$BSLIVE_INJECT_CONFIG$";
pub const WS_PATH: &str = "/__bs_ws";

pub fn html_with_base(base_override: &str) -> String {
let base = UI_HTML;
let next = format!("<base href=\"{}\" />", base_override);
let replaced = base.replace("<base href=\"/\" />", next.as_str());
replaced
}

pub fn inject_js_with_config(inject: bsnext_dto::InjectConfig) -> String {
let json = serde_json::to_string(&inject).expect("its a known type");
INJECT_JS.replace(REPLACE_STR, &json)
}
24 changes: 16 additions & 8 deletions crates/bsnext_core/src/server/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use crate::server::router::pub_api::pub_api;
use crate::server::state::ServerState;
use crate::ws::ws_handler;
use axum::body::Body;
use bsnext_client::html_with_base;
use bsnext_dto::{RouteDTO, ServerDesc};
use http::header::CONTENT_TYPE;
use bsnext_client::{html_with_base, inject_js_with_config, WS_PATH};
use bsnext_dto::{ConnectInfo, InjectConfig, RouteDTO, ServerDesc};
use http::header::{CONTENT_TYPE, HOST};
use http::{HeaderValue, StatusCode};
use hyper_tls::HttpsConnector;
use hyper_util::client::legacy::connect::HttpConnector;
Expand All @@ -26,6 +26,7 @@ use mime_guess::mime;
use std::sync::Arc;
use tower::{ServiceBuilder, ServiceExt};
use tower_http::catch_panic::CatchPanicLayer;
use tower_http::cors::CorsLayer;
use tracing::{span, Level};

mod assets;
Expand All @@ -37,7 +38,7 @@ pub fn make_router(state: &Arc<ServerState>) -> Router {
Client::builder(TokioExecutor::new()).build(https);

let router = Router::new()
.merge(built_ins(state.clone()))
.merge(built_ins(state.clone()).layer(CorsLayer::permissive()))
.merge(dynamic_loaders(state.clone()));

router
Expand Down Expand Up @@ -68,21 +69,28 @@ pub fn built_ins(state: Arc<ServerState>) -> Router {
)
.into_response()
}
async fn js_handler(_uri: Uri) -> impl IntoResponse {
let markup = include_str!("../../../../../inject/dist/index.js");
async fn js_handler(_uri: Uri, req: Request) -> impl IntoResponse {
let host = req.headers().get(HOST);
let inject = InjectConfig {
ctx_message: "This InjectConfig was created in the Browsersync LIVE js_handler".into(),
connect: ConnectInfo {
ws_path: WS_PATH.into(),
host: host.and_then(|x| x.to_str().ok().map(ToOwned::to_owned)),
},
};
(
[(
CONTENT_TYPE,
HeaderValue::from_static(mime::APPLICATION_JAVASCRIPT_UTF_8.as_ref()),
)],
markup,
inject_js_with_config(inject),
)
.into_response()
}

route("/__bslive", get(handler))
.route("/__bs_js", get(js_handler))
.route("/__bs_ws", get(ws_handler))
.route(WS_PATH, get(ws_handler))
.nest("/__bs_api", pub_api(state.clone()))
.nest("/__bs_assets/ui", pub_ui_assets(state.clone()))
.with_state(state.clone())
Expand Down
14 changes: 14 additions & 0 deletions crates/bsnext_dto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,17 @@ pub enum ChangeKind {
Added,
Removed,
}

#[typeshare::typeshare]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct InjectConfig {
pub connect: ConnectInfo,
pub ctx_message: String,
}

#[typeshare::typeshare]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct ConnectInfo {
pub ws_path: String,
pub host: Option<String>,
}
10 changes: 10 additions & 0 deletions generated/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export interface ClientConfigDTO {
log_level: LogLevelDTO;
}

export interface ConnectInfo {
ws_path: string;
host?: string;
}

export interface DebounceDTO {
kind: string;
ms: string;
Expand Down Expand Up @@ -48,6 +53,11 @@ export interface GetServersMessageResponseDTO {
servers: ServerDTO[];
}

export interface InjectConfig {
connect: ConnectInfo;
ctx_message: string;
}

export interface InputAcceptedDTO {
path: string;
}
Expand Down
10 changes: 10 additions & 0 deletions generated/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ var logLevelDTOSchema = z.nativeEnum(LogLevelDTO);
var clientConfigDTOSchema = z.object({
log_level: logLevelDTOSchema
});
var connectInfoSchema = z.object({
ws_path: z.string(),
host: z.string().optional()
});
var debounceDTOSchema = z.object({
kind: z.string(),
ms: z.string()
Expand Down Expand Up @@ -64,6 +68,10 @@ var serverDTOSchema = z.object({
var getServersMessageResponseDTOSchema = z.object({
servers: z.array(serverDTOSchema)
});
var injectConfigSchema = z.object({
connect: connectInfoSchema,
ctx_message: z.string()
});
var inputAcceptedDTOSchema = z.object({
path: z.string()
});
Expand Down Expand Up @@ -295,12 +303,14 @@ export {
changeKindSchema,
clientConfigDTOSchema,
clientEventSchema,
connectInfoSchema,
debounceDTOSchema,
eventLevelSchema,
externalEventsDTOSchema,
fileChangedDTOSchema,
filesChangedDTOSchema,
getServersMessageResponseDTOSchema,
injectConfigSchema,
inputAcceptedDTOSchema,
inputErrorDTOSchema,
internalEventsDTOSchema,
Expand Down
10 changes: 10 additions & 0 deletions generated/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export const clientConfigDTOSchema = z.object({
log_level: logLevelDTOSchema,
});

export const connectInfoSchema = z.object({
ws_path: z.string(),
host: z.string().optional(),
});

export const debounceDTOSchema = z.object({
kind: z.string(),
ms: z.string(),
Expand Down Expand Up @@ -53,6 +58,11 @@ export const getServersMessageResponseDTOSchema = z.object({
servers: z.array(serverDTOSchema),
});

export const injectConfigSchema = z.object({
connect: connectInfoSchema,
ctx_message: z.string(),
});

export const inputAcceptedDTOSchema = z.object({
path: z.string(),
});
Expand Down
4 changes: 2 additions & 2 deletions inject/dist/index.js

Large diffs are not rendered by default.

99 changes: 55 additions & 44 deletions inject/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,61 @@ import { Producer } from "./producers/producer";
import { ws } from "./producers/ws";
import { consolePlugin, NULL_CONSOLE } from "./sinks/console";
import { domPlugin } from "./sinks/dom";
import { InjectConfig } from "@browsersync/generated/dto";
import { injectConfigSchema } from "@browsersync/generated/schema";

// const [logEvent$, log] = createLogStream();
const producer: Producer = ws();
const clientEvent$ = producer.create();

const [logEvent$, log] = consolePlugin.globalSetup(clientEvent$, NULL_CONSOLE);
const [domEvents$, domApis] = domPlugin.globalSetup(clientEvent$, log);

// prettier-ignore
const connection$ = clientEvent$.pipe(
filter((x) => x.kind === "WsConnection"),
map((x) => x.payload),
share(),
);

// prettier-ignore
const config$ = clientEvent$.pipe(
filter((x) => x.kind === "Config"),
map((x) => x.payload)
);

// prettier-ignore
const change$ = clientEvent$.pipe(
filter((x) => x.kind === "Change"),
map(x => x.payload)
);

/**
* Side effects - this is where we react to incoming WS events
*/
merge(config$, connection$)
.pipe(
switchMap((config) => {
const sinks: Observable<unknown>[] = [
domPlugin.resetSink(domEvents$, domApis, config),
consolePlugin.resetSink(logEvent$, log, config),
];
return merge(...sinks);
}),
)
.subscribe();

connection$.subscribe((config) => {
log.info("🟢 Browsersync Live connected", { config });
});
((injectConfig) => {
injectConfigSchema.parse(injectConfig);

const producer: Producer = ws();
const clientEvent$ = producer.create(injectConfig.connect);

const [logEvent$, log] = consolePlugin.globalSetup(
clientEvent$,
NULL_CONSOLE,
);
const [domEvents$, domApis] = domPlugin.globalSetup(clientEvent$, log);

// prettier-ignore
const connection$ = clientEvent$.pipe(
filter((x) => x.kind === "WsConnection"),
map((x) => x.payload),
share()
);

// prettier-ignore
const config$ = clientEvent$.pipe(
filter((x) => x.kind === "Config"),
map((x) => x.payload)
);

// prettier-ignore
const change$ = clientEvent$.pipe(
filter((x) => x.kind === "Change"),
map(x => x.payload)
);

/**
* Side effects - this is where we react to incoming WS events
*/
merge(config$, connection$)
.pipe(
switchMap((config) => {
const sinks: Observable<unknown>[] = [
domPlugin.resetSink(domEvents$, domApis, config),
consolePlugin.resetSink(logEvent$, log, config),
];
return merge(...sinks);
}),
)
.subscribe();

connection$.subscribe((config) => {
log.info("🟢 Browsersync Live connected", { config });
});
})(window.$BSLIVE_INJECT_CONFIG$);

export {};

// todo: share this with tests
declare global {
Expand All @@ -56,5 +66,6 @@ declare global {
calls?: any[];
record?: (...args: any[]) => void;
};
$BSLIVE_INJECT_CONFIG$: InjectConfig;
}
}
4 changes: 2 additions & 2 deletions inject/src/producers/producer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ClientEvent } from "@browsersync/generated/dto";
import { ClientEvent, ConnectInfo } from "@browsersync/generated/dto";
import { Observable } from "rxjs";

export interface Producer {
create(): Observable<ClientEvent>;
create(connectInfo: ConnectInfo): Observable<ClientEvent>;
}
23 changes: 18 additions & 5 deletions inject/src/producers/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@ import { retry } from "rxjs";

export function ws(): Producer {
return {
create: () => {
const url = new URL(window.location.href);
create: (connectInfo) => {
const current = new URL(window.location.href);

url.protocol = url.protocol === "http:" ? "ws" : "wss";
url.pathname = "/__bs_ws";
// only use 'wss' protocol when we know it's safe to do so
const ws_proto = current.protocol === "https:" ? "wss" : "ws";

const socket = webSocket<ClientEvent>(url.origin + url.pathname);
let ws_url;
if (connectInfo.host) {
ws_url = new URL(
connectInfo.ws_path,
ws_proto + "://" + connectInfo.host,
);
} else {
const clone = new URL(current);
clone.protocol = ws_proto;
clone.pathname = connectInfo.ws_path;
ws_url = clone;
}

const socket = webSocket<ClientEvent>(ws_url.toString());
return socket.pipe(retry({ delay: 5000 }));
},
};
Expand Down
Loading

0 comments on commit b388b47

Please sign in to comment.