From cfe2e8ef06099e21742b0245eabca8d633d57689 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 24 Jun 2026 06:27:10 +0200 Subject: [PATCH] fix(developer): consolidate user options in TypeScript code * Remove Server's 'config.json' redundant user options handling * Refactor options.ts in Server and kmc to share common cross-platform code * Move option defaults into common code and sync with .pas defaults * Rename config.ts to standardPaths.ts to better represent the remaining purpose of the module. Fixes: #13458 --- developer/src/common/web/utils/src/index.ts | 2 + .../web/utils/src/keyman-developer-options.ts | 128 ++++++++++++++++++ .../src/commands/buildClasses/BuildProject.ts | 2 +- developer/src/kmc/src/util/KeymanSentry.ts | 2 +- developer/src/kmc/src/util/options.ts | 85 ++++-------- developer/src/server/src/KeymanSentry.ts | 2 +- developer/src/server/src/config.ts | 48 ------- developer/src/server/src/data.ts | 6 +- .../src/handlers/api/debugobject/register.ts | 4 +- developer/src/server/src/index.ts | 37 ++--- developer/src/server/src/options.ts | 86 ++++-------- developer/src/server/src/routes.ts | 5 +- developer/src/server/src/standardPaths.ts | 33 +++++ ....Developer.System.KeymanDeveloperPaths.pas | 1 - .../src/tike/main/KeymanDeveloperOptions.pas | 20 --- 15 files changed, 241 insertions(+), 220 deletions(-) create mode 100644 developer/src/common/web/utils/src/keyman-developer-options.ts delete mode 100644 developer/src/server/src/config.ts create mode 100644 developer/src/server/src/standardPaths.ts diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts index a4141395844..5371620d674 100644 --- a/developer/src/common/web/utils/src/index.ts +++ b/developer/src/common/web/utils/src/index.ts @@ -78,3 +78,5 @@ export { getFontFamily, getFontFamilySync } from './font-family.js'; export * as ValidIds from './valid-ids.js'; export * as ProjectLoader from './project-loader.js'; + +export { optionsManager, KeymanDeveloperOption, KeymanDeveloperOptions, KeymanDeveloperOptionsPath } from './keyman-developer-options.js'; diff --git a/developer/src/common/web/utils/src/keyman-developer-options.ts b/developer/src/common/web/utils/src/keyman-developer-options.ts new file mode 100644 index 00000000000..f1e831e6843 --- /dev/null +++ b/developer/src/common/web/utils/src/keyman-developer-options.ts @@ -0,0 +1,128 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * User options for Keyman Developer. These are stored in options.json in the + * user profile; the location varies by operating system or may be stored in + * browser storage on web sites. + * + * The node-based loader is implemented in both kmc and Keyman Developer Server, + * in order to keep node dependencies out of the developer-utils module. + */ + +/** + * The standard path under the user profile where options.json is stored; use + * `path.join(os.homedir(), ...KeymanDeveloperOptionsPath)` or similar + */ +export const KeymanDeveloperOptionsPath = [/* '~', */ '.keymandeveloper', 'options.json']; + +/** + * The set of standard user options for Keyman Developer. Corresponds to + * TKeymanDeveloperOptions in the Keyman Developer TIKE source. + */ +export interface KeymanDeveloperOptions { + "use tab char": boolean; + "link font sizes": boolean; + "indent size": number; + "use old debugger": boolean; + "editor theme": string; + "debugger break when exiting line": boolean; + "debugger single step after break": boolean; + "debugger show store offset": boolean; + "debugger recompile with debug info": boolean; + "debugger auto reset before compilng": boolean; + "auto save before compiling": boolean; + "osk auto save before importing": boolean; + "web host port": number; + "server keep alive": boolean; + "server use local addresses": boolean; + "server ngrok token": string; + "server ngrok region": string; + "server use ngrok": boolean; + "server show console window": boolean; + "char map disable database lookups": boolean; + "char map auto lookup": boolean; + "open keyboard files in source view": boolean; + "display theme": string; + "external editor path": string; + "smtp server": string; + "test email addresses": string; + "web ladder length": number; + "default project path": string; + "automatically report errors": boolean; + "automatically report usage": boolean; + "toolbar visible": boolean; + "active project": string; + "prompt to upgrade projects": boolean; +}; + +/** + * A single Keyman Developer user option. + */ +export type KeymanDeveloperOption = keyof KeymanDeveloperOptions; + +const DEFAULT_OPTIONS: KeymanDeveloperOptions = { + // Corresponds to KeymanDeveloperOptions.pas, TKeymanDeveloperOptions.Read + "use tab char": false, + "link font sizes": true, + "indent size": 4, + "use old debugger": false, + "editor theme": '', + "debugger break when exiting line": true, + "debugger single step after break": false, + "debugger show store offset": false, + "debugger recompile with debug info": false, + "debugger auto reset before compilng": false, + "auto save before compiling": false, + "osk auto save before importing": false, + "web host port": 8008, + "server keep alive": false, + "server use local addresses": true, + "server ngrok token": '', + "server ngrok region": '', + "server use ngrok": false, + "server show console window": false, + "char map disable database lookups": false, + "char map auto lookup": true, + "open keyboard files in source view": false, + "display theme": 'Windows10', + "external editor path": '', + "smtp server": '', + "test email addresses": '', + "web ladder length": 100, + "default project path": '', // Note: this diverges from Delphi code, which uses CSIDL_PERSONAL on Windows, but it is not used in Server + "automatically report errors": true, + "automatically report usage": true, + "toolbar visible": true, + "active project": '', + "prompt to upgrade projects": true, +} + + +class KeymanDeveloperOptionsManager { + private options: KeymanDeveloperOptions = {...DEFAULT_OPTIONS}; + constructor() {} + + public load(blob: Uint8Array | null) { + this.options = {...DEFAULT_OPTIONS}; + if(blob !== null && blob !== undefined) { + const data = JSON.parse(new TextDecoder('utf-8').decode(blob)); + if(typeof data == 'object') { + // TODO: verify fields in options + this.options = {...DEFAULT_OPTIONS, ...data}; + return true; + } + } + return false; + } + + public get(valueName: T): KeymanDeveloperOptions[T] { + return this.options[valueName]; + } + + public clear() { + this.options = {...DEFAULT_OPTIONS}; + } +} + +export const optionsManager = new KeymanDeveloperOptionsManager(); + diff --git a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts index 76fdfe1d04d..6d51a3914ac 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts @@ -46,7 +46,7 @@ class ProjectBuilder { // Give a hint if the project is v1.0 if(this.project.options.version != '2.0') { - if(getOption("prompt to upgrade projects", true)) { + if(getOption("prompt to upgrade projects")) { this.callbacks.reportMessage(InfrastructureMessages.Hint_ProjectIsVersion10()); } } diff --git a/developer/src/kmc/src/util/KeymanSentry.ts b/developer/src/kmc/src/util/KeymanSentry.ts index 9ee05ecb7fa..658f750d37c 100644 --- a/developer/src/kmc/src/util/KeymanSentry.ts +++ b/developer/src/kmc/src/util/KeymanSentry.ts @@ -23,7 +23,7 @@ export class KeymanSentry { return true; } - return getOption('automatically report errors', true); + return getOption('automatically report errors'); } static init(options?: SentryNodeOptions) { diff --git a/developer/src/kmc/src/util/options.ts b/developer/src/kmc/src/util/options.ts index 9d1a93895ef..51217628cf2 100644 --- a/developer/src/kmc/src/util/options.ts +++ b/developer/src/kmc/src/util/options.ts @@ -1,69 +1,32 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Load Keyman Developer's options from the standard Options location. This + * small loader is duplicated in Keyman Developer Server, because we do not have + * a shared node-aware module at this time. + */ + import * as os from 'node:os'; import * as fs from 'node:fs'; import * as path from 'node:path'; +import { KeymanDeveloperOption, KeymanDeveloperOptions, KeymanDeveloperOptionsPath, optionsManager } from '@keymanapp/developer-utils'; -export interface KeymanDeveloperOptions { - "use tab char"?: boolean; - "link font sizes"?: boolean; - "indent size"?: number; - "use old debugger"?: boolean; - "editor theme"?: string; - "debugger break when exiting line"?: boolean; - "debugger single step after break"?: boolean; - "debugger show store offset"?: boolean; - "debugger recompile with debug info"?: boolean; - "debugger auto reset before compilng"?: boolean; - "auto save before compiling"?: boolean; - "osk auto save before importing"?: boolean; - "web host port"?: number; - "server keep alive"?: boolean; - "server use local addresses"?: boolean; - "server ngrok token"?: string; - "server ngrok region"?: string; - "server use ngrok"?: boolean; - "server show console window"?: boolean; - "char map disable database lookups"?: boolean; - "char map auto lookup"?: boolean; - "open keyboard files in source view"?: boolean; - "display theme"?: string; - "external editor path"?: string; - "smtp server"?: string; - "test email addresses"?: string; - "web ladder length"?: number; - "default project path"?: string; - "automatically report errors"?: boolean; - "automatically report usage"?: boolean; - "toolbar visible"?: boolean; - "active project"?: string; - "prompt to upgrade projects"?: boolean; -}; - -type KeymanDeveloperOption = keyof KeymanDeveloperOptions; - -// Default has no options set, and unit tests will use the defaults (won't call -// `loadOptions()`) -let options: KeymanDeveloperOptions = {}; - -// We only load the options from disk once on first use -let optionsLoaded = false; +let optionsLoaded: boolean = false; -export async function loadOptions(): Promise { +export async function loadOptions(): Promise { if(optionsLoaded) { - return options; + return true; } + optionsLoaded = true; - options = {}; try { - const optionsFile = path.join(os.homedir(), '.keymandeveloper', 'options.json'); + const optionsFile = path.join(os.homedir(), ...KeymanDeveloperOptionsPath); if(fs.existsSync(optionsFile)) { for(let i = 0; i < 5; i++) { try { - const data = JSON.parse(fs.readFileSync(optionsFile, 'utf-8')); - if(typeof data == 'object') { - options = data; - } - break; - } catch(e) { + const data = fs.readFileSync(optionsFile) as Uint8Array; + return optionsManager.load(data); + } catch(e: any) { if(e?.code == 'EBUSY') { await new Promise(resolve => setTimeout(resolve, 500)); } else { @@ -76,20 +39,20 @@ export async function loadOptions(): Promise { } catch(e) { // Nothing to report here, sadly -- because we cannot rely on Sentry at this // low level. - options = {}; } - optionsLoaded = true; - return options; + + optionsManager.clear(); + return false; } -export function getOption(valueName: T, defaultValue: KeymanDeveloperOptions[T]): KeymanDeveloperOptions[T] { - return options[valueName] ?? defaultValue; +export function getOption(valueName: T): KeymanDeveloperOptions[T] { + return optionsManager.get(valueName); } /** * unit tests will clear options before running, for consistency */ export function clearOptions() { - options = {}; - optionsLoaded = true; + optionsLoaded = false; + return optionsManager.clear(); } \ No newline at end of file diff --git a/developer/src/server/src/KeymanSentry.ts b/developer/src/server/src/KeymanSentry.ts index 9dba30a0d41..b531e802357 100644 --- a/developer/src/server/src/KeymanSentry.ts +++ b/developer/src/server/src/KeymanSentry.ts @@ -23,7 +23,7 @@ export class KeymanSentry { return true; } - return getOption('automatically report errors', true); + return getOption('automatically report errors'); } static init(options?: SentryNodeOptions) { diff --git a/developer/src/server/src/config.ts b/developer/src/server/src/config.ts deleted file mode 100644 index e60ec6d0a04..00000000000 --- a/developer/src/server/src/config.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { mkdirSync } from 'fs'; -import { loadJsonFile } from './load-json-file.js'; - -export class Configuration { - public readonly appDataPath: string; - public readonly cachePath: string; - public readonly cacheStateFilename: string; - public readonly lockFilename: string; - public readonly pidFilename: string; - public readonly configFilename: string; - - /* Configuration values - set in config.json by TIKE */ - - public readonly port: number; - - /* ngrok Configuration */ - - public readonly useNgrok: boolean; - public readonly ngrokToken: string; - public readonly ngrokVisible: boolean; - - public ngrokEndpoint: string = ''; - - constructor() { - - this.appDataPath = (process.env.APPDATA || - (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share")) + - '/Keyman/Keyman Developer/Server/'; - this.cachePath = this.appDataPath + 'cache/'; - this.cacheStateFilename = this.appDataPath + 'cache.json'; - this.lockFilename = this.appDataPath + 'lock.json'; - this.pidFilename = this.appDataPath + 'pid.json'; - this.configFilename = this.appDataPath + 'config.json'; - - mkdirSync(this.cachePath, { recursive: true}); - - const cfg = loadJsonFile(this.configFilename); - - this.port = cfg?.port ?? 8008; - - // ngrok configuration - this.useNgrok = cfg?.useNgrok ?? false; - this.ngrokToken = cfg?.ngrokToken ?? ''; - this.ngrokVisible = cfg?.ngrokVisible ?? false; - } -}; - -export const configuration = new Configuration(); \ No newline at end of file diff --git a/developer/src/server/src/data.ts b/developer/src/server/src/data.ts index 6600d57b4d9..8d2753ea214 100644 --- a/developer/src/server/src/data.ts +++ b/developer/src/server/src/data.ts @@ -1,5 +1,5 @@ import { writeFileSync } from 'fs'; -import { configuration } from './config.js'; +import { standardPaths } from './standardPaths.js'; import { loadJsonFile } from './load-json-file.js'; export interface DebugObject { @@ -97,7 +97,7 @@ export class SiteData { } private loadState() { - const state = loadJsonFile(configuration.cacheStateFilename); + const state = loadJsonFile(standardPaths.cacheStateFilename); this.loadDebugObject(DebugKeyboard, state?.keyboards, this.keyboards); this.loadDebugObject(DebugModel, state?.models, this.models); this.loadDebugObject(DebugFont, state?.fonts, this.fonts); @@ -106,7 +106,7 @@ export class SiteData { } public saveState() { - writeFileSync(configuration.cacheStateFilename, JSON.stringify(this, null, 2), 'utf-8'); + writeFileSync(standardPaths.cacheStateFilename, JSON.stringify(this, null, 2), 'utf-8'); } }; diff --git a/developer/src/server/src/handlers/api/debugobject/register.ts b/developer/src/server/src/handlers/api/debugobject/register.ts index a1f7a72cd4c..bb28c87a586 100644 --- a/developer/src/server/src/handlers/api/debugobject/register.ts +++ b/developer/src/server/src/handlers/api/debugobject/register.ts @@ -2,7 +2,7 @@ import * as express from 'express'; import { DebugObject, isValidId, simplifyId } from "../../../data.js"; import * as fs from 'fs'; import * as crypto from 'crypto'; -import { configuration } from '../../../config.js'; +import { standardPaths } from '../../../standardPaths.js'; import chalk from 'chalk'; // We allow only 12 objects of each type in the cache @@ -41,7 +41,7 @@ export function apiRegisterFile (intf: new () => O, root: o.lastUse = new Date(); o.id = id; - o.filename = configuration.cachePath + o.filenameFromId(id); + o.filename = standardPaths.cachePath + o.filenameFromId(id); fs.writeFileSync(o.filename, file); o.sha256 = crypto.createHash('sha256').update(file).digest('hex'); diff --git a/developer/src/server/src/index.ts b/developer/src/server/src/index.ts index 36fd8431d8b..e248b19625a 100644 --- a/developer/src/server/src/index.ts +++ b/developer/src/server/src/index.ts @@ -5,12 +5,12 @@ import express from 'express'; import multer from 'multer'; import * as ws from 'ws'; import { KeymanSentry } from './KeymanSentry.js'; -import { configuration } from './config.js'; +import { standardPaths } from './standardPaths.js'; import { environment } from './environment.js'; import setupRoutes from './routes.js'; import { shutdown } from './shutdown.js'; import { initTray } from './tray.js'; -import { loadOptions } from './options.js'; +import { getOption, loadOptions } from './options.js'; const options = { ngrokLog: false, // Set this to true if you need to see ngrok logs in the console @@ -18,13 +18,13 @@ const options = { /* Lock file - report on PID and prevent multiple instances cleanly */ -console.log(`Starting Keyman Developer Server ${environment.versionWithTag}, listening on port ${configuration.port}.`); - // We need to load the Keyman Developer options before attempting to initialize // Sentry. `loadOptions` silently suppresses exceptions and returns a default // set of options if an error occurs. await loadOptions(); +console.log(`Starting Keyman Developer Server ${environment.versionWithTag}, listening on port ${getOption('web host port')}.`); + KeymanSentry.init(); try { await run(); @@ -80,10 +80,11 @@ export async function run() { let server = null; try { - server = app.listen(configuration.port); + server = app.listen(getOption("web host port")); } catch(err) { console.error(err); // TODO handle and cleanup EADDRINUSE, throw anything else + return; } /* Attach the web socket server */ @@ -96,14 +97,14 @@ export async function run() { /* Launch ngrok if enabled */ - configuration.ngrokEndpoint = ''; - if(configuration.useNgrok) { + standardPaths.ngrokEndpoint = ''; + if(getOption("server use ngrok")) { await startNGrok(); } /* Load the tray icon */ - tray.start(configuration.port, configuration.ngrokEndpoint); + tray.start(getOption("web host port"), standardPaths.ngrokEndpoint); } async function loadNGrok() { @@ -129,8 +130,8 @@ async function startNGrok() { let started = false; const listener = await ngrok.forward({ proto: 'http', - addr: configuration.port, - authtoken: configuration.ngrokToken, + addr: getOption("web host port"), + authtoken: getOption("server ngrok token"), onLogEvent: (msg: string) => { if(options.ngrokLog) { console.log(chalk.cyan(('\n'+msg).split('\n').join('\n[ngrok] ').trim())); @@ -139,19 +140,19 @@ async function startNGrok() { onStatusChange: (state: string) => { if(state == 'connected' && started) { // We only announce reconnection after initial start - configuration.ngrokEndpoint = listener.url() ?? ''; - console.log(chalk.blueBright('ngrok tunnel reconnected at %s'), configuration.ngrokEndpoint); + standardPaths.ngrokEndpoint = listener.url() ?? ''; + console.log(chalk.blueBright('ngrok tunnel reconnected at %s'), standardPaths.ngrokEndpoint); } else if(state == 'closed') { - configuration.ngrokEndpoint = ''; + standardPaths.ngrokEndpoint = ''; console.log(chalk.blueBright('ngrok tunnel closed')); } } }); started = true; - configuration.ngrokEndpoint = listener.url(); - console.log(chalk.blueBright('ngrok tunnel established at %s'), configuration.ngrokEndpoint); + standardPaths.ngrokEndpoint = listener.url() ?? ""; + console.log(chalk.blueBright('ngrok tunnel established at %s'), standardPaths.ngrokEndpoint); } catch(e) { - configuration.ngrokEndpoint = ''; + standardPaths.ngrokEndpoint = ''; console.error(chalk.red('ngrok tunnel failed to connect with an error: %s'), e); return false; } @@ -168,8 +169,8 @@ function getRunningInstancePid(pidFilename: string) { } function writeLockFile() { - const lockFilename = configuration.lockFilename.replaceAll(/[\\\/]/g, path.sep); - const pidFilename = configuration.pidFilename.replaceAll(/[\\\/]/g, path.sep); + const lockFilename = standardPaths.lockFilename.replaceAll(/[\\\/]/g, path.sep); + const pidFilename = standardPaths.pidFilename.replaceAll(/[\\\/]/g, path.sep); // console.debug(`Testing existence of ${lockFilename}`); if(fs.existsSync(lockFilename)) { diff --git a/developer/src/server/src/options.ts b/developer/src/server/src/options.ts index 45799ba5f29..a911283be39 100644 --- a/developer/src/server/src/options.ts +++ b/developer/src/server/src/options.ts @@ -1,70 +1,32 @@ -// TODO: this is duplicated in kmc +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Load Keyman Developer's options from the standard Options location. This + * small loader is duplicated in kmc, because we do not have a shared node-aware + * module at this time. + */ + import * as os from 'node:os'; import * as fs from 'node:fs'; import * as path from 'node:path'; +import { KeymanDeveloperOption, KeymanDeveloperOptions, KeymanDeveloperOptionsPath, optionsManager } from '@keymanapp/developer-utils'; -export interface KeymanDeveloperOptions { - "use tab char"?: boolean; - "link font sizes"?: boolean; - "indent size"?: number; - "use old debugger"?: boolean; - "editor theme"?: string; - "debugger break when exiting line"?: boolean; - "debugger single step after break"?: boolean; - "debugger show store offset"?: boolean; - "debugger recompile with debug info"?: boolean; - "debugger auto reset before compilng"?: boolean; - "auto save before compiling"?: boolean; - "osk auto save before importing"?: boolean; - "web host port"?: number; - "server keep alive"?: boolean; - "server use local addresses"?: boolean; - "server ngrok token"?: string; - "server ngrok region"?: string; - "server use ngrok"?: boolean; - "server show console window"?: boolean; - "char map disable database lookups"?: boolean; - "char map auto lookup"?: boolean; - "open keyboard files in source view"?: boolean; - "display theme"?: string; - "external editor path"?: string; - "smtp server"?: string; - "test email addresses"?: string; - "web ladder length"?: number; - "default project path"?: string; - "automatically report errors"?: boolean; - "automatically report usage"?: boolean; - "toolbar visible"?: boolean; - "active project"?: string; - "prompt to upgrade projects"?: boolean; -}; - -type KeymanDeveloperOption = keyof KeymanDeveloperOptions; - -// Default has no options set, and unit tests will use the defaults (won't call -// `loadOptions()`) -let options: KeymanDeveloperOptions = {}; - -// We only load the options from disk once on first use -let optionsLoaded = false; +let optionsLoaded: boolean = false; -export async function loadOptions(): Promise { +export async function loadOptions(): Promise { if(optionsLoaded) { - return options; + return true; } + optionsLoaded = true; - options = {}; try { - const optionsFile = path.join(os.homedir(), '.keymandeveloper', 'options.json'); + const optionsFile = path.join(os.homedir(), ...KeymanDeveloperOptionsPath); if(fs.existsSync(optionsFile)) { for(let i = 0; i < 5; i++) { try { - const data = JSON.parse(fs.readFileSync(optionsFile, 'utf-8')); - if(typeof data == 'object') { - options = data; - } - break; - } catch(e) { + const data = fs.readFileSync(optionsFile) as Uint8Array; + return optionsManager.load(data); + } catch(e: any) { if(e?.code == 'EBUSY') { await new Promise(resolve => setTimeout(resolve, 500)); } else { @@ -77,20 +39,20 @@ export async function loadOptions(): Promise { } catch(e) { // Nothing to report here, sadly -- because we cannot rely on Sentry at this // low level. - options = {}; } - optionsLoaded = true; - return options; + + optionsManager.clear(); + return false; } -export function getOption(valueName: T, defaultValue: KeymanDeveloperOptions[T]): KeymanDeveloperOptions[T] { - return options[valueName] ?? defaultValue; +export function getOption(valueName: T): KeymanDeveloperOptions[T] { + return optionsManager.get(valueName); } /** * unit tests will clear options before running, for consistency */ export function clearOptions() { - options = {}; - optionsLoaded = true; + optionsLoaded = false; + return optionsManager.clear(); } \ No newline at end of file diff --git a/developer/src/server/src/routes.ts b/developer/src/server/src/routes.ts index 3876bf0d7a1..acccf234020 100644 --- a/developer/src/server/src/routes.ts +++ b/developer/src/server/src/routes.ts @@ -12,9 +12,10 @@ import handleIncPackagesJson from './handlers/inc/packages-json.js'; import apiPackageRegister from './handlers/api/package/register.js'; import handleIncKeyboardsCss from './handlers/inc/keyboards-css.js'; import { Environment } from './version-data.js'; -import { configuration } from './config.js'; +import { standardPaths } from './standardPaths.js'; import chalk from 'chalk'; import { shutdown } from './shutdown.js'; +import { getOption } from './options.js'; export default function setupRoutes(app: express.Express, upload: multer.Multer, wsServer: ws.WebSocketServer, environment: Environment ) { @@ -163,7 +164,7 @@ export default function setupRoutes(app: express.Express, upload: multer.Multer, /* ngrok data */ app.get('/api/status', (_req,res,next) => { - const response = { ngrokEnabled: configuration.useNgrok, ngrokEndpoint: configuration.ngrokEndpoint }; + const response = { ngrokEnabled: getOption("server use ngrok"), ngrokEndpoint: standardPaths.ngrokEndpoint }; res.send(response); next(); }); diff --git a/developer/src/server/src/standardPaths.ts b/developer/src/server/src/standardPaths.ts new file mode 100644 index 00000000000..bc84104d9e7 --- /dev/null +++ b/developer/src/server/src/standardPaths.ts @@ -0,0 +1,33 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Path and URL constants (in some cases calculated) + */ +import { mkdirSync } from 'node:fs'; + +class StandardPaths { + public readonly appDataPath: string; + public readonly cachePath: string; + public readonly cacheStateFilename: string; + public readonly lockFilename: string; + public readonly pidFilename: string; + + /* ngrok Configuration */ + + public ngrokEndpoint: string = ''; + + constructor() { + + this.appDataPath = (process.env.APPDATA || + (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share")) + + '/Keyman/Keyman Developer/Server/'; + this.cachePath = this.appDataPath + 'cache/'; + this.cacheStateFilename = this.appDataPath + 'cache.json'; + this.lockFilename = this.appDataPath + 'lock.json'; + this.pidFilename = this.appDataPath + 'pid.json'; + + mkdirSync(this.cachePath, {recursive: true}); + } +}; + +export const standardPaths = new StandardPaths(); diff --git a/developer/src/tike/main/Keyman.Developer.System.KeymanDeveloperPaths.pas b/developer/src/tike/main/Keyman.Developer.System.KeymanDeveloperPaths.pas index 6beeafe62e5..1322010798d 100644 --- a/developer/src/tike/main/Keyman.Developer.System.KeymanDeveloperPaths.pas +++ b/developer/src/tike/main/Keyman.Developer.System.KeymanDeveloperPaths.pas @@ -20,7 +20,6 @@ TKeymanDeveloperPaths = class sealed const S_Kmc = 'kmc.cmd'; class function KmcPath: string; static; - const S_ServerConfigJson = 'config.json'; class function ServerDataPath: string; static; class function ServerPath: string; static; diff --git a/developer/src/tike/main/KeymanDeveloperOptions.pas b/developer/src/tike/main/KeymanDeveloperOptions.pas index 22109f58ff2..09c6afcd9c6 100644 --- a/developer/src/tike/main/KeymanDeveloperOptions.pas +++ b/developer/src/tike/main/KeymanDeveloperOptions.pas @@ -81,7 +81,6 @@ TKeymanDeveloperOptions = class procedure optWriteString(const nm, value: string); procedure optWriteBool(const nm: string; value: Boolean); procedure optWriteInt(const nm: string; value: Integer); - procedure WriteServerConfigurationJson; class function Get_Initial_DefaultProjectPath: string; static; function BackOffAndSaveJson(const Filename: string; const JSON: TJSONObject): Boolean; public @@ -476,8 +475,6 @@ procedure TKeymanDeveloperOptions.Write; finally FreeAndNil(json); end; - - WriteServerConfigurationJson; end; function TKeymanDeveloperOptions.BackOffAndSaveJson(const Filename: string; const JSON: TJSONObject): Boolean; @@ -513,23 +510,6 @@ function TKeymanDeveloperOptions.BackOffAndSaveJson(const Filename: string; cons Result := False; end; -procedure TKeymanDeveloperOptions.WriteServerConfigurationJson; -var - o: TJSONObject; -begin - o := TJSONObject.Create; - try - o.AddPair('port', TJSONNumber.Create(FServerDefaultPort)); - o.AddPair('ngrokToken', FServerNgrokToken); - o.AddPair('useNgrok', TJSONBool.Create(FServerUseNgrok)); - o.AddPair('ngrokVisible', TJSONBool.Create(FServerServerShowConsoleWindow)); - ForceDirectories(TKeymanDeveloperPaths.ServerDataPath); - BackOffAndSaveJSON(TKeymanDeveloperPaths.ServerDataPath + TKeymanDeveloperPaths.S_ServerConfigJson, o); - finally - o.Free; - end; -end; - procedure TKeymanDeveloperOptions.optWriteBool(const nm: string; value: Boolean); begin json.AddPair(nm, TJSONBool.Create(value));