Skip to content

Commit 9bc9604

Browse files
committed
fix: reuse existing simdeck services
1 parent 600b258 commit 9bc9604

4 files changed

Lines changed: 88 additions & 7 deletions

File tree

packages/vscode-extension/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# SimDeck VS Code Extension
22

3-
This extension opens the local SimDeck browser client inside a VS Code webview panel. It starts or reuses the project daemon through `simdeck ui`.
3+
This extension opens the local SimDeck browser client inside a VS Code webview panel. It uses the configured server URL when available, falls back to the default always-on service at `http://127.0.0.1:4310`, and starts or reuses a project daemon through `simdeck ui` only when needed.
44

55
## Commands
66

packages/vscode-extension/extension.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const { spawn } = require("node:child_process");
88
let outputChannel;
99
let simulatorPanel;
1010

11+
const DEFAULT_SERVICE_URL = "http://127.0.0.1:4310";
12+
1113
function activate(context) {
1214
outputChannel = vscode.window.createOutputChannel("SimDeck");
1315

@@ -49,17 +51,39 @@ async function resolveSimulatorUrl(context) {
4951
return serverUrl;
5052
}
5153

54+
const serviceUrl = await resolveExistingServiceUrl(serverUrl);
55+
if (serviceUrl) {
56+
outputChannel.appendLine(`Using existing SimDeck service at ${serviceUrl}`);
57+
return serviceUrl;
58+
}
59+
5260
const config = vscode.workspace.getConfiguration("simdeck");
5361
const autoStart = getAutoStartDaemon(config);
5462
if (!autoStart) {
5563
throw new Error(
56-
`SimDeck is not reachable at ${serverUrl}. Enable auto-start or launch the daemon manually.`,
64+
`SimDeck is not reachable at ${serverUrl} or ${DEFAULT_SERVICE_URL}. Enable auto-start or launch the daemon manually.`,
5765
);
5866
}
5967

6068
return await startProjectDaemon(context);
6169
}
6270

71+
async function resolveExistingServiceUrl(preferredUrl) {
72+
for (const serviceUrl of serviceUrlCandidates(preferredUrl)) {
73+
if (await isServerHealthy(serviceUrl)) {
74+
return serviceUrl;
75+
}
76+
}
77+
return "";
78+
}
79+
80+
function serviceUrlCandidates(preferredUrl) {
81+
if (sameOrigin(preferredUrl, DEFAULT_SERVICE_URL)) {
82+
return [];
83+
}
84+
return [DEFAULT_SERVICE_URL];
85+
}
86+
6387
function getAutoStartDaemon(config) {
6488
const daemonSetting = config.inspect("autoStartDaemon");
6589
if (
@@ -300,6 +324,14 @@ function getOrigin(value) {
300324
return `${url.protocol}//${url.host}`;
301325
}
302326

327+
function sameOrigin(left, right) {
328+
try {
329+
return getOrigin(left) === getOrigin(right);
330+
} catch {
331+
return false;
332+
}
333+
}
334+
303335
function escapeHtml(value) {
304336
return value
305337
.replaceAll("&", "&")

packages/vscode-extension/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"simdeck.serverUrl": {
4545
"type": "string",
4646
"default": "http://127.0.0.1:4310",
47-
"description": "Preferred URL loaded inside the simulator webview. If auto-start launches a daemon on a different port, the extension uses the daemon URL returned by the CLI."
47+
"description": "Preferred URL loaded inside the simulator webview. If this URL is not reachable, the extension tries the default always-on service URL before auto-starting a project daemon."
4848
},
4949
"simdeck.cliPath": {
5050
"type": "string",
@@ -66,7 +66,7 @@
6666
"simdeck.autoStartDaemon": {
6767
"type": "boolean",
6868
"default": true,
69-
"description": "Start or reuse the project daemon with `simdeck ui` when opening the simulator view if the preferred URL is not already reachable."
69+
"description": "Start or reuse a daemon with `simdeck ui` when opening the simulator view if neither the preferred URL nor the default service URL is already reachable."
7070
},
7171
"simdeck.autoStartServer": {
7272
"type": "boolean",

server/src/service.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use std::time::{Duration, Instant};
99

1010
const SERVICE_LABEL: &str = "org.nativescript.simdeck";
1111
const LEGACY_SERVICE_LABELS: &[&str] = &["dev.nativescript.simdeck"];
12+
const SERVICE_SHUTDOWN_GRACE: Duration = Duration::from_millis(750);
13+
const SERVICE_KILL_GRACE: Duration = Duration::from_millis(500);
1214

1315
#[derive(Clone, Debug)]
1416
pub struct ServiceInstallResult {
@@ -25,6 +27,9 @@ pub struct ServiceInstallResult {
2527

2628
pub fn enable(mut options: ServiceOptions) -> anyhow::Result<()> {
2729
preserve_or_create_credentials(&mut options);
30+
if let Some(result) = reuse_running_service_if_matching(&options)? {
31+
return print_install_result(&result);
32+
}
2833
let result = install(options)?;
2934
print_install_result(&result)
3035
}
@@ -257,7 +262,9 @@ fn reuse_running_service_if_matching(
257262
Some(path) => path.clone(),
258263
None => default_client_root()?,
259264
};
260-
if !service_options_match_arguments(&arguments, options, &client_root) {
265+
if enable_action_for_installed_arguments(Some(&arguments), options, &client_root)
266+
!= ServiceEnableAction::Reuse
267+
{
261268
return Ok(None);
262269
}
263270
let plist_path = plist_path()?;
@@ -275,6 +282,25 @@ fn reuse_running_service_if_matching(
275282
}))
276283
}
277284

285+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
286+
enum ServiceEnableAction {
287+
Reuse,
288+
Install,
289+
}
290+
291+
fn enable_action_for_installed_arguments(
292+
arguments: Option<&[String]>,
293+
options: &ServiceOptions,
294+
client_root: &Path,
295+
) -> ServiceEnableAction {
296+
match arguments {
297+
Some(arguments) if service_options_match_arguments(arguments, options, client_root) => {
298+
ServiceEnableAction::Reuse
299+
}
300+
_ => ServiceEnableAction::Install,
301+
}
302+
}
303+
278304
fn service_options_match_arguments(
279305
arguments: &[String],
280306
options: &ServiceOptions,
@@ -418,7 +444,7 @@ fn unload_existing_services(domain: &str) -> anyhow::Result<()> {
418444
.output();
419445
}
420446
if let Some(pid) = old_pid {
421-
terminate_process_group(pid, Duration::from_secs(5));
447+
terminate_process_group(pid, SERVICE_SHUTDOWN_GRACE);
422448
}
423449
}
424450
Ok(())
@@ -470,7 +496,7 @@ fn terminate_process_group(pid: u32, timeout: Duration) {
470496
}
471497
signal_process_group(pid, "KILL");
472498
signal_process(pid, "KILL");
473-
let _ = wait_for_process_exit(pid, Duration::from_secs(2));
499+
let _ = wait_for_process_exit(pid, SERVICE_KILL_GRACE);
474500
}
475501

476502
fn signal_process(pid: u32, signal: &str) {
@@ -801,4 +827,27 @@ mod tests {
801827
Path::new("/tmp/client")
802828
));
803829
}
830+
831+
#[test]
832+
fn enable_action_reuses_matching_installed_service() {
833+
let mut options = service_options_for_test();
834+
options.access_token = Some("token".to_owned());
835+
options.pairing_code = Some("123456".to_owned());
836+
let arguments = service_arguments_for_test(&options);
837+
838+
assert_eq!(
839+
enable_action_for_installed_arguments(
840+
Some(&arguments),
841+
&options,
842+
Path::new("/tmp/client")
843+
),
844+
ServiceEnableAction::Reuse
845+
);
846+
}
847+
848+
#[test]
849+
fn service_shutdown_grace_period_stays_short() {
850+
assert!(SERVICE_SHUTDOWN_GRACE <= Duration::from_secs(1));
851+
assert!(SERVICE_KILL_GRACE <= Duration::from_secs(1));
852+
}
804853
}

0 commit comments

Comments
 (0)