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
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.0.65",
"version": "0.0.66",
"private": true,
"type": "module",
"main": "dist-electron/boot.cjs",
Expand Down
7 changes: 2 additions & 5 deletions apps/desktop/src/app/DesktopApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,9 @@ const bootstrap = Effect.gen(function* () {
yield* logBootstrapInfo("bootstrap enabled network access", {
endpointUrl: serverExposureState.endpointUrl,
});
} else if (
settings.serverExposureMode === "network-accessible" &&
serverExposureState.mode === "local-only"
) {
} else if (serverExposureState.mode === "network-accessible") {
yield* logBootstrapWarning(
"bootstrap fell back to local-only because no advertised network host was available",
"bootstrap enabled network access before any network address was available",
);
}

Expand Down
60 changes: 38 additions & 22 deletions apps/desktop/src/backend/DesktopServerExposure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,25 @@ function makeEnvironmentLayer(baseDir: string, env: Record<string, string | unde
);
}

/** A fixed interface list, or a getter for one that changes mid-test. */
type NetworkInterfacesInput =
| DesktopNetworkInterfaces.NetworkInterfaces
| (() => DesktopNetworkInterfaces.NetworkInterfaces);

function makeLayer(input: {
readonly baseDir: string;
readonly networkInterfaces?: DesktopNetworkInterfaces.NetworkInterfaces;
readonly networkInterfaces?: NetworkInterfacesInput;
readonly env?: Record<string, string | undefined>;
readonly spawnerLayer?: Layer.Layer<ChildProcessSpawner.ChildProcessSpawner>;
readonly desktopSettingsLayer?: Layer.Layer<DesktopAppSettings.DesktopAppSettings>;
}) {
const env = { T3CODE_HOME: input.baseDir, ...input.env };
const environmentLayer = makeEnvironmentLayer(input.baseDir, env);
const networkLayer = Layer.succeed(DesktopNetworkInterfaces.DesktopNetworkInterfaces, {
read: Effect.succeed(input.networkInterfaces ?? emptyNetworkInterfaces),
read: Effect.sync(() => {
const networkInterfaces = input.networkInterfaces ?? emptyNetworkInterfaces;
return typeof networkInterfaces === "function" ? networkInterfaces() : networkInterfaces;
}),
});

return DesktopServerExposure.layer.pipe(
Expand All @@ -111,7 +119,7 @@ function makeLayer(input: {
}

const withHarness = <A, E, R>(
networkInterfaces: DesktopNetworkInterfaces.NetworkInterfaces,
networkInterfaces: NetworkInterfacesInput,
effect: Effect.Effect<
A,
E,
Expand Down Expand Up @@ -144,25 +152,33 @@ const withHarness = <A, E, R>(
}).pipe(Effect.provide(NodeServices.layer), Effect.scoped);

describe("DesktopServerExposure", () => {
it.effect("falls back to local-only without losing the requested network preference", () =>
withHarness(
emptyNetworkInterfaces,
Effect.gen(function* () {
const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;
const settings = yield* DesktopAppSettings.DesktopAppSettings;

yield* settings.setServerExposureMode("network-accessible");

const state = yield* serverExposure.configureFromSettings({ port: 4173 });
assert.equal(state.mode, "local-only");
assert.equal(state.endpointUrl, null);
assert.equal((yield* settings.get).serverExposureMode, "network-accessible");

const backendConfig = yield* serverExposure.backendConfig;
assert.equal(backendConfig.bindHost, "127.0.0.1");
assert.equal(backendConfig.httpBaseUrl.href, "http://127.0.0.1:4173/");
}),
),
it.effect(
"keeps network access requested at launch and advertises once an address appears",
() => {
let currentNetworkInterfaces = emptyNetworkInterfaces;
return withHarness(
() => currentNetworkInterfaces,
Effect.gen(function* () {
const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;
const settings = yield* DesktopAppSettings.DesktopAppSettings;

yield* settings.setServerExposureMode("network-accessible");

const state = yield* serverExposure.configureFromSettings({ port: 4173 });
assert.equal(state.mode, "network-accessible");
assert.equal(state.endpointUrl, null);

const backendConfig = yield* serverExposure.backendConfig;
assert.equal(backendConfig.bindHost, "0.0.0.0");
assert.equal(backendConfig.httpBaseUrl.href, "http://127.0.0.1:4173/");

currentNetworkInterfaces = lanNetworkInterfaces;
const refreshed = yield* serverExposure.getState;
assert.equal(refreshed.endpointUrl, "http://192.168.1.20:4173");
assert.equal(refreshed.advertisedHost, "192.168.1.20");
}),
);
},
);

it.effect("returns a typed error when network access is explicitly unavailable", () =>
Expand Down
41 changes: 30 additions & 11 deletions apps/desktop/src/backend/DesktopServerExposure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,20 +448,14 @@ function resolveRuntimeState(input: {
!address.internal && address.family === "IPv4" && isTailscaleIpv4Address(address.address),
),
);
const exposure = unavailable
? resolveDesktopServerExposure({
mode: "local-only",
port: input.port,
networkInterfaces: input.networkInterfaces,
...(advertisedHostOverride ? { advertisedHostOverride } : {}),
})
: requestedExposure;

// An unavailable network keeps the requested binding: a launch at login can
// beat DHCP, and the advertised host is re-read once an address appears.
return {
state: runtimeStateFromResolvedExposure({
requestedMode: input.requestedMode,
settings: input.settings,
exposure,
exposure: requestedExposure,
port: input.port,
}),
unavailable,
Expand Down Expand Up @@ -512,7 +506,32 @@ export const make = Effect.gen(function* () {
? cachedReadDefaultRouteInterface
: Effect.succeed(null);

const getState = Ref.get(stateRef).pipe(Effect.map(toContractState));
// Re-reads the advertised host so pairing links follow the network: an
// address that arrived after launch, or one DHCP changed while running.
const refreshAdvertisedHost = Effect.gen(function* () {
const state = yield* Ref.get(stateRef);
if (state.mode !== "network-accessible") return state;
const currentNetworkInterfaces = yield* readNetworkInterfaces;
const advertisedHostOverride = Option.getOrUndefined(config.desktopLanHostOverride);
const exposure = resolveDesktopServerExposure({
mode: state.mode,
port: state.port,
networkInterfaces: currentNetworkInterfaces,
preferredInterface: yield* readPreferredInterface(state.mode, currentNetworkInterfaces),
...(advertisedHostOverride ? { advertisedHostOverride } : {}),
});
return yield* Ref.updateAndGet(stateRef, (current) =>
current.mode === state.mode && current.port === state.port
? {
...current,
endpointUrl: Option.fromNullishOr(exposure.endpointUrl),
advertisedHost: Option.fromNullishOr(exposure.advertisedHost),
}
: current,
);
});

const getState = refreshAdvertisedHost.pipe(Effect.map(toContractState));
const backendConfig = Ref.get(stateRef).pipe(Effect.map(toBackendConfig));

const configureFromSettings = Effect.fn("desktop.serverExposure.configureFromSettings")(
Expand Down Expand Up @@ -613,7 +632,7 @@ export const make = Effect.gen(function* () {
);

const getAdvertisedEndpoints = Effect.gen(function* () {
const state = yield* Ref.get(stateRef);
const state = yield* refreshAdvertisedHost;
const currentNetworkInterfaces = yield* readNetworkInterfaces;
const coreEndpoints = resolveDesktopCoreAdvertisedEndpoints({
port: state.port,
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"expo-image-picker": "~58.0.9",
"expo-keep-awake": "~58.0.2",
"expo-linking": "~58.0.9",
"expo-modules-core": "~58.0.9",
"expo-modules-core": "58.0.9",
"expo-network": "~58.0.2",
"expo-notifications": "~58.0.9",
"expo-paste-input": "^0.1.15",
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "t3",
"version": "0.0.65",
"version": "0.0.66",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/web",
"version": "0.0.65",
"version": "0.0.66",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/contracts",
"version": "0.0.65",
"version": "0.0.66",
"private": true,
"files": [
"dist"
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

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

Loading