1
- import { mkdirSync , writeFileSync } from 'node:fs'
1
+ import fs from 'node:fs'
2
2
import os from 'node:os'
3
3
import path from 'node:path'
4
4
import process from 'node:process'
@@ -9,9 +9,13 @@ import { logger } from '@socketsecurity/registry/lib/logger'
9
9
import { safeReadFileSync } from './fs'
10
10
import constants from '../constants'
11
11
12
+ // Default app data folder env var on Win
12
13
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'
13
17
14
- const supportedApiKeys = new Set ( [
18
+ const supportedApiKeys : Set < keyof Settings > = new Set ( [
15
19
'apiBaseUrl' ,
16
20
'apiKey' ,
17
21
'apiProxy' ,
@@ -20,50 +24,65 @@ const supportedApiKeys = new Set([
20
24
21
25
interface Settings {
22
26
apiBaseUrl ?: string | null | undefined
27
+ // @deprecated
23
28
apiKey ?: string | null | undefined
24
29
apiProxy ?: string | null | undefined
25
30
enforcedOrgs ?: string [ ] | readonly string [ ] | null | undefined
26
31
// apiToken is an alias for apiKey.
27
32
apiToken ?: string | null | undefined
28
33
}
29
34
30
- let _settings : Settings | undefined
35
+ let settings : Settings | undefined
36
+ let settingsPath : string | undefined
37
+ let warnedSettingPathWin32Missing = false
38
+ let pendingSave = false
39
+
31
40
function getSettings ( ) : Settings {
32
- if ( _settings === undefined ) {
33
- _settings = { } as Settings
41
+ if ( settings === undefined ) {
42
+ settings = { } as Settings
34
43
const settingsPath = getSettingsPath ( )
35
44
if ( settingsPath ) {
36
45
const raw = safeReadFileSync ( settingsPath )
37
46
if ( raw ) {
38
47
try {
39
48
Object . assign (
40
- _settings ,
49
+ settings ,
41
50
JSON . parse ( Buffer . from ( raw , 'base64' ) . toString ( ) )
42
51
)
43
52
} catch {
44
53
logger . warn ( `Failed to parse settings at ${ settingsPath } ` )
45
54
}
46
55
} else {
47
- mkdirSync ( path . dirname ( settingsPath ) , { recursive : true } )
56
+ fs . mkdirSync ( path . dirname ( settingsPath ) , { recursive : true } )
48
57
}
49
58
}
50
59
}
51
- return _settings
60
+ return settings
52
61
}
53
62
54
- let _settingsPath : string | undefined
55
- let _warnedSettingPathWin32Missing = false
56
63
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 ) {
58
77
// Lazily access constants.WIN32.
59
78
const { WIN32 } = constants
60
79
let dataHome : string | undefined = WIN32
61
80
? process . env [ LOCALAPPDATA ]
62
- : process . env [ ' XDG_DATA_HOME' ]
81
+ : process . env [ XDG_DATA_HOME ]
63
82
if ( ! dataHome ) {
64
83
if ( WIN32 ) {
65
- if ( ! _warnedSettingPathWin32Missing ) {
66
- _warnedSettingPathWin32Missing = true
84
+ if ( ! warnedSettingPathWin32Missing ) {
85
+ warnedSettingPathWin32Missing = true
67
86
logger . warn ( `Missing %${ LOCALAPPDATA } %` )
68
87
}
69
88
} else {
@@ -75,19 +94,17 @@ function getSettingsPath(): string | undefined {
75
94
)
76
95
}
77
96
}
78
- _settingsPath = dataHome
79
- ? path . join ( dataHome , 'socket/settings' )
80
- : undefined
97
+ settingsPath = dataHome ? path . join ( dataHome , SOCKET_APP_DIR ) : undefined
81
98
}
82
- return _settingsPath
99
+ return settingsPath
83
100
}
84
101
85
- function normalizeSettingsKey ( key : string ) : string {
102
+ function normalizeSettingsKey ( key : keyof Settings ) : keyof Settings {
86
103
const normalizedKey = key === 'apiToken' ? 'apiKey' : key
87
- if ( ! supportedApiKeys . has ( normalizedKey ) ) {
104
+ if ( ! supportedApiKeys . has ( normalizedKey as keyof Settings ) ) {
88
105
throw new Error ( `Invalid settings key: ${ normalizedKey } ` )
89
106
}
90
- return normalizedKey
107
+ return normalizedKey as keyof Settings
91
108
}
92
109
93
110
export function findSocketYmlSync ( ) {
@@ -122,20 +139,19 @@ export function getSetting<Key extends keyof Settings>(
122
139
return getSettings ( ) [ normalizeSettingsKey ( key ) as Key ]
123
140
}
124
141
125
- let pendingSave = false
126
142
export function updateSetting < Key extends keyof Settings > (
127
143
key : Key ,
128
144
value : Settings [ Key ]
129
145
) : void {
130
146
const settings = getSettings ( )
131
- ; ( settings as any ) [ normalizeSettingsKey ( key ) as Key ] = value
147
+ settings [ normalizeSettingsKey ( key ) as Key ] = value
132
148
if ( ! pendingSave ) {
133
149
pendingSave = true
134
150
process . nextTick ( ( ) => {
135
151
pendingSave = false
136
152
const settingsPath = getSettingsPath ( )
137
153
if ( settingsPath ) {
138
- writeFileSync (
154
+ fs . writeFileSync (
139
155
settingsPath ,
140
156
Buffer . from ( JSON . stringify ( settings ) ) . toString ( 'base64' )
141
157
)
0 commit comments