Skip to content

Commit 37f1f37

Browse files
pvdzjdalton
authored andcommitted
Refactor the settings handling (#368)
1 parent b927e31 commit 37f1f37

File tree

1 file changed

+40
-24
lines changed

1 file changed

+40
-24
lines changed

src/utils/settings.ts

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mkdirSync, writeFileSync } from 'node:fs'
1+
import fs from 'node:fs'
22
import os from 'node:os'
33
import path from 'node:path'
44
import process from 'node:process'
@@ -9,9 +9,13 @@ import { logger } from '@socketsecurity/registry/lib/logger'
99
import { safeReadFileSync } from './fs'
1010
import constants from '../constants'
1111

12+
// Default app data folder env var on Win
1213
const LOCALAPPDATA = 'LOCALAPPDATA'
14+
// Default app data folder env var on Mac/Linux
15+
const XDG_DATA_HOME = 'XDG_DATA_HOME'
16+
const SOCKET_APP_DIR = 'socket/settings'
1317

14-
const supportedApiKeys = new Set([
18+
const supportedApiKeys: Set<keyof Settings> = new Set([
1519
'apiBaseUrl',
1620
'apiKey',
1721
'apiProxy',
@@ -20,50 +24,65 @@ const supportedApiKeys = new Set([
2024

2125
interface Settings {
2226
apiBaseUrl?: string | null | undefined
27+
// @deprecated
2328
apiKey?: string | null | undefined
2429
apiProxy?: string | null | undefined
2530
enforcedOrgs?: string[] | readonly string[] | null | undefined
2631
// apiToken is an alias for apiKey.
2732
apiToken?: string | null | undefined
2833
}
2934

30-
let _settings: Settings | undefined
35+
let settings: Settings | undefined
36+
let settingsPath: string | undefined
37+
let warnedSettingPathWin32Missing = false
38+
let pendingSave = false
39+
3140
function getSettings(): Settings {
32-
if (_settings === undefined) {
33-
_settings = {} as Settings
41+
if (settings === undefined) {
42+
settings = {} as Settings
3443
const settingsPath = getSettingsPath()
3544
if (settingsPath) {
3645
const raw = safeReadFileSync(settingsPath)
3746
if (raw) {
3847
try {
3948
Object.assign(
40-
_settings,
49+
settings,
4150
JSON.parse(Buffer.from(raw, 'base64').toString())
4251
)
4352
} catch {
4453
logger.warn(`Failed to parse settings at ${settingsPath}`)
4554
}
4655
} else {
47-
mkdirSync(path.dirname(settingsPath), { recursive: true })
56+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true })
4857
}
4958
}
5059
}
51-
return _settings
60+
return settings
5261
}
5362

54-
let _settingsPath: string | undefined
55-
let _warnedSettingPathWin32Missing = false
5663
function getSettingsPath(): string | undefined {
57-
if (_settingsPath === undefined) {
64+
// Get the OS app data folder:
65+
// - Win: %LOCALAPPDATA% or fail?
66+
// - Mac: %XDG_DATA_HOME% or fallback to "~/Library/Application Support/"
67+
// - Linux: %XDG_DATA_HOME% or fallback to "~/.local/share/"
68+
// Note: LOCALAPPDATA is typically: C:\Users\USERNAME\AppData
69+
// Note: XDG stands for "X Desktop Group", nowadays "freedesktop.org"
70+
// On most systems that path is: $HOME/.local/share
71+
// Then append `socket/settings`, so:
72+
// - Win: %LOCALAPPDATA%\socket\settings or return undefined
73+
// - Mac: %XDG_DATA_HOME%/socket/settings or "~/Library/Application Support/socket/settings"
74+
// - Linux: %XDG_DATA_HOME%/socket/settings or "~/.local/share/socket/settings"
75+
76+
if (settingsPath === undefined) {
5877
// Lazily access constants.WIN32.
5978
const { WIN32 } = constants
6079
let dataHome: string | undefined = WIN32
6180
? process.env[LOCALAPPDATA]
62-
: process.env['XDG_DATA_HOME']
81+
: process.env[XDG_DATA_HOME]
6382
if (!dataHome) {
6483
if (WIN32) {
65-
if (!_warnedSettingPathWin32Missing) {
66-
_warnedSettingPathWin32Missing = true
84+
if (!warnedSettingPathWin32Missing) {
85+
warnedSettingPathWin32Missing = true
6786
logger.warn(`Missing %${LOCALAPPDATA}%`)
6887
}
6988
} else {
@@ -75,19 +94,17 @@ function getSettingsPath(): string | undefined {
7594
)
7695
}
7796
}
78-
_settingsPath = dataHome
79-
? path.join(dataHome, 'socket/settings')
80-
: undefined
97+
settingsPath = dataHome ? path.join(dataHome, SOCKET_APP_DIR) : undefined
8198
}
82-
return _settingsPath
99+
return settingsPath
83100
}
84101

85-
function normalizeSettingsKey(key: string): string {
102+
function normalizeSettingsKey(key: keyof Settings): keyof Settings {
86103
const normalizedKey = key === 'apiToken' ? 'apiKey' : key
87-
if (!supportedApiKeys.has(normalizedKey)) {
104+
if (!supportedApiKeys.has(normalizedKey as keyof Settings)) {
88105
throw new Error(`Invalid settings key: ${normalizedKey}`)
89106
}
90-
return normalizedKey
107+
return normalizedKey as keyof Settings
91108
}
92109

93110
export function findSocketYmlSync() {
@@ -122,20 +139,19 @@ export function getSetting<Key extends keyof Settings>(
122139
return getSettings()[normalizeSettingsKey(key) as Key]
123140
}
124141

125-
let pendingSave = false
126142
export function updateSetting<Key extends keyof Settings>(
127143
key: Key,
128144
value: Settings[Key]
129145
): void {
130146
const settings = getSettings()
131-
;(settings as any)[normalizeSettingsKey(key) as Key] = value
147+
settings[normalizeSettingsKey(key) as Key] = value
132148
if (!pendingSave) {
133149
pendingSave = true
134150
process.nextTick(() => {
135151
pendingSave = false
136152
const settingsPath = getSettingsPath()
137153
if (settingsPath) {
138-
writeFileSync(
154+
fs.writeFileSync(
139155
settingsPath,
140156
Buffer.from(JSON.stringify(settings)).toString('base64')
141157
)

0 commit comments

Comments
 (0)