windows-development-sandbox 1.17.3 → 1.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/config.example.json +97 -4
- package/dist/app/features/accounts/accounts.def.d.ts +116 -2
- package/dist/app/features/accounts/accounts.def.js +80 -12
- package/dist/app/features/accounts/accounts.run.d.ts +29 -0
- package/dist/app/features/accounts/accounts.run.js +36 -6
- package/dist/app/features/accounts/index.d.ts +2 -0
- package/dist/app/features/accounts/index.js +4 -1
- package/dist/app/features/accounts/services.def.d.ts +111 -0
- package/dist/app/features/accounts/services.def.js +187 -0
- package/dist/app/features/accounts/services.run.d.ts +65 -0
- package/dist/app/features/accounts/services.run.js +212 -0
- package/dist/app/features/defs.d.ts +112 -1
- package/dist/app/features/defs.js +5 -0
- package/dist/app/features/settings/categories.def.js +21 -4
- package/dist/app/registry.js +5 -1
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-QLGGAG3D.js → chunk-IIHL7W6B.js} +51 -16
- package/dist/bin/{chunk-XYUVIVJT.js → chunk-M4OM632I.js} +1 -1
- package/dist/bin/{chunk-REE3EMHD.js → chunk-MQIA7GNB.js} +3813 -1702
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/commands/launch/launch.js +30 -1
- package/dist/cli/manifest.d.ts +93 -65
- package/dist/cli/manifest.js +23 -5
- package/dist/cli/menu/pages/accounts-page.js +31 -16
- package/dist/cli/services/accounts-service.d.ts +14 -4
- package/dist/cli/services/accounts-service.js +19 -6
- package/dist/cli/services/data-collect.js +26 -1
- package/dist/core/config/defaults.js +47 -4
- package/dist/core/detect/toolchains.d.ts +15 -1
- package/dist/core/detect/toolchains.js +45 -12
- package/dist/core/local-config/overrides.js +6 -0
- package/dist/core/schema/accounts.d.ts +27 -0
- package/dist/core/schema/accounts.js +32 -0
- package/dist/core/schema/config.d.ts +239 -0
- package/dist/core/schema/config.js +6 -0
- package/dist/core/schema/pc-installs.d.ts +7 -0
- package/dist/core/schema/pc-installs.js +17 -0
- package/dist/core/schema/toolchains.d.ts +61 -0
- package/dist/core/schema/toolchains.js +106 -0
- package/dist/core/types.d.ts +18 -0
- package/dist/guest/accounts/adapter.d.ts +25 -0
- package/dist/guest/accounts/adapter.js +21 -0
- package/dist/guest/accounts/adapters.d.ts +5 -0
- package/dist/guest/accounts/adapters.js +18 -0
- package/dist/guest/accounts/apply.d.ts +41 -0
- package/dist/guest/accounts/apply.js +47 -0
- package/dist/guest/accounts/kinds/appstore.d.ts +2 -0
- package/dist/guest/accounts/kinds/appstore.js +7 -0
- package/dist/guest/accounts/kinds/cfx.d.ts +2 -0
- package/dist/guest/accounts/kinds/cfx.js +7 -0
- package/dist/guest/accounts/kinds/epic.d.ts +2 -0
- package/dist/guest/accounts/kinds/epic.js +26 -0
- package/dist/guest/accounts/kinds/expo.d.ts +2 -0
- package/dist/guest/accounts/kinds/expo.js +7 -0
- package/dist/guest/accounts/kinds/keystore.d.ts +2 -0
- package/dist/guest/accounts/kinds/keystore.js +7 -0
- package/dist/guest/accounts/kinds/play.d.ts +2 -0
- package/dist/guest/accounts/kinds/play.js +7 -0
- package/dist/guest/accounts/kinds/steam.d.ts +4 -0
- package/dist/guest/accounts/kinds/steam.js +17 -0
- package/dist/guest/accounts/kinds/unity.d.ts +2 -0
- package/dist/guest/accounts/kinds/unity.js +7 -0
- package/dist/guest/accounts/watch.d.ts +28 -0
- package/dist/guest/accounts/watch.js +78 -0
- package/dist/guest/apps/resolvers-gamedev.d.ts +2 -0
- package/dist/guest/apps/resolvers-gamedev.js +1 -0
- package/dist/guest/apps/resolvers-mobile.d.ts +2 -0
- package/dist/guest/apps/resolvers-mobile.js +1 -0
- package/dist/guest/apps/resolvers.js +4 -0
- package/dist/guest/boot/boot.d.ts +5 -0
- package/dist/guest/boot/boot.js +35 -1
- package/dist/guest/daemon/accounts.d.ts +3 -0
- package/dist/guest/daemon/accounts.js +25 -0
- package/dist/guest/daemon/daemon.js +2 -0
- package/dist/guest/daemon/idle-shutdown.js +3 -1
- package/dist/guest/daemon/sessions.js +2 -0
- package/dist/guest/devtools/android.d.ts +2 -0
- package/dist/guest/devtools/android.js +7 -0
- package/dist/guest/devtools/devtool.d.ts +9 -0
- package/dist/guest/devtools/devtool.js +5 -0
- package/dist/guest/devtools/fivem.d.ts +2 -0
- package/dist/guest/devtools/fivem.js +7 -0
- package/dist/guest/devtools/registry.d.ts +6 -0
- package/dist/guest/devtools/registry.js +12 -0
- package/dist/guest/devtools/steam.d.ts +2 -0
- package/dist/guest/devtools/steam.js +228 -0
- package/dist/guest/devtools/unity.d.ts +2 -0
- package/dist/guest/devtools/unity.js +7 -0
- package/dist/guest/entries/daemon.js +2 -0
- package/dist/guest/entries/tool.d.ts +1 -0
- package/dist/guest/entries/tool.js +17 -0
- package/dist/guest/toolchains/engines/godot.d.ts +4 -0
- package/dist/guest/toolchains/engines/godot.js +4 -0
- package/dist/guest/toolchains/engines/unity-release.d.ts +15 -0
- package/dist/guest/toolchains/engines/unity-release.js +6 -0
- package/dist/guest/toolchains/engines/unity.d.ts +4 -0
- package/dist/guest/toolchains/engines/unity.js +4 -0
- package/dist/guest/toolchains/engines/unreal-engines.d.ts +4 -0
- package/dist/guest/toolchains/engines/unreal-engines.js +4 -0
- package/dist/guest/toolchains/fivem/artifacts.d.ts +6 -0
- package/dist/guest/toolchains/fivem/artifacts.js +7 -0
- package/dist/guest/toolchains/fivem/fivem.d.ts +4 -0
- package/dist/guest/toolchains/fivem/fivem.js +4 -0
- package/dist/guest/toolchains/installers.d.ts +12 -0
- package/dist/guest/toolchains/installers.js +39 -0
- package/dist/guest/toolchains/languages/java.js +1 -1
- package/dist/guest/toolchains/languages/kotlin.d.ts +3 -0
- package/dist/guest/toolchains/languages/kotlin.js +6 -0
- package/dist/guest/toolchains/mobile/android-repo.d.ts +11 -0
- package/dist/guest/toolchains/mobile/android-repo.js +10 -0
- package/dist/guest/toolchains/mobile/android.d.ts +17 -0
- package/dist/guest/toolchains/mobile/android.js +16 -0
- package/dist/guest/toolchains/mobile/dart.d.ts +4 -0
- package/dist/guest/toolchains/mobile/dart.js +4 -0
- package/dist/guest/toolchains/mobile/flutter.d.ts +4 -0
- package/dist/guest/toolchains/mobile/flutter.js +4 -0
- package/dist/guest/toolchains/mobile/react-native.d.ts +4 -0
- package/dist/guest/toolchains/mobile/react-native.js +4 -0
- package/dist/guest/toolchains/native/unreal.d.ts +3 -7
- package/dist/guest/toolchains/native/unreal.js +16 -167
- package/dist/guest/toolchains/native/visual-studio.d.ts +15 -0
- package/dist/guest/toolchains/native/visual-studio.js +159 -0
- package/dist/guest/toolchains/native/vs-request.d.ts +19 -0
- package/dist/guest/toolchains/native/vs-request.js +46 -0
- package/dist/guest/toolchains/not-yet.d.ts +2 -0
- package/dist/guest/toolchains/not-yet.js +6 -0
- package/dist/guest/toolchains/persisted.d.ts +48 -0
- package/dist/guest/toolchains/persisted.js +159 -0
- package/dist/guest/toolchains/plan.js +80 -4
- package/dist/guest/toolchains/steam/apps.d.ts +27 -0
- package/dist/guest/toolchains/steam/apps.js +112 -0
- package/dist/guest/toolchains/steam/client.d.ts +5 -0
- package/dist/guest/toolchains/steam/client.js +67 -0
- package/dist/guest/toolchains/steam/commands.d.ts +58 -0
- package/dist/guest/toolchains/steam/commands.js +155 -0
- package/dist/guest/toolchains/steam/steam.d.ts +4 -0
- package/dist/guest/toolchains/steam/steam.js +61 -0
- package/dist/guest/toolchains/steam/steamcmd-install.d.ts +29 -0
- package/dist/guest/toolchains/steam/steamcmd-install.js +177 -0
- package/dist/guest/toolchains/steam/steamcmd.d.ts +6 -0
- package/dist/guest/toolchains/steam/steamcmd.js +23 -0
- package/dist/guest/toolchains/step.d.ts +10 -0
- package/dist/host/accounts/deliver.d.ts +26 -0
- package/dist/host/accounts/deliver.js +80 -0
- package/dist/host/accounts/handoff.d.ts +17 -0
- package/dist/host/accounts/handoff.js +110 -0
- package/dist/host/accounts/index.d.ts +6 -0
- package/dist/host/accounts/index.js +8 -0
- package/dist/host/accounts/kinds/appstore.d.ts +2 -0
- package/dist/host/accounts/kinds/appstore.js +10 -0
- package/dist/host/accounts/kinds/cfx.d.ts +2 -0
- package/dist/host/accounts/kinds/cfx.js +7 -0
- package/dist/host/accounts/kinds/epic.d.ts +2 -0
- package/dist/host/accounts/kinds/epic.js +15 -0
- package/dist/host/accounts/kinds/expo.d.ts +2 -0
- package/dist/host/accounts/kinds/expo.js +7 -0
- package/dist/host/accounts/kinds/keystore.d.ts +2 -0
- package/dist/host/accounts/kinds/keystore.js +7 -0
- package/dist/host/accounts/kinds/play.d.ts +2 -0
- package/dist/host/accounts/kinds/play.js +7 -0
- package/dist/host/accounts/kinds/steam.d.ts +6 -0
- package/dist/host/accounts/kinds/steam.js +31 -0
- package/dist/host/accounts/kinds/unity.d.ts +2 -0
- package/dist/host/accounts/kinds/unity.js +13 -0
- package/dist/host/accounts/leases.d.ts +40 -0
- package/dist/host/accounts/leases.js +62 -0
- package/dist/host/accounts/source.d.ts +21 -0
- package/dist/host/accounts/source.js +15 -0
- package/dist/host/accounts/sources.d.ts +5 -0
- package/dist/host/accounts/sources.js +18 -0
- package/dist/host/accounts/store.d.ts +23 -0
- package/dist/host/accounts/store.js +74 -0
- package/dist/host/audit/log.d.ts +1 -1
- package/dist/host/launch/generate.d.ts +2 -0
- package/dist/host/launch/generate.js +16 -1
- package/dist/host/launch/hints/android.d.ts +3 -0
- package/dist/host/launch/hints/android.js +3 -0
- package/dist/host/launch/hints/engines.d.ts +3 -0
- package/dist/host/launch/hints/engines.js +3 -0
- package/dist/host/launch/hints/fivem.d.ts +3 -0
- package/dist/host/launch/hints/fivem.js +3 -0
- package/dist/host/launch/hints/hint.d.ts +2 -0
- package/dist/host/launch/hints/hint.js +1 -0
- package/dist/host/launch/hints/index.d.ts +5 -0
- package/dist/host/launch/hints/index.js +29 -0
- package/dist/host/launch/hints/mobile.d.ts +3 -0
- package/dist/host/launch/hints/mobile.js +3 -0
- package/dist/host/launch/hints/steam.d.ts +7 -0
- package/dist/host/launch/hints/steam.js +59 -0
- package/dist/host/launch/index.d.ts +2 -2
- package/dist/host/launch/index.js +2 -2
- package/dist/host/launch/mappings.js +2 -0
- package/dist/host/launch/session-file.js +2 -0
- package/dist/host/launch/stage.js +10 -1
- package/dist/host/launch/toolchains.d.ts +7 -0
- package/dist/host/launch/toolchains.js +11 -0
- package/dist/host/pc-installs/discover.d.ts +19 -0
- package/dist/host/pc-installs/discover.js +59 -0
- package/dist/host/pc-installs/epic.d.ts +2 -0
- package/dist/host/pc-installs/epic.js +7 -0
- package/dist/host/pc-installs/index.d.ts +3 -0
- package/dist/host/pc-installs/index.js +2 -0
- package/dist/host/pc-installs/source.d.ts +9 -0
- package/dist/host/pc-installs/source.js +1 -0
- package/dist/host/pc-installs/steam.d.ts +16 -0
- package/dist/host/pc-installs/steam.js +143 -0
- package/dist/host/pc-installs/unity.d.ts +2 -0
- package/dist/host/pc-installs/unity.js +7 -0
- package/dist/host/pc-installs/unreal.d.ts +2 -0
- package/dist/host/pc-installs/unreal.js +7 -0
- package/dist/host/profile/accounts.d.ts +21 -6
- package/dist/host/profile/accounts.js +108 -28
- package/dist/host/profile/claude-login.d.ts +32 -2
- package/dist/host/profile/claude-login.js +101 -8
- package/dist/host/profile/index.d.ts +1 -1
- package/dist/host/profile/index.js +1 -1
- package/dist/host/secrets/launch-secrets.js +1 -0
- package/dist/runtime/agent.js +321 -9
- package/dist/runtime/daemon.js +1987 -604
- package/dist/runtime/db.js +334 -11
- package/dist/runtime/main.js +2519 -723
- package/dist/runtime/netproxy.js +321 -9
- package/dist/runtime/tool.js +28549 -0
- package/dist/runtime/view.js +321 -9
- package/dist/selftest/accounts.test.js +172 -1
- package/dist/selftest/agent-paths.test.js +1 -0
- package/dist/selftest/android.test.d.ts +2 -0
- package/dist/selftest/android.test.js +7 -0
- package/dist/selftest/app.test.js +1 -1
- package/dist/selftest/engines.test.d.ts +2 -0
- package/dist/selftest/engines.test.js +7 -0
- package/dist/selftest/fivem.test.d.ts +2 -0
- package/dist/selftest/fivem.test.js +7 -0
- package/dist/selftest/generate.test.js +1 -1
- package/dist/selftest/index.js +18 -0
- package/dist/selftest/menu-screens.js +1 -0
- package/dist/selftest/menu.test.js +2 -1
- package/dist/selftest/mobile.test.d.ts +2 -0
- package/dist/selftest/mobile.test.js +7 -0
- package/dist/selftest/pc-installs.test.d.ts +2 -0
- package/dist/selftest/pc-installs.test.js +111 -0
- package/dist/selftest/persisted-installs.test.d.ts +2 -0
- package/dist/selftest/persisted-installs.test.js +107 -0
- package/dist/selftest/projects.test.js +6 -4
- package/dist/selftest/service-accounts.test.d.ts +2 -0
- package/dist/selftest/service-accounts.test.js +320 -0
- package/dist/selftest/steam.test.d.ts +2 -0
- package/dist/selftest/steam.test.js +316 -0
- package/dist/selftest/storage-helper.test.js +31 -8
- package/dist/selftest/support/engine-golden.js +3 -0
- package/dist/selftest/support/fixtures.d.ts +87 -0
- package/dist/selftest/support/fixtures.js +4 -3
- package/dist/selftest/support/pc-memory.d.ts +2 -0
- package/dist/selftest/support/pc-memory.js +17 -0
- package/dist/selftest/support/web-preview.js +20 -1
- package/dist/selftest/unity.test.d.ts +2 -0
- package/dist/selftest/unity.test.js +7 -0
- package/dist/shared/accounts/fingerprint.d.ts +1 -0
- package/dist/shared/accounts/fingerprint.js +20 -0
- package/dist/shared/accounts/index.d.ts +5 -0
- package/dist/shared/accounts/index.js +7 -0
- package/dist/shared/accounts/kinds/epic.d.ts +10 -0
- package/dist/shared/accounts/kinds/epic.js +56 -0
- package/dist/shared/accounts/kinds/steam.d.ts +28 -0
- package/dist/shared/accounts/kinds/steam.js +99 -0
- package/dist/shared/accounts/kinds.d.ts +41 -0
- package/dist/shared/accounts/kinds.js +154 -0
- package/dist/shared/accounts/payloads.d.ts +151 -0
- package/dist/shared/accounts/payloads.js +128 -0
- package/dist/shared/apps/catalogue-gamedev.d.ts +2 -0
- package/dist/shared/apps/catalogue-gamedev.js +1 -0
- package/dist/shared/apps/catalogue-mobile.d.ts +2 -0
- package/dist/shared/apps/catalogue-mobile.js +1 -0
- package/dist/shared/apps/catalogue-modding.d.ts +2 -0
- package/dist/shared/apps/catalogue-modding.js +1 -0
- package/dist/shared/apps/catalogue.d.ts +1 -0
- package/dist/shared/apps/catalogue.js +6 -1
- package/dist/shared/channel/schema.d.ts +183 -0
- package/dist/shared/channel/secrets.d.ts +42 -0
- package/dist/shared/channel/secrets.js +3 -0
- package/dist/shared/download/downloader.d.ts +7 -0
- package/dist/shared/download/downloader.js +10 -3
- package/dist/shared/installs.d.ts +19 -0
- package/dist/shared/installs.js +13 -0
- package/dist/shared/io/bounded.d.ts +6 -0
- package/dist/shared/io/bounded.js +38 -0
- package/dist/shared/io/index.d.ts +2 -0
- package/dist/shared/io/index.js +2 -0
- package/dist/shared/io/text.d.ts +9 -0
- package/dist/shared/io/text.js +18 -0
- package/dist/shared/paths/guest-paths.js +1 -1
- package/dist/shared/paths/index.d.ts +1 -0
- package/dist/shared/paths/index.js +1 -0
- package/dist/shared/paths/pc-installs.d.ts +20 -0
- package/dist/shared/paths/pc-installs.js +19 -0
- package/dist/shared/vdf.d.ts +25 -0
- package/dist/shared/vdf.js +151 -0
- package/dist/sync/sync-cycle.js +9 -0
- package/dist/web/index.html +1 -1
- package/dist/web/{main-JTH3FT4O.js → main-CWIF4VQI.js} +63 -61
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/package.json +1 -1
- package/sandbox/scripts/add-firewall-port.ps1 +13 -0
- package/sandbox/templates/fivem/README.txt +2 -0
- package/schemas/config.schema.json +485 -0
- package/schemas/sandbox-config.schema.json +416 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// Service sign-ins for sandboxes (accounts.<kind>.share): the sealed store, the one-holder leases, which launch gets
|
|
2
|
+
// what (and only in the one-shot file), what a sandbox hands back and what is refused, forgetting everywhere, the
|
|
3
|
+
// bounded and BOM-aware readers, the Epic launcher's ini, the sandbox's watch, the `account` command line and the
|
|
4
|
+
// shared-core flows. Everything in temp folders with SecretStore.memory().
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { accountsView, importServiceFlow, loginServiceFlow, logoutServiceFlow, SERVICE_NAMES_TEXT, servicePage, serviceRow, } from '../app/features/accounts/index.js';
|
|
8
|
+
import { fakeIO } from '../app/kernel/index.js';
|
|
9
|
+
import { buildProgram, parseCli } from '../cli/args.js';
|
|
10
|
+
import { COMMANDS } from '../cli/manifest.js';
|
|
11
|
+
import { pageLines } from '../cli/output/index.js';
|
|
12
|
+
import { DEFAULTS } from '../core/config/index.js';
|
|
13
|
+
import { applyOverrides, normalizeOverrides } from '../core/local-config/index.js';
|
|
14
|
+
import { AccountLeases, forgetEverywhere, handoffDir, HOST_SOURCES, prepareServiceAccounts, ServiceAccountStore, takeHandoffs, } from '../host/accounts/index.js';
|
|
15
|
+
import { generateSandbox } from '../host/launch/index.js';
|
|
16
|
+
import { hostDataFolder } from '../host/storage/index.js';
|
|
17
|
+
import { clearSecrets, SecretStore } from '../host/secrets/index.js';
|
|
18
|
+
import { applyServiceAccounts, readDelivered } from '../guest/accounts/apply.js';
|
|
19
|
+
import { AccountWatch } from '../guest/accounts/watch.js';
|
|
20
|
+
import { epicAdapter } from '../guest/accounts/kinds/epic.js';
|
|
21
|
+
import { epicSettingsFile, fingerprint, readEpicLogin, SERVICE_KINDS, SERVICE_SPECS, serviceWanted, withEpicLogin, } from '../shared/accounts/index.js';
|
|
22
|
+
import { RunDir } from '../shared/channel/index.js';
|
|
23
|
+
import { decodeText, encodeText, readBoundedFile } from '../shared/io/index.js';
|
|
24
|
+
const SIGN_IN = 'PlantedEpicSignIn' + 'AbCdEf0123456789+/'.repeat(6) + '==';
|
|
25
|
+
const RENEWED = 'PlantedRenewed' + 'ZyXw9876543210'.repeat(6);
|
|
26
|
+
const CFX = 'cfxk_PlantedServerKey_0123456789';
|
|
27
|
+
const INI = [
|
|
28
|
+
'[Launcher]',
|
|
29
|
+
'DefaultAppInstallLocation=C:\\Games',
|
|
30
|
+
'',
|
|
31
|
+
'[RememberMe]',
|
|
32
|
+
'Enable=True',
|
|
33
|
+
'Data=' + SIGN_IN,
|
|
34
|
+
'',
|
|
35
|
+
'[Offline]',
|
|
36
|
+
'Data=keep-me',
|
|
37
|
+
'',
|
|
38
|
+
].join('\r\n');
|
|
39
|
+
function allText(dir) {
|
|
40
|
+
return fs
|
|
41
|
+
.readdirSync(dir, { recursive: true, withFileTypes: true })
|
|
42
|
+
.filter((e) => e.isFile())
|
|
43
|
+
.map((e) => fs.readFileSync(path.join(e.parentPath, e.name), 'utf8'));
|
|
44
|
+
}
|
|
45
|
+
const UNREAL = new Set(['unreal']);
|
|
46
|
+
const notRunning = () => false;
|
|
47
|
+
export const serviceAccountsSuite = {
|
|
48
|
+
name: 'service-accounts',
|
|
49
|
+
title: 'service sign-ins: store, leases, delivery gates, handoffs, forgetting, readers, the watch, CLI and flows',
|
|
50
|
+
run: async (t, f) => {
|
|
51
|
+
t.section('the kinds');
|
|
52
|
+
t.equal(SERVICE_NAMES_TEXT, SERVICE_KINDS.slice(0, -1).join(', ') + ' or ' + SERVICE_KINDS.at(-1), 'the help names every kind');
|
|
53
|
+
t.check(SERVICE_KINDS.every((k) => DEFAULTS.accounts[k].share === false), 'no sign-in is shared by default');
|
|
54
|
+
t.check(serviceWanted('epic', UNREAL) &&
|
|
55
|
+
!serviceWanted('epic', new Set(['steam'])) &&
|
|
56
|
+
serviceWanted('keystore', new Set(['flutter'])) &&
|
|
57
|
+
serviceWanted('keystore', new Set(['unity'])) &&
|
|
58
|
+
!serviceWanted('cfx', new Set(['android'])), 'a sign-in is wanted only by its toolchains (Flutter, React Native, Godot and Unity build for Android)');
|
|
59
|
+
t.section("the Epic launcher's settings file");
|
|
60
|
+
t.equal(readEpicLogin(INI), { data: SIGN_IN }, 'the sign-in is read from [RememberMe] only');
|
|
61
|
+
t.equal(readEpicLogin(INI.replace('Enable=True', 'Enable=False')), null, 'a turned-off "Remember me" is none');
|
|
62
|
+
t.equal(readEpicLogin(INI.replace(SIGN_IN, 'not base64 !')), null, 'a value of the wrong shape is refused');
|
|
63
|
+
t.equal(readEpicLogin('[Launcher]\r\nData=' + SIGN_IN), null, 'a Data line in another section is not the sign-in');
|
|
64
|
+
const written = withEpicLogin(INI, { data: RENEWED });
|
|
65
|
+
t.check(readEpicLogin(written)?.data === RENEWED &&
|
|
66
|
+
!written.includes(SIGN_IN) &&
|
|
67
|
+
written.includes('DefaultAppInstallLocation=C:\\Games') &&
|
|
68
|
+
written.includes('Data=keep-me') &&
|
|
69
|
+
written.split('[RememberMe]').length === 2, 'writing replaces only [RememberMe] and keeps the other sections');
|
|
70
|
+
t.section('bounded and BOM-aware reading');
|
|
71
|
+
const utf16 = encodeText(INI, 'utf16le');
|
|
72
|
+
const decoded = decodeText(utf16);
|
|
73
|
+
t.check(decoded.encoding === 'utf16le' &&
|
|
74
|
+
decoded.text === INI &&
|
|
75
|
+
encodeText(decoded.text, decoded.encoding).equals(utf16) &&
|
|
76
|
+
decodeText(encodeText('x', 'utf8bom')).text === 'x' &&
|
|
77
|
+
decodeText(Buffer.from('plain')).encoding === 'utf8', 'UTF-16LE and UTF-8 (with or without a mark) round-trip in the encoding they were read in');
|
|
78
|
+
const grows = t.write(t.at('bounded', 'grows.txt'), 'x'.repeat(100));
|
|
79
|
+
t.check(readBoundedFile(grows, 100)?.length === 100, 'a file within the bound is read');
|
|
80
|
+
t.check(readBoundedFile(grows, 99) === null, 'a file over the bound is not read');
|
|
81
|
+
t.check(readBoundedFile(grows, 120, { afterStat: () => fs.appendFileSync(grows, 'y'.repeat(50)) }) === null, 'a file that grows past the bound between the size check and the read is still refused');
|
|
82
|
+
t.check(readBoundedFile(t.at('bounded', 'missing.txt'), 10) === null, 'a missing file reads as nothing');
|
|
83
|
+
t.section('the sealed store');
|
|
84
|
+
const storage = t.dir('svc-storage');
|
|
85
|
+
const store = new ServiceAccountStore(storage, SecretStore.memory());
|
|
86
|
+
t.check(store.state('epic') === 'none' && store.info('epic') === null, 'nothing is saved at first');
|
|
87
|
+
store.save('epic', { data: SIGN_IN }, 'pc');
|
|
88
|
+
const file = path.join(storage, 'accounts', 'epic.json');
|
|
89
|
+
t.check(store.state('epic') === 'saved' && !fs.readFileSync(file, 'utf8').includes(SIGN_IN), 'it is saved sealed: the sign-in is not in the file');
|
|
90
|
+
t.equal(store.read('epic'), { data: SIGN_IN }, 'and it opens for the launch');
|
|
91
|
+
t.equal(store.info('epic')?.source, 'pc', 'where it came from is kept');
|
|
92
|
+
await t.throws(() => store.save('cfx', { licenseKey: 'not a key' }, 'typed'), '', 'a payload of the wrong shape is refused');
|
|
93
|
+
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace(/"sealed": "[^"]*"/, '"sealed": "dpapi1:AAAA"'));
|
|
94
|
+
t.check(store.state('epic') === 'unreadable', 'a sign-in this Windows user cannot open is reported as unreadable');
|
|
95
|
+
t.check(store.remove('epic') && store.state('epic') === 'none' && !store.remove('epic'), 'removing deletes it (once)');
|
|
96
|
+
t.section('leases');
|
|
97
|
+
const leases = new AccountLeases(t.dir('svc-leases'));
|
|
98
|
+
t.equal(leases.claim('steam', 'default', 'L1', null, notRunning), { ok: true }, 'a free lease is claimed');
|
|
99
|
+
t.equal(leases.claim('steam', 'other', 'L2', null, (set) => set === 'default'), { ok: false, heldBy: 'default' }, 'one holder: another data set is refused while its sandbox runs');
|
|
100
|
+
t.equal(leases.claim('steam', 'other', 'L3', null, notRunning), { ok: true }, 'a stale lease is taken over');
|
|
101
|
+
t.equal(leases.holder('steam')?.launchId, 'L3', 'and names the new launch');
|
|
102
|
+
leases.releaseAll('other');
|
|
103
|
+
t.equal(leases.holder('steam'), null, 'releasing a data set lets go of its leases');
|
|
104
|
+
t.section('which launch gets what');
|
|
105
|
+
const config = f.makeConfig();
|
|
106
|
+
const root = config.storage.root;
|
|
107
|
+
const signIns = new ServiceAccountStore(root, SecretStore.memory());
|
|
108
|
+
const launchLeases = new AccountLeases(root);
|
|
109
|
+
const run = await f.run({}, config);
|
|
110
|
+
const deliver = (over = {}) => prepareServiceAccounts({
|
|
111
|
+
config,
|
|
112
|
+
others: [],
|
|
113
|
+
on: UNREAL,
|
|
114
|
+
storageRoot: root,
|
|
115
|
+
dataPath: run.dataPath,
|
|
116
|
+
launchId: 'L-boot',
|
|
117
|
+
isRunning: notRunning,
|
|
118
|
+
store: signIns,
|
|
119
|
+
leases: launchLeases,
|
|
120
|
+
...over,
|
|
121
|
+
});
|
|
122
|
+
signIns.save('epic', { data: SIGN_IN }, 'pc');
|
|
123
|
+
signIns.save('cfx', { licenseKey: CFX }, 'typed');
|
|
124
|
+
t.equal(deliver().payloads, {}, 'share off: nothing is handed over');
|
|
125
|
+
config.accounts.epic.share = true;
|
|
126
|
+
config.accounts.cfx.share = true;
|
|
127
|
+
t.equal(Object.keys(deliver({ on: new Set(['steam']) }).payloads), [], 'no toolchain that uses it: nothing');
|
|
128
|
+
const other = { name: 'mod-pack', config: structuredClone(config) };
|
|
129
|
+
other.config.accounts.epic.share = false;
|
|
130
|
+
const tightened = deliver({ others: [other] });
|
|
131
|
+
t.check(!tightened.payloads.epic && tightened.notes.some((n) => n.includes('mod-pack')), 'another project in the boot that turns it off keeps it out (and it says which)');
|
|
132
|
+
const both = deliver({ on: new Set(['unreal', 'fivem']) });
|
|
133
|
+
t.check(both.payloads.epic?.data === SIGN_IN &&
|
|
134
|
+
both.payloads.cfx?.licenseKey === CFX &&
|
|
135
|
+
both.leased.includes('epic') &&
|
|
136
|
+
!both.leased.includes('cfx') &&
|
|
137
|
+
!both.notes.join(' ').includes(SIGN_IN) &&
|
|
138
|
+
!both.notes.join(' ').includes(CFX), 'shared and used: handed over, one-holder kinds leased; the notes never hold a secret');
|
|
139
|
+
const busy = deliver({ isRunning: () => true, dataPath: path.join(root, 'data', 'busy') });
|
|
140
|
+
t.check(busy.payloads.epic === undefined && busy.notes.some((n) => n.includes('in use by sandbox')), 'held by another running sandbox: not handed over, and it says so');
|
|
141
|
+
signIns.remove('epic');
|
|
142
|
+
const first = deliver();
|
|
143
|
+
t.check(!first.payloads.epic && first.leased.includes('epic') && launchLeases.holder('epic')?.launchId === 'L-boot', 'nothing saved yet: the lease is claimed anyway, so a first sign-in made there can come back');
|
|
144
|
+
const sharedRun = await f.run({}, config);
|
|
145
|
+
const launched = generateSandbox({
|
|
146
|
+
packageRoot: t.packageRoot,
|
|
147
|
+
run: sharedRun,
|
|
148
|
+
harnessArgs: [],
|
|
149
|
+
probe: f.probe,
|
|
150
|
+
pcLanguages: [],
|
|
151
|
+
serviceAccounts: { epic: { data: RENEWED }, cfx: { licenseKey: CFX } },
|
|
152
|
+
accountLeases: ['epic'],
|
|
153
|
+
});
|
|
154
|
+
t.check(!allText(launched.launchPath).some((text) => text.includes(RENEWED) || text.includes(CFX)), 'the sign-ins are in neither session.json nor any file of the launch folder');
|
|
155
|
+
t.equal(f.readJson(launched.sessionPath).accountLeases, ['epic'], 'session.json names the leased kinds only');
|
|
156
|
+
const secrets = f.readJson(new RunDir(sharedRun.dataPath).secretFile(launched.launchId));
|
|
157
|
+
t.check(secrets.serviceAccounts?.epic?.data === RENEWED && secrets.serviceAccounts?.cfx?.licenseKey === CFX, 'they are only in the one-shot file for the sandbox');
|
|
158
|
+
clearSecrets(root, sharedRun.dataPath);
|
|
159
|
+
const offConfig = structuredClone(config);
|
|
160
|
+
offConfig.accounts.epic.share = false;
|
|
161
|
+
const offRun = await f.run({}, offConfig);
|
|
162
|
+
const off = generateSandbox({
|
|
163
|
+
packageRoot: t.packageRoot,
|
|
164
|
+
run: offRun,
|
|
165
|
+
harnessArgs: [],
|
|
166
|
+
probe: f.probe,
|
|
167
|
+
pcLanguages: [],
|
|
168
|
+
serviceAccounts: { epic: { data: RENEWED } },
|
|
169
|
+
});
|
|
170
|
+
const offFile = new RunDir(offRun.dataPath).secretFile(off.launchId);
|
|
171
|
+
t.check(!fs.existsSync(offFile) || f.readJson(offFile).serviceAccounts?.epic === undefined, 'generateSandbox checks again: a kind that is not shared is never handed over');
|
|
172
|
+
clearSecrets(root, offRun.dataPath);
|
|
173
|
+
t.section('a project file can only turn sharing off');
|
|
174
|
+
const mine = structuredClone(DEFAULTS);
|
|
175
|
+
t.check(applyOverrides(mine, normalizeOverrides({ accounts: { epic: { share: true } } }, f.workA, 'x')).accounts.epic
|
|
176
|
+
.share === false, 'a project asking for a sign-in leaves it off');
|
|
177
|
+
const shared = structuredClone(DEFAULTS);
|
|
178
|
+
shared.accounts.steam.share = true;
|
|
179
|
+
t.check(applyOverrides(shared, normalizeOverrides({ accounts: { steam: { share: false } } }, f.workA, 'x')).accounts.steam
|
|
180
|
+
.share === false, 'and it may turn one off');
|
|
181
|
+
t.section('what a sandbox hands back');
|
|
182
|
+
const dataPath = run.dataPath;
|
|
183
|
+
const handoffs = handoffDir(dataPath);
|
|
184
|
+
t.equal(handoffs, path.join(hostDataFolder(dataPath, 'state'), 'accounts'), 'handoffs are found where the state folder is (contained storage too)');
|
|
185
|
+
const handoff = (kind) => path.join(handoffs, kind + '.json');
|
|
186
|
+
const back = new ServiceAccountStore(root, SecretStore.memory());
|
|
187
|
+
t.write(handoff('epic'), JSON.stringify({ kind: 'epic', launchId: 'L-boot', payload: { data: RENEWED } }));
|
|
188
|
+
t.write(handoff('steam'), JSON.stringify({ kind: 'steam', launchId: 'L-boot', payload: { account: 'me', configVdf: 'x', sentries: [] } }));
|
|
189
|
+
const took = takeHandoffs(dataPath, false, back, launchLeases);
|
|
190
|
+
t.check(took.taken.includes('epic') && back.read('epic')?.data === RENEWED && back.info('epic')?.source === 'sandbox', 'a renewed sign-in of the leased launch is sealed into the store');
|
|
191
|
+
t.check(took.dropped.includes('steam') && back.state('steam') === 'none' && !fs.existsSync(handoff('steam')), 'one without a lease for this data set is dropped unsealed');
|
|
192
|
+
t.check(!fs.existsSync(handoff('epic')), 'and the plain copy is deleted');
|
|
193
|
+
t.write(handoff('epic'), JSON.stringify({ kind: 'epic', launchId: 'L-other', payload: { data: SIGN_IN } }));
|
|
194
|
+
t.check(takeHandoffs(dataPath, false, back, launchLeases).dropped.includes('epic') && back.read('epic')?.data === RENEWED, 'one from another launch is dropped');
|
|
195
|
+
t.write(handoff('epic'), '{"kind":"epic","launchId":"L-boot","payload":{"data":"' + 'A'.repeat(80 * 1024) + '"}}');
|
|
196
|
+
t.check(takeHandoffs(dataPath, false, back, launchLeases).dropped.includes('epic') && !fs.existsSync(handoff('epic')), 'an oversized copy is never read');
|
|
197
|
+
t.write(handoff('epic'), '{"kind":"epic","launchId":"L-boot","payload":{"data":"not base64 !"}}');
|
|
198
|
+
t.check(takeHandoffs(dataPath, false, back, launchLeases).dropped.includes('epic') && back.read('epic')?.data === RENEWED, 'a damaged copy is dropped and the saved one stays');
|
|
199
|
+
fs.rmSync(handoffs, { recursive: true, force: true });
|
|
200
|
+
const victim = t.dir('svc-victim');
|
|
201
|
+
t.write(path.join(victim, 'epic.json'), JSON.stringify({ kind: 'epic', launchId: 'L-boot', payload: { data: SIGN_IN } }));
|
|
202
|
+
fs.mkdirSync(path.dirname(handoffs), { recursive: true });
|
|
203
|
+
fs.symlinkSync(victim, handoffs, 'junction');
|
|
204
|
+
const linked = takeHandoffs(dataPath, false, back, launchLeases);
|
|
205
|
+
t.check(linked.dropped.includes('epic') &&
|
|
206
|
+
back.read('epic')?.data === RENEWED &&
|
|
207
|
+
fs.existsSync(path.join(victim, 'epic.json')), 'through a planted junction nothing is taken and nothing behind it is deleted');
|
|
208
|
+
fs.rmdirSync(handoffs);
|
|
209
|
+
takeHandoffs(dataPath, true, back, launchLeases);
|
|
210
|
+
t.check(launchLeases.holder('epic') === null, "the sandbox's last sync releases its leases");
|
|
211
|
+
t.section('forgetting everywhere');
|
|
212
|
+
launchLeases.claim('epic', path.basename(dataPath), 'L-boot', null, notRunning);
|
|
213
|
+
t.write(handoff('epic'), JSON.stringify({ kind: 'epic', launchId: 'L-boot', payload: { data: SIGN_IN } }));
|
|
214
|
+
const forgot = forgetEverywhere('epic', root, back, launchLeases);
|
|
215
|
+
t.check(forgot.removed &&
|
|
216
|
+
forgot.pendingDeleted === 1 &&
|
|
217
|
+
back.state('epic') === 'none' &&
|
|
218
|
+
launchLeases.holder('epic') === null &&
|
|
219
|
+
!fs.existsSync(handoff('epic')), 'the saved one, its lease and the copy a sandbox left are gone');
|
|
220
|
+
t.section('in the sandbox');
|
|
221
|
+
const guestRoot = t.dir('svc-guest');
|
|
222
|
+
const fake = { held: null, released: 0 };
|
|
223
|
+
const fakeEpic = {
|
|
224
|
+
kind: 'epic',
|
|
225
|
+
apply: (p) => {
|
|
226
|
+
fake.held = p;
|
|
227
|
+
return { summary: 'fake epic', env: { FAKE_EPIC: '1' } };
|
|
228
|
+
},
|
|
229
|
+
current: () => fake.held,
|
|
230
|
+
release: () => {
|
|
231
|
+
fake.released++;
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
const env = await applyServiceAccounts({ epic: { data: SIGN_IN } }, { leased: ['epic'], root: guestRoot, adapters: { epic: fakeEpic } });
|
|
235
|
+
const delivered = readDelivered(guestRoot);
|
|
236
|
+
t.check(env.FAKE_EPIC === '1' &&
|
|
237
|
+
delivered.leased.includes('epic') &&
|
|
238
|
+
delivered.fingerprints.epic === fingerprint({ data: SIGN_IN }) &&
|
|
239
|
+
!fs.readFileSync(path.join(guestRoot, 'delivered.json'), 'utf8').includes(SIGN_IN), 'boot applies each sign-in and remembers only its fingerprint and the leased kinds');
|
|
240
|
+
const guestData = t.dir('svc-guest-data');
|
|
241
|
+
const watch = new AccountWatch(['epic', 'cfx'], {
|
|
242
|
+
dataDir: guestData,
|
|
243
|
+
launchId: 'L-boot',
|
|
244
|
+
root: guestRoot,
|
|
245
|
+
adapters: { epic: fakeEpic },
|
|
246
|
+
});
|
|
247
|
+
const guestHandoff = path.join(guestData, 'state', 'accounts', 'epic.json');
|
|
248
|
+
t.check(watch.watched().join() === 'epic', 'only kinds that come back are watched');
|
|
249
|
+
t.check(!watch.check('epic') && !fs.existsSync(guestHandoff), 'the first look writes nothing (it is what was delivered)');
|
|
250
|
+
fake.held = { data: RENEWED };
|
|
251
|
+
await watch.flush();
|
|
252
|
+
t.check(fs.existsSync(guestHandoff) &&
|
|
253
|
+
f.readJson(guestHandoff).payload.data === RENEWED, 'a renewed one is left for the PC at the next flush, with its launch');
|
|
254
|
+
fs.rmSync(guestHandoff);
|
|
255
|
+
t.check(!watch.check('epic'), 'and only once');
|
|
256
|
+
await watch.flush(true);
|
|
257
|
+
t.equal(fake.released, 1, 'the last flush lets go of what is held');
|
|
258
|
+
const oldLocal = process.env.LOCALAPPDATA;
|
|
259
|
+
const local = t.dir('svc-local');
|
|
260
|
+
process.env.LOCALAPPDATA = local;
|
|
261
|
+
try {
|
|
262
|
+
t.write(epicSettingsFile(local), '');
|
|
263
|
+
fs.writeFileSync(epicSettingsFile(local), encodeText(INI.replace(SIGN_IN, RENEWED), 'utf16le'));
|
|
264
|
+
await epicAdapter.apply({ data: SIGN_IN });
|
|
265
|
+
const after = decodeText(fs.readFileSync(epicSettingsFile(local)));
|
|
266
|
+
t.check(after.encoding === 'utf16le' &&
|
|
267
|
+
readEpicLogin(after.text)?.data === SIGN_IN &&
|
|
268
|
+
epicAdapter.current?.()?.data === SIGN_IN, "the Epic launcher's UTF-16 file gets the sign-in and stays UTF-16");
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
if (oldLocal === undefined)
|
|
272
|
+
delete process.env.LOCALAPPDATA;
|
|
273
|
+
else
|
|
274
|
+
process.env.LOCALAPPDATA = oldLocal;
|
|
275
|
+
}
|
|
276
|
+
t.section('the account command');
|
|
277
|
+
t.check(!COMMANDS.some((c) => ['epic', 'steam', 'unity'].includes(c.name)), 'no command of its own for a service');
|
|
278
|
+
const account = buildProgram(() => undefined).commands.find((c) => c.name() === 'account');
|
|
279
|
+
const help = account.helpInformation();
|
|
280
|
+
t.check(['status', 'import', 'login', 'logout', '--from', '--yes'].every((w) => help.includes(w)), 'sandbox account --help lists status, import, login, logout, --from and --yes', help);
|
|
281
|
+
const parsed = parseCli(['account', 'import', 'steam', '--from', 'C:\\steamcmd', '--yes']);
|
|
282
|
+
t.check(parsed.command === 'account' &&
|
|
283
|
+
parsed.positionals.join() === 'import,steam' &&
|
|
284
|
+
parsed.yes &&
|
|
285
|
+
parsed.raw.from === 'C:\\steamcmd', 'sandbox account import steam --from <folder> --yes parses');
|
|
286
|
+
t.section('the shared-core flows');
|
|
287
|
+
const pc = { localAppData: t.dir('svc-pc'), appData: '', programData: '' };
|
|
288
|
+
t.write(epicSettingsFile(pc.localAppData), INI);
|
|
289
|
+
const flowStore = new ServiceAccountStore(t.dir('svc-flow'), SecretStore.memory());
|
|
290
|
+
await t.throws(() => importServiceFlow(fakeIO([false]).io, 'epic', undefined, { store: flowStore, pc, share: false }), '', 'saying no takes nothing');
|
|
291
|
+
t.check(flowStore.state('epic') === 'none', 'and nothing is saved');
|
|
292
|
+
const { io: yesIO, seen } = fakeIO([true]);
|
|
293
|
+
const taken = await importServiceFlow(yesIO, 'epic', undefined, { store: flowStore, pc, share: false });
|
|
294
|
+
const asked = seen.questions[0];
|
|
295
|
+
t.check(flowStore.state('epic') === 'saved' &&
|
|
296
|
+
asked.kind === 'confirm' &&
|
|
297
|
+
asked.implied !== true &&
|
|
298
|
+
JSON.stringify(asked).includes('signs the launcher on this PC out') &&
|
|
299
|
+
!JSON.stringify(seen).includes(SIGN_IN) &&
|
|
300
|
+
!taken.message.includes(SIGN_IN) &&
|
|
301
|
+
taken.message.includes('accounts.epic.share'), 'importing asks first (the command line too: --yes answers it), warns the PC is signed out, never shows the sign-in');
|
|
302
|
+
const empty = await importServiceFlow(fakeIO([true]).io, 'epic', undefined, {
|
|
303
|
+
store: flowStore,
|
|
304
|
+
pc: { ...pc, localAppData: t.dir('svc-pc-empty') },
|
|
305
|
+
share: true,
|
|
306
|
+
});
|
|
307
|
+
t.check(empty.message.includes('No saved'), 'no launcher sign-in on this PC: it says so');
|
|
308
|
+
await t.throws(() => loginServiceFlow(fakeIO([]).io, 'epic', { store: flowStore, share: true }), 'nothing to type', 'Epic has nothing to type');
|
|
309
|
+
await t.throws(() => loginServiceFlow(fakeIO([CFX, '']).io, 'cfx', { store: flowStore, share: true }), 'not available yet', 'a kind whose host side is not written yet says so');
|
|
310
|
+
t.check(Boolean(HOST_SOURCES.epic.fromPc) && !HOST_SOURCES.epic.fromAnswers, 'Epic comes from this PC (or a sandbox), never typed');
|
|
311
|
+
const gone = await logoutServiceFlow(fakeIO([true]).io, 'epic', t.dir('svc-flow-root'), flowStore, new AccountLeases(t.dir('svc-flow-root')));
|
|
312
|
+
t.check(gone.deleted && flowStore.state('epic') === 'none', 'forgetting deletes it after asking');
|
|
313
|
+
t.section('the accounts page');
|
|
314
|
+
const row = serviceRow('epic', { ...config, accounts: { ...config.accounts, epic: { share: true } } }, signIns, launchLeases);
|
|
315
|
+
const lines = pageLines(servicePage(row)).join('\n');
|
|
316
|
+
t.check(lines.includes('Epic Games') && lines.includes(SERVICE_SPECS.epic.warnings[1]) && !lines.includes(SIGN_IN), 'a service shows its state and its warnings, never the sign-in');
|
|
317
|
+
t.check(Object.values(accountsView.queries).some((q) => q.id === 'accounts.services') &&
|
|
318
|
+
['accounts.service.import', 'accounts.service.login', 'accounts.service.logout', 'settings.set'].every((id) => accountsView.operations.some((o) => o.id === id)), 'the accounts view offers the services and their actions on every surface');
|
|
319
|
+
},
|
|
320
|
+
};
|