1- import { mkdirSync , writeFileSync } from 'node:fs'
1+ import fs from 'node:fs'
22import os from 'node:os'
33import path from 'node:path'
44import process from 'node:process'
@@ -9,9 +9,13 @@ import { logger } from '@socketsecurity/registry/lib/logger'
99import { safeReadFileSync } from './fs'
1010import constants from '../constants'
1111
12+ // Default app data folder env var on Win
1213const 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
2125interface 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+
3140function 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
5663function 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
93110export 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
126142export 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