Compare commits
2 Commits
282d0c6630
...
5f061f92eb
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f061f92eb | |||
| 35a50fa97d |
@ -1,4 +1,4 @@
|
||||
!include "MUI2.nsh"
|
||||
!include "MUI2.nsh"
|
||||
!include "LogicLib.nsh"
|
||||
!include "installer.nsh"
|
||||
|
||||
@ -10,6 +10,10 @@
|
||||
!define VERSION "0.1.0"
|
||||
!endif
|
||||
|
||||
!ifndef BUILD_NUMBER
|
||||
!define BUILD_NUMBER "dev"
|
||||
!endif
|
||||
|
||||
!ifndef APP_SOURCE_DIR
|
||||
!define APP_SOURCE_DIR "app"
|
||||
!endif
|
||||
@ -21,6 +25,7 @@ InstallDirRegKey HKLM "Software\Tom Butcher\Farm Control" "InstallDir"
|
||||
RequestExecutionLevel admin
|
||||
|
||||
!define MUI_ABORTWARNING
|
||||
!define MUI_BRANDINGTEXT "Farm Control v${VERSION}-b${BUILD_NUMBER} Installer"
|
||||
|
||||
!ifndef INSTALLER_ICON
|
||||
!define INSTALLER_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
|
||||
|
||||
@ -8,6 +8,8 @@ param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Version,
|
||||
|
||||
[string]$BuildNumber = "dev",
|
||||
|
||||
[long]$MinInstallerBytes = 10485760
|
||||
)
|
||||
|
||||
@ -97,6 +99,7 @@ $makensisArgs = @(
|
||||
"/NOCD"
|
||||
"/DOUTFILE=$localInstallerName"
|
||||
"/DVERSION=$Version"
|
||||
"/DBUILD_NUMBER=$BuildNumber"
|
||||
"/DAPP_SOURCE_DIR=app"
|
||||
)
|
||||
|
||||
|
||||
@ -447,6 +447,15 @@ function buildWindowsNsis(appDir, arch) {
|
||||
artifactDir,
|
||||
getReleaseArtifactName(version, arch, 'exe')
|
||||
)
|
||||
const buildInfoPath = path.join(rootDir, 'src/buildInfo.json')
|
||||
const buildInfo = existsSync(buildInfoPath)
|
||||
? JSON.parse(readFileSync(buildInfoPath, 'utf8'))
|
||||
: {}
|
||||
const buildNumber =
|
||||
process.env.BUILD_NUMBER ||
|
||||
process.env.VITE_BUILD_NUMBER ||
|
||||
buildInfo.buildNumber ||
|
||||
'dev'
|
||||
const powershell = process.env.SystemRoot
|
||||
? path.join(
|
||||
process.env.SystemRoot,
|
||||
@ -470,7 +479,9 @@ function buildWindowsNsis(appDir, arch) {
|
||||
'-OutputExe',
|
||||
exePath,
|
||||
'-Version',
|
||||
version
|
||||
version,
|
||||
'-BuildNumber',
|
||||
buildNumber
|
||||
],
|
||||
{ stdio: 'inherit' }
|
||||
)
|
||||
|
||||
@ -11,7 +11,10 @@ import { join } from 'node:path'
|
||||
export const PROTOCOL_PREFIX = 'farmcontrol://'
|
||||
export const WINDOWS_PIPE_NAME = '\\\\.\\pipe\\com.tombutcher.farmcontrol.instance'
|
||||
export const SIGNAL_FILE = 'instance-signal.json'
|
||||
export const LOCK_FILE = 'primary.lock'
|
||||
const FORWARD_TIMEOUT_MS = 750
|
||||
const PIPE_RETRY_ATTEMPTS = 3
|
||||
const PIPE_RETRY_DELAY_MS = 100
|
||||
|
||||
export function getInstanceDir() {
|
||||
if (process.platform === 'win32') {
|
||||
@ -28,6 +31,37 @@ export function getSignalPath() {
|
||||
return join(getInstanceDir(), SIGNAL_FILE)
|
||||
}
|
||||
|
||||
export function getLockPath() {
|
||||
return join(getInstanceDir(), LOCK_FILE)
|
||||
}
|
||||
|
||||
export function isProcessAlive(pid) {
|
||||
if (!Number.isInteger(pid) || pid <= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isPrimaryInstanceRunning() {
|
||||
const lockPath = getLockPath()
|
||||
if (!existsSync(lockPath)) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const existingPid = Number.parseInt(readFileSync(lockPath, 'utf8').trim(), 10)
|
||||
return isProcessAlive(existingPid)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function findProtocolUrl(args) {
|
||||
const directMatch = args.find(
|
||||
(arg) => typeof arg === 'string' && arg.startsWith(PROTOCOL_PREFIX)
|
||||
@ -117,7 +151,7 @@ export function writeDeeplinkSignal(payload) {
|
||||
unlinkSync(tempPath)
|
||||
}
|
||||
|
||||
export function forwardDeeplinkToRunningInstance(payload) {
|
||||
function tryForwardViaPipe(payload) {
|
||||
return new Promise((resolve) => {
|
||||
let settled = false
|
||||
const finish = (forwarded) => {
|
||||
@ -126,7 +160,7 @@ export function forwardDeeplinkToRunningInstance(payload) {
|
||||
resolve(forwarded)
|
||||
}
|
||||
|
||||
const client = net.connect(WINDOWS_PIPE_NAME)
|
||||
const client = net.connect({ path: WINDOWS_PIPE_NAME })
|
||||
const message = JSON.stringify(payload)
|
||||
|
||||
client.on('connect', () => {
|
||||
@ -145,3 +179,38 @@ export function forwardDeeplinkToRunningInstance(payload) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
||||
|
||||
async function tryForwardViaPipeWithRetries(payload) {
|
||||
for (let attempt = 0; attempt < PIPE_RETRY_ATTEMPTS; attempt += 1) {
|
||||
if (await tryForwardViaPipe(payload)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (attempt < PIPE_RETRY_ATTEMPTS - 1) {
|
||||
await delay(PIPE_RETRY_DELAY_MS)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export async function forwardDeeplinkToRunningInstance(payload) {
|
||||
if (process.platform === 'win32') {
|
||||
if (await tryForwardViaPipeWithRetries(payload)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (isPrimaryInstanceRunning()) {
|
||||
writeDeeplinkSignal(payload)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@ -9,20 +9,19 @@ import {
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import net from 'node:net'
|
||||
import { join } from 'node:path'
|
||||
import {
|
||||
buildDeeplinkPayload,
|
||||
findProtocolUrl,
|
||||
forwardDeeplinkToRunningInstance,
|
||||
getInstanceDir,
|
||||
getLockPath,
|
||||
getSignalPath,
|
||||
isProcessAlive,
|
||||
SIGNAL_FILE,
|
||||
WINDOWS_PIPE_NAME,
|
||||
writeDeeplinkSignal
|
||||
} from './deeplink-ipc.js'
|
||||
|
||||
const LOCK_FILE = 'primary.lock'
|
||||
|
||||
let lockFd = null
|
||||
let pollInterval = null
|
||||
let fsWatcher = null
|
||||
@ -33,23 +32,6 @@ let handlers = {
|
||||
}
|
||||
const pendingMessages = []
|
||||
|
||||
function getLockPath() {
|
||||
return join(getInstanceDir(), LOCK_FILE)
|
||||
}
|
||||
|
||||
function isProcessAlive(pid) {
|
||||
if (!Number.isInteger(pid) || pid <= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function tryAcquirePrimaryLock() {
|
||||
mkdirSync(getInstanceDir(), { recursive: true })
|
||||
const lockPath = getLockPath()
|
||||
@ -247,7 +229,7 @@ function startWindowsPipeServer() {
|
||||
}
|
||||
})
|
||||
|
||||
server.listen(WINDOWS_PIPE_NAME)
|
||||
server.listen({ path: WINDOWS_PIPE_NAME })
|
||||
pipeServer = server
|
||||
}
|
||||
|
||||
@ -274,6 +256,7 @@ export async function ensureSingleInstanceLock({ launchUrl } = {}) {
|
||||
}
|
||||
|
||||
startWindowsPipeServer()
|
||||
startInstanceSignalWatcher()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user