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
9 changes: 8 additions & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import { secureWindow } from './security'
import { SafeModeFrame } from './safe-mode-frame'
import { desktopResourceUrl, installDesktopProtocol, registerDesktopScheme, SAFE_MODE_PAGE } from './desktop-protocol'
import { ensureLaunchRoot } from './state/launch-root'
import { initializeDesktopInstall } from './state/desktop-startup-install'
import {
listInstalledProfilePlugins,
pruneUnresolvableProfileBundles,
Expand Down Expand Up @@ -3557,7 +3558,13 @@ if (isDaemonLaunch(process.env, process.platform)) {
// and the splash instead of blocking the main process right before the
// Harness spawn. Only the instance that will actually launch pays for it.
void prewarmShellEnvironment()
initializeDesktopService()
// Classify before DesktopService creates installation.json and bootstrap
// creates launch-root; either path would otherwise look like legacy data.
initializeDesktopInstall({
userDataPath: app.getPath('userData'),
appVersion: app.getVersion(),
developmentBuild
}, initializeDesktopService)
app.on('second-instance', (_event, argv) => {
if (!isUserInitiatedInstance(argv)) return
if (shouldStartInSafeMode(argv)) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/state/desktop-startup-install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { classifyDesktopInstall, type ClassifyDesktopInstallOptions } from './desktop-install-state'

/** Persist the first-install decision before startup services create legacy evidence. */
export function initializeDesktopInstall(
options: ClassifyDesktopInstallOptions,
initializeService: () => void
): void {
classifyDesktopInstall(options)
initializeService()
}
53 changes: 53 additions & 0 deletions test/desktop-startup-install.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises'
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { initializeDesktopInstall } from '../src/main/state/desktop-startup-install'
import { desktopInstallStatePath } from '../src/main/state/desktop-install-state'

const roots: string[] = []

async function userData(): Promise<string> {
const root = await mkdtemp(join(tmpdir(), 'dsh-startup-install-'))
roots.push(root)
return root
}

afterEach(async () => {
await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true })))
})

describe('desktop startup install classification', () => {
it('persists a new install before the desktop service creates legacy evidence', async () => {
const root = await userData()
let classificationAtServiceStart: string | undefined
initializeDesktopInstall({
userDataPath: root,
appVersion: '0.11.0',
developmentBuild: false
}, () => {
classificationAtServiceStart = JSON.parse(
// The real DesktopService creates installation.json at this point.
readFileSync(desktopInstallStatePath(join(root, 'harness')), 'utf8')
).classification
mkdirSync(join(root, 'desktop-service'), { recursive: true })
writeFileSync(join(root, 'desktop-service', 'installation.json'), '{}')
})

expect(classificationAtServiceStart).toBe('new')
expect(JSON.parse(await readFile(desktopInstallStatePath(join(root, 'harness')), 'utf8')).classification).toBe('new')
})

it('keeps an existing install ineligible when the service starts', async () => {
const root = await userData()
await mkdir(join(root, 'launch-root'))
let classificationAtServiceStart: string | undefined
initializeDesktopInstall({ userDataPath: root, appVersion: '0.11.0', developmentBuild: false }, () => {
classificationAtServiceStart = readFileSync(
desktopInstallStatePath(join(root, 'harness')), 'utf8'
)
})
expect(JSON.parse(classificationAtServiceStart ?? '').classification).toBe('existing')
})
})
Loading