@harperfast/harper-pro 5.0.6 → 5.0.8

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.
Files changed (89) hide show
  1. package/core/components/EntryHandler.ts +2 -4
  2. package/core/components/OptionsWatcher.ts +9 -1
  3. package/core/components/Scope.ts +1 -1
  4. package/core/components/componentLoader.ts +4 -11
  5. package/core/components/requestRestart.ts +2 -17
  6. package/core/package-lock.json +67 -975
  7. package/core/resources/RecordEncoder.ts +9 -1
  8. package/core/resources/Table.ts +30 -14
  9. package/core/resources/databases.ts +1 -0
  10. package/core/resources/graphql.ts +168 -158
  11. package/core/resources/indexes/HierarchicalNavigableSmallWorld.ts +3 -14
  12. package/core/resources/indexes/vector.ts +0 -17
  13. package/core/resources/loadEnv.ts +16 -20
  14. package/core/resources/login.ts +3 -4
  15. package/core/resources/roles.ts +65 -60
  16. package/core/security/auth.ts +14 -15
  17. package/core/security/keys.js +1 -1
  18. package/core/server/DurableSubscriptionsSession.ts +1 -0
  19. package/core/server/REST.ts +11 -10
  20. package/core/server/fastifyRoutes.ts +29 -30
  21. package/core/server/graphqlQuerying.ts +3 -4
  22. package/core/server/http.ts +1 -175
  23. package/core/server/mqtt.ts +2 -8
  24. package/core/server/threads/threadServer.js +2 -30
  25. package/core/server/throttle.ts +0 -18
  26. package/core/utility/environment/environmentManager.js +4 -10
  27. package/core/utility/hdbTerms.ts +0 -1
  28. package/dist/core/components/EntryHandler.js +2 -4
  29. package/dist/core/components/EntryHandler.js.map +1 -1
  30. package/dist/core/components/OptionsWatcher.js +8 -1
  31. package/dist/core/components/OptionsWatcher.js.map +1 -1
  32. package/dist/core/components/Scope.js +1 -1
  33. package/dist/core/components/Scope.js.map +1 -1
  34. package/dist/core/components/componentLoader.js +3 -11
  35. package/dist/core/components/componentLoader.js.map +1 -1
  36. package/dist/core/components/requestRestart.js +1 -12
  37. package/dist/core/components/requestRestart.js.map +1 -1
  38. package/dist/core/resources/RecordEncoder.js +10 -1
  39. package/dist/core/resources/RecordEncoder.js.map +1 -1
  40. package/dist/core/resources/Table.js +31 -16
  41. package/dist/core/resources/Table.js.map +1 -1
  42. package/dist/core/resources/databases.js.map +1 -1
  43. package/dist/core/resources/graphql.js +12 -5
  44. package/dist/core/resources/graphql.js.map +1 -1
  45. package/dist/core/resources/indexes/HierarchicalNavigableSmallWorld.js +2 -14
  46. package/dist/core/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  47. package/dist/core/resources/indexes/vector.js +0 -14
  48. package/dist/core/resources/indexes/vector.js.map +1 -1
  49. package/dist/core/resources/loadEnv.js +17 -20
  50. package/dist/core/resources/loadEnv.js.map +1 -1
  51. package/dist/core/resources/login.js +4 -4
  52. package/dist/core/resources/login.js.map +1 -1
  53. package/dist/core/resources/roles.js +68 -64
  54. package/dist/core/resources/roles.js.map +1 -1
  55. package/dist/core/security/auth.js +15 -17
  56. package/dist/core/security/auth.js.map +1 -1
  57. package/dist/core/security/keys.js +1 -1
  58. package/dist/core/security/keys.js.map +1 -1
  59. package/dist/core/server/DurableSubscriptionsSession.js +2 -0
  60. package/dist/core/server/DurableSubscriptionsSession.js.map +1 -1
  61. package/dist/core/server/REST.js +11 -11
  62. package/dist/core/server/REST.js.map +1 -1
  63. package/dist/core/server/fastifyRoutes.js +29 -30
  64. package/dist/core/server/fastifyRoutes.js.map +1 -1
  65. package/dist/core/server/graphqlQuerying.js +4 -5
  66. package/dist/core/server/graphqlQuerying.js.map +1 -1
  67. package/dist/core/server/http.js +0 -179
  68. package/dist/core/server/http.js.map +1 -1
  69. package/dist/core/server/mqtt.js +3 -5
  70. package/dist/core/server/mqtt.js.map +1 -1
  71. package/dist/core/server/threads/threadServer.js +2 -26
  72. package/dist/core/server/threads/threadServer.js.map +1 -1
  73. package/dist/core/server/throttle.js +0 -17
  74. package/dist/core/server/throttle.js.map +1 -1
  75. package/dist/core/utility/environment/environmentManager.js +4 -9
  76. package/dist/core/utility/environment/environmentManager.js.map +1 -1
  77. package/dist/core/utility/hdbTerms.js +0 -1
  78. package/dist/core/utility/hdbTerms.js.map +1 -1
  79. package/dist/replication/replicationConnection.js +13 -6
  80. package/dist/replication/replicationConnection.js.map +1 -1
  81. package/dist/security/certificate.js +1 -1
  82. package/dist/security/certificate.js.map +1 -1
  83. package/npm-shrinkwrap.json +58 -978
  84. package/package.json +2 -3
  85. package/replication/replicationConnection.ts +24 -18
  86. package/security/certificate.ts +1 -1
  87. package/studio/web/assets/{index-qbLPhOzw.js → index-BftP-yQ8.js} +2 -2
  88. package/studio/web/assets/{index-qbLPhOzw.js.map → index-BftP-yQ8.js.map} +1 -1
  89. package/studio/web/index.html +1 -1
@@ -9,72 +9,73 @@ const USERS_NOT_DBS = ['super_user', 'structure_user'];
9
9
  * This is the component for handling role declarations in the Harper system. This will read roles.yaml for role
10
10
  * definitions and ensure that they are created in the system database.
11
11
  */
12
- export function handleApplication(scope: import('../components/Scope.ts').Scope) {
13
- scope.handleEntry(async (entry) => {
14
- if (entry.eventType === 'unlink') return;
15
- return handleFile(entry.contents);
16
- });
17
- }
12
+ // eslint-disable-next-line no-unused-vars
13
+ export function start({ ensureTable }) {
14
+ return {
15
+ handleFile,
16
+ setupFile: handleFile,
17
+ };
18
18
 
19
- /**
20
- * This function will handle the roles.yaml file content that has been read, and ensure that the roles are translated to
21
- * the right shape and created in the system database.
22
- * @param rolesContent
23
- */
24
- async function handleFile(rolesContent) {
25
- let rolesToDefine = parseDocument(rolesContent.toString(), { simpleKeys: true }).toJSON();
26
- for (let roleName in rolesToDefine) {
27
- let role = rolesToDefine[roleName];
28
- if (!role.permission) {
29
- // we allow the permission object to be collapsed into the root object for convenience
30
- role = {
31
- permission: role,
32
- };
33
- if (role.permission.access) {
34
- // this is the designed property object for user-defined flags and access levels
35
- role.access = role.permission.access;
36
- delete role.permission.access;
37
- }
38
- }
39
- for (let dbName in role.permission) {
40
- if (USERS_NOT_DBS.includes(dbName)) continue;
41
- let db = role.permission[dbName];
42
- if (!db.tables) {
43
- // we allow the tables object to be collapsed into the root object for convenience
44
- role.permission[dbName] = db = { tables: db };
19
+ /**
20
+ * This function will handle the roles.yaml file content that has been read, and ensure that the roles are translated to
21
+ * the right shape and created in the system database.
22
+ * @param rolesContent
23
+ */
24
+ async function handleFile(rolesContent) {
25
+ let rolesToDefine = parseDocument(rolesContent.toString(), { simpleKeys: true }).toJSON();
26
+ for (let roleName in rolesToDefine) {
27
+ let role = rolesToDefine[roleName];
28
+ if (!role.permission) {
29
+ // we allow the permission object to be collapsed into the root object for convenience
30
+ role = {
31
+ permission: role,
32
+ };
33
+ if (role.permission.access) {
34
+ // this is the designed property object for user-defined flags and access levels
35
+ role.access = role.permission.access;
36
+ delete role.permission.access;
37
+ }
45
38
  }
46
- for (let tableName in db.tables) {
47
- let table = db.tables[tableName];
48
- // ensure that all the flags are boolean
49
- table.read = Boolean(table.read);
50
- table.insert = Boolean(table.insert);
51
- table.update = Boolean(table.update);
52
- table.delete = Boolean(table.delete);
53
- if (table.attributes) {
54
- // allow attributes to be defined with an object, translating to an array
55
- let attributes = [];
56
- for (let attribute_name in table.attributes) {
57
- let attribute = table.attributes[attribute_name];
58
- attribute.attribute_name = attribute_name;
59
- attributes.push(attribute);
60
- }
61
- table.attribute_permissions = attributes;
62
- delete table.attributes;
39
+ for (let dbName in role.permission) {
40
+ if (USERS_NOT_DBS.includes(dbName)) continue;
41
+ let db = role.permission[dbName];
42
+ if (!db.tables) {
43
+ // we allow the tables object to be collapsed into the root object for convenience
44
+ role.permission[dbName] = db = { tables: db };
63
45
  }
64
- if (table.attribute_permissions) {
65
- if (!Array.isArray(table.attribute_permissions))
66
- throw new Error('attribute_permissions must be an array if defined');
67
- for (let attribute of table.attribute_permissions) {
68
- // ensure that all the flags are boolean
69
- attribute.read = Boolean(attribute.read);
70
- attribute.insert = Boolean(attribute.insert);
71
- attribute.update = Boolean(attribute.update);
46
+ for (let tableName in db.tables) {
47
+ let table = db.tables[tableName];
48
+ // ensure that all the flags are boolean
49
+ table.read = Boolean(table.read);
50
+ table.insert = Boolean(table.insert);
51
+ table.update = Boolean(table.update);
52
+ table.delete = Boolean(table.delete);
53
+ if (table.attributes) {
54
+ // allow attributes to be defined with an object, translating to an array
55
+ let attributes = [];
56
+ for (let attribute_name in table.attributes) {
57
+ let attribute = table.attributes[attribute_name];
58
+ attribute.attribute_name = attribute_name;
59
+ attributes.push(attribute);
60
+ }
61
+ table.attribute_permissions = attributes;
62
+ delete table.attributes;
72
63
  }
73
- } else table.attribute_permissions = null;
64
+ if (table.attribute_permissions) {
65
+ if (!Array.isArray(table.attribute_permissions))
66
+ throw new Error('attribute_permissions must be an array if defined');
67
+ for (let attribute of table.attribute_permissions) {
68
+ // ensure that all the flags are boolean
69
+ attribute.read = Boolean(attribute.read);
70
+ attribute.insert = Boolean(attribute.insert);
71
+ attribute.update = Boolean(attribute.update);
72
+ }
73
+ } else table.attribute_permissions = null;
74
+ }
74
75
  }
76
+ role.role = role.id = roleName;
77
+ await ensureRole(role);
75
78
  }
76
- role.role = role.id = roleName;
77
- await ensureRole(role);
78
79
  }
79
80
  }
80
81
  async function ensureRole(role) {
@@ -91,3 +92,7 @@ async function ensureRole(role) {
91
92
  }
92
93
  return addRole(role);
93
94
  }
95
+
96
+ // we can define these on the main thread
97
+ export const startOnMainThread = start;
98
+ // useful for testing
@@ -243,9 +243,7 @@ export async function authentication(request, nextHandler) {
243
243
  request.user = await server.getUser(session.user, null, request);
244
244
  } else if (
245
245
  (AUTHORIZE_LOCAL && (request.ip?.includes('127.0.0.') || request.ip == '::1')) ||
246
- (request?._nodeRequest?.socket?.server?._pipeName &&
247
- request?._nodeRequest?.socket?.server?.bypassLocalAuth &&
248
- request.ip === undefined) // allow operations API domain socket
246
+ (request?._nodeRequest?.socket?.server?._pipeName && request.ip === undefined) // allow socket domain
249
247
  ) {
250
248
  request.user = await getSuperUser();
251
249
  }
@@ -346,18 +344,19 @@ export async function authentication(request, nextHandler) {
346
344
  return response;
347
345
  }
348
346
  }
349
- setInterval(() => {
350
- authorizationCache = new Map();
351
- }, env.get(CONFIG_PARAMS.AUTHENTICATION_CACHETTL)).unref();
352
- user.addListener(() => {
353
- authorizationCache = new Map();
354
- });
355
- let started = false;
356
- export function handleApplication(scope: import('../components/Scope.ts').Scope) {
357
- if (started) return;
358
- started = true;
359
- const { port, securePort } = scope.options.getAll() as { port?: number; securePort?: number };
360
- scope.server.http(authentication, port || securePort ? { port, securePort } : { port: 'all' });
347
+ let started;
348
+ export function start({ server, port, securePort }) {
349
+ server.http(authentication, port || securePort ? { port, securePort } : { port: 'all' });
350
+ // keep it cleaned out periodically
351
+ if (!started) {
352
+ started = true;
353
+ setInterval(() => {
354
+ authorizationCache = new Map();
355
+ }, env.get(CONFIG_PARAMS.AUTHENTICATION_CACHETTL)).unref();
356
+ user.addListener(() => {
357
+ authorizationCache = new Map();
358
+ });
359
+ }
361
360
  }
362
361
  // operations
363
362
  export async function login(loginObject) {
@@ -857,7 +857,7 @@ function createTLSSelector(type, mtlsOptions) {
857
857
  return SNICallback;
858
858
  function SNICallback(servername, cb) {
859
859
  // find the matching server name, substituting wildcards for each part of the domain to find matches
860
- logger.info?.('TLS requested for', servername || '(no SNI)');
860
+ logger.debug?.('TLS requested for', servername || '(no SNI)');
861
861
  let matchingName = servername;
862
862
  while (true) {
863
863
  let context = secureContexts.get(matchingName);
@@ -257,6 +257,7 @@ class SubscriptionsSession {
257
257
  const _result = (async () => {
258
258
  for await (const update of subscription) {
259
259
  try {
260
+ if (!update || typeof update !== 'object') continue;
260
261
  let messageId;
261
262
  if (
262
263
  update.type &&
@@ -1,6 +1,7 @@
1
1
  import { serialize, serializeMessage, getDeserializer } from '../server/serverHelpers/contentTypes.ts';
2
2
  import { addAnalyticsListener, recordAction, recordActionBinary } from '../resources/analytics/write.ts';
3
3
  import * as harperLogger from '../utility/logging/harper_logger.js';
4
+ import { ServerOptions } from 'http';
4
5
  import { ServerError, ClientError } from '../utility/errors/hdbError.js';
5
6
  import { Resources } from '../resources/Resources.ts';
6
7
  import { Resource, missingMethod, allowedMethods } from '../resources/Resource.ts';
@@ -276,26 +277,26 @@ async function http(request: Context & Request, nextHandler) {
276
277
  }
277
278
  }
278
279
 
279
- let started = false;
280
+ let started;
280
281
  let resources: Resources;
281
282
  let addedMetrics;
282
283
  let connectionCount = 0;
283
284
 
284
- export function handleApplication(scope: import('../components/Scope.ts').Scope) {
285
- httpOptions = scope.options.getAll();
286
- if ((httpOptions as any).includeExpensiveRecordCountEstimates) {
285
+ export function start(options: ServerOptions & { path: string; port: number; server: any; resources: Resources }) {
286
+ httpOptions = options;
287
+ if (options.includeExpensiveRecordCountEstimates) {
287
288
  // If they really want to enable expensive record count estimates
288
289
  Request.prototype.includeExpensiveRecordCountEstimates = true;
289
290
  }
290
- resources = scope.resources;
291
291
  if (started) return;
292
292
  started = true;
293
- scope.server.http(async (request: Request, nextHandler) => {
293
+ resources = options.resources;
294
+ options.server.http(async (request: Request, nextHandler) => {
294
295
  if (request.isWebSocket) return;
295
296
  return http(request, nextHandler);
296
- }, httpOptions);
297
- if ((httpOptions as any).webSocket === false) return;
298
- scope.server.ws(async (ws, request, chainCompletion) => {
297
+ }, options);
298
+ if (options.webSocket === false) return;
299
+ options.server.ws(async (ws, request, chainCompletion) => {
299
300
  connectionCount++;
300
301
  const incomingMessages = new IterableEventQueue();
301
302
  if (!addedMetrics) {
@@ -381,7 +382,7 @@ export function handleApplication(scope: import('../components/Scope.ts').Scope)
381
382
  );
382
383
  }
383
384
  ws.close();
384
- }, httpOptions);
385
+ }, options);
385
386
  }
386
387
  const HTTP_TO_WEBSOCKET_CLOSE_CODES = {
387
388
  401: 3000,
@@ -1,4 +1,4 @@
1
- import { dirname, basename } from 'path';
1
+ import { dirname } from 'path';
2
2
  import { existsSync } from 'fs';
3
3
  import fastify from 'fastify';
4
4
  import fastifyCors from '@fastify/cors';
@@ -32,36 +32,35 @@ const routeFolders = new Set();
32
32
  * @param filePath
33
33
  * @param projectName
34
34
  */
35
- export function handleApplication(scope: import('../components/Scope.ts').Scope) {
35
+ export function start(options) {
36
36
  // if we have a secure port, need to use the secure HTTP server for fastify (it can be used for HTTP as well)
37
- const isHttps = (scope.options.getAll() as { securePort?: number }).securePort > 0;
38
- scope.handleEntry(async (entry) => {
39
- if (entry.eventType !== 'add') {
40
- scope.requestRestart();
41
- return;
42
- }
43
- if (!fastifyServer) {
44
- fastifyServer = buildServer(isHttps);
45
- server.http((await fastifyServer).server);
46
- }
47
- const resolvedServer = await fastifyServer;
48
- const routeFolder = dirname(entry.absolutePath);
49
- let prefix = basename(scope.appName);
50
- if (prefix.startsWith('/')) prefix = prefix.slice(1);
51
- if (!routeFolders.has(routeFolder)) {
52
- routeFolders.add(routeFolder);
53
- try {
54
- resolvedServer.register(buildRouteFolder(routeFolder, prefix));
55
- } catch (error) {
56
- if (error.message === 'Root plugin has already booted')
57
- harperLogger.warn(
58
- `Could not load root fastify route for ${entry.absolutePath}, this may require a restart to install properly`
59
- );
60
- else throw error;
37
+ const isHttps = options.securePort > 0;
38
+ return {
39
+ // eslint-disable-next-line no-unused-vars
40
+ async handleFile(jsContent, relativePath, filePath, projectName) {
41
+ if (!fastifyServer) {
42
+ fastifyServer = buildServer(isHttps);
43
+ server.http((await fastifyServer).server);
61
44
  }
62
- }
63
- await ready();
64
- });
45
+ const resolvedServer = await fastifyServer;
46
+ const routeFolder = dirname(filePath);
47
+ let prefix = dirname(relativePath);
48
+ if (prefix.startsWith('/')) prefix = prefix.slice(1);
49
+ if (!routeFolders.has(routeFolder)) {
50
+ routeFolders.add(routeFolder);
51
+ try {
52
+ resolvedServer.register(buildRouteFolder(routeFolder, prefix));
53
+ } catch (error) {
54
+ if (error.message === 'Root plugin has already booted')
55
+ harperLogger.warn(
56
+ `Could not load root fastify route for ${filePath}, this may require a restart to install properly`
57
+ );
58
+ else throw error;
59
+ }
60
+ }
61
+ },
62
+ ready,
63
+ };
65
64
  }
66
65
  /**
67
66
  * Function called to start up server instance on a forked process - this method is called from customFunctionServer after process is
@@ -119,7 +118,7 @@ async function setUp() {
119
118
  }
120
119
  }
121
120
 
122
- //
121
+ // eslint-disable-next-line require-await
123
122
  function buildRouteFolder(routesFolder, projectName) {
124
123
  return async function (cfServer) {
125
124
  try {
@@ -570,9 +570,8 @@ async function graphqlQueryingHandler(request: Request) {
570
570
  }
571
571
  }
572
572
 
573
- export function handleApplication(scope: import('../components/Scope.ts').Scope) {
574
- const { port, securePort } = scope.options.getAll() as { port?: number; securePort?: number };
575
- scope.server.http(
573
+ export function start(options) {
574
+ options.server.http(
576
575
  async (request, nextLayer) => {
577
576
  if (!request.url.startsWith('/graphql')) {
578
577
  return nextLayer(request);
@@ -696,6 +695,6 @@ export function handleApplication(scope: import('../components/Scope.ts').Scope)
696
695
  throw error;
697
696
  }
698
697
  },
699
- { port, securePort }
698
+ { port: options.port, securePort: options.securePort }
700
699
  );
701
700
  }
@@ -2,7 +2,6 @@
2
2
  * This module represents the HTTP component for Harper, and receives the HTTP options and uses them to configure
3
3
  * HTTP servers
4
4
  */
5
- import { currentThreadId } from '@harperfast/rocksdb-js';
6
5
  import { Scope } from '../components/Scope.ts';
7
6
  import { Socket } from 'node:net';
8
7
  import harperLogger from '../utility/logging/harper_logger.js';
@@ -10,7 +9,7 @@ import { parentPort } from 'node:worker_threads';
10
9
  import env from '../utility/environment/environmentManager.js';
11
10
  import * as terms from '../utility/hdbTerms.ts';
12
11
  import { getConfigPath } from '../config/configUtils.js';
13
- import { getTicketKeys, getWorkerIndex } from './threads/manageThreads.js';
12
+ import { getTicketKeys } from './threads/manageThreads.js';
14
13
  import { createTLSSelector } from '../security/keys.js';
15
14
  import { createSecureServer } from 'node:http2';
16
15
  import { createServer as createSecureServerHttp1 } from 'node:https';
@@ -20,8 +19,6 @@ import { appendHeader, Headers } from './serverHelpers/Headers.ts';
20
19
  import { Blob } from '../resources/blob.ts';
21
20
  import { recordAction, recordActionBinary } from '../resources/analytics/write.ts';
22
21
  import { Readable } from 'node:stream';
23
- import { mkdirSync, writeFileSync, unlinkSync, readdirSync } from 'node:fs';
24
- import { join } from 'node:path';
25
22
  import { server, type ServerOptions, type HttpOptions, type UpgradeOptions, UpgradeListener } from './Server.ts';
26
23
  import { setPortServerMap, SERVERS } from './serverRegistry.ts';
27
24
  import { getComponentName } from '../components/componentLoader.ts';
@@ -39,77 +36,6 @@ const httpServers = {},
39
36
  httpResponders = [];
40
37
  let httpOptions: HttpOptions = {};
41
38
  export const universalHeaders: [string, string][] = [];
42
- const udsCleanupPaths: { socketPath: string; yamlPath: string }[] = [];
43
-
44
- export function registerUdsCleanupPaths(socketPath: string, yamlPath: string) {
45
- udsCleanupPaths.push({ socketPath, yamlPath });
46
- }
47
-
48
- export function cleanupUdsFiles() {
49
- for (const { socketPath, yamlPath } of udsCleanupPaths) {
50
- try {
51
- unlinkSync(socketPath);
52
- } catch {}
53
- try {
54
- unlinkSync(yamlPath);
55
- } catch {}
56
- }
57
- }
58
-
59
- /** Write YAML metadata for a UDS mirror socket, describing the TLS certs from the corresponding secure server. */
60
- export function writeUdsMetadata(yamlPath: string, port: number | string, secureServer: any) {
61
- const contexts = secureServer.secureContexts;
62
- let yaml = `pid: ${process.pid}\ntid: ${currentThreadId()}\nport: ${port}\n`;
63
- yaml += `certificates:\n`;
64
- if (contexts?.size > 0) {
65
- const seen = new Set();
66
- for (const [, ctx] of contexts) {
67
- if (seen.has(ctx.name)) continue;
68
- seen.add(ctx.name);
69
- yaml += ` - name: ${JSON.stringify(ctx.name)}\n`;
70
- yaml += ` hostnames:\n`;
71
- for (const [h, c] of contexts) {
72
- if (c.name === ctx.name) yaml += ` - ${JSON.stringify(h)}\n`;
73
- }
74
- if (ctx.options.key_file) {
75
- yaml += ` privateKeyFile: ${JSON.stringify(join(env.get(terms.CONFIG_PARAMS.ROOTPATH), 'keys', ctx.options.key_file))}\n`;
76
- }
77
- if (ctx.options.cert) {
78
- yaml += ` certificate: |\n`;
79
- for (const line of ctx.options.cert.trimEnd().split('\n')) {
80
- yaml += ` ${line}\n`;
81
- }
82
- }
83
- if (ctx.certificateAuthorities?.length > 0) {
84
- yaml += ` certificateAuthorities:\n`;
85
- for (const [, ca] of ctx.certificateAuthorities) {
86
- yaml += ` - |\n`;
87
- for (const line of ca.trimEnd().split('\n')) {
88
- yaml += ` ${line}\n`;
89
- }
90
- }
91
- }
92
- }
93
- }
94
- try {
95
- writeFileSync(yamlPath, yaml);
96
- } catch (error) {
97
- harperLogger.error('Error writing UDS metadata to ' + yamlPath, error);
98
- }
99
- }
100
-
101
- /** Clean all files in the sockets directory. Call from main thread on process startup. */
102
- export function cleanupSocketsDirectory() {
103
- if (!env.get(terms.CONFIG_PARAMS.TLS_UNIXDOMAINSOCKETS)) return;
104
- const socketsDir = join(env.getHdbBasePath(), 'sockets');
105
- try {
106
- for (const file of readdirSync(socketsDir)) {
107
- try {
108
- unlinkSync(join(socketsDir, file));
109
- } catch {}
110
- }
111
- } catch {}
112
- }
113
39
 
114
40
  export function handleApplication(scope: Scope) {
115
41
  httpOptions = scope.options.getAll() as HttpOptions;
@@ -517,46 +443,6 @@ function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
517
443
  server.isSecure = true;
518
444
  }
519
445
  registerServer(server, port);
520
-
521
- // Operations API domain socket connections bypass auth (equivalent to local access)
522
- if (isOperationsServer && String(port).includes('/')) server.bypassLocalAuth = true;
523
-
524
- // Create a corresponding Unix Domain Socket mirror for secure ports
525
- if (secure && env.get(terms.CONFIG_PARAMS.TLS_UNIXDOMAINSOCKETS)) {
526
- const socketsDir = join(env.getHdbBasePath(), 'sockets');
527
- mkdirSync(socketsDir, { recursive: true });
528
- const socketName = `${getWorkerIndex()}-${port}`;
529
- const udsPath = join(socketsDir, `${socketName}.sock`);
530
- const yamlPath = join(socketsDir, `${socketName}.yaml`);
531
-
532
- // Create a plain HTTP server (no TLS) with the same request handler
533
- const udsServer = createServer(
534
- {
535
- keepAliveTimeout,
536
- headersTimeout,
537
- requestTimeout,
538
- highWaterMark: 128 * 1024,
539
- noDelay: true,
540
- keepAlive: true,
541
- keepAliveInitialDelay: 600,
542
- maxHeaderSize: env.get(terms.CONFIG_PARAMS.HTTP_MAXHEADERSIZE),
543
- },
544
- (nodeRequest: IncomingMessage, nodeResponse: any) => {
545
- const method = nodeRequest.method;
546
- if (method === 'GET' || method === 'OPTIONS' || method === 'HEAD') requestHandler(nodeRequest, nodeResponse);
547
- else throttledRequestHandler(nodeRequest, nodeResponse);
548
- }
549
- );
550
-
551
- udsServer.isPerThreadSocket = true;
552
- enableProxyProtocol(udsServer);
553
- SERVERS[udsPath] = udsServer;
554
- registerUdsCleanupPaths(udsPath, yamlPath);
555
-
556
- const writeMetadata = () => writeUdsMetadata(yamlPath, port, server);
557
- options.SNICallback.ready.then(writeMetadata);
558
- server.secureContextsListeners.push(writeMetadata);
559
- }
560
446
  }
561
447
  return httpServers[port];
562
448
  }
@@ -698,66 +584,6 @@ function onWebSocket(listener: (ws: WebSocket) => void, options: OnWebSocketOpti
698
584
  return servers;
699
585
  }
700
586
 
701
- // PROXY protocol v1 max header length per spec: 108 bytes
702
- const PROXY_V1_MAX_HEADER = 108;
703
-
704
- function enableProxyProtocol(httpServer) {
705
- // In Node.js v24+, the HTTP parser's data path goes through the C++ stream layer
706
- // and does not call socket.emit('data') via JavaScript method dispatch.
707
- // Overriding socket.emit or socket.push has no effect on the HTTP parser's data intake.
708
- //
709
- // Instead: use process.nextTick inside the 'connection' handler to wrap the HTTP
710
- // parser's 'data' listener after it has been registered (synchronously, by the HTTP
711
- // parser's own 'connection' handler which runs right after ours).
712
- // process.nextTick fires before any I/O callbacks, so it is guaranteed to run before
713
- // the first network data chunk reaches the socket — making the interception race-free.
714
- httpServer.prependListener('connection', (socket) => {
715
- process.nextTick(() => {
716
- // Capture the HTTP parser's 'data' listener(s) registered during this connection event.
717
- const dataListeners = socket.listeners('data') as ((chunk: Buffer) => void)[];
718
- if (dataListeners.length === 0) return;
719
- socket.removeAllListeners('data');
720
-
721
- let proxyDone = false;
722
- socket.on('data', (chunk: Buffer) => {
723
- if (!proxyDone) {
724
- proxyDone = true;
725
- // Fast path: PROXY v1 always starts with "PROXY " (0x50 0x52 0x4f 0x58 0x59 0x20)
726
- if (
727
- chunk.length >= 6 &&
728
- chunk[0] === 0x50 &&
729
- chunk[1] === 0x52 &&
730
- chunk[2] === 0x4f &&
731
- chunk[3] === 0x58 &&
732
- chunk[4] === 0x59 &&
733
- chunk[5] === 0x20
734
- ) {
735
- const header = chunk.toString('latin1', 0, Math.min(PROXY_V1_MAX_HEADER, chunk.length));
736
- const eol = header.indexOf('\r\n');
737
- if (eol !== -1) {
738
- // "PROXY TCP4 <src-ip> <dst-ip> <src-port> <dst-port>"
739
- const parts = header.slice(0, eol).split(' ');
740
- if (parts.length === 6) {
741
- // Override the UDS socket's undefined remoteAddress/remotePort with the real client values.
742
- Object.defineProperty(socket, 'remoteAddress', { value: parts[2], configurable: true });
743
- Object.defineProperty(socket, 'remotePort', { value: parseInt(parts[4], 10), configurable: true });
744
- }
745
- // Forward only the bytes after the PROXY header to the HTTP parser.
746
- const rest = chunk.subarray(eol + 2);
747
- if (rest.length > 0) {
748
- for (const listener of dataListeners) listener.call(socket, rest);
749
- }
750
- return;
751
- }
752
- }
753
- }
754
- // Not a PROXY header (or already handled) — forward unchanged.
755
- for (const listener of dataListeners) listener.call(socket, chunk);
756
- });
757
- });
758
- });
759
- }
760
-
761
587
  function defaultNotFound(request, response) {
762
588
  if (response.headersSent || response.writableEnded) return;
763
589
  response.writeHead(404);
@@ -23,14 +23,7 @@ export function bypassAuth() {
23
23
  const authorizeLocal = (remoteAddress: string) =>
24
24
  AUTHORIZE_LOCAL && (remoteAddress.includes('127.0.0.') || remoteAddress === '::1');
25
25
 
26
- export function handleApplication(scope: import('../components/Scope.ts').Scope) {
27
- const { network, webSocket, requireAuthentication } = scope.options.getAll() as {
28
- network?: any;
29
- webSocket?: any;
30
- requireAuthentication?: boolean;
31
- };
32
- const server = scope.server;
33
- const { port, securePort } = network ?? {};
26
+ export function start({ server, port, network, webSocket, securePort, requireAuthentication }) {
34
27
  // here we basically normalize the different types of sockets to pass to our socket/message handler
35
28
  if (!server.mqtt) {
36
29
  server.mqtt = {
@@ -167,6 +160,7 @@ export function handleApplication(scope: import('../components/Scope.ts').Scope)
167
160
  )
168
161
  );
169
162
  }
163
+ return serverInstances;
170
164
  }
171
165
  let addingMetrics,
172
166
  numberOfConnections = 0;
@@ -2,8 +2,7 @@
2
2
 
3
3
  const { isMainThread, parentPort, threadId, workerData } = require('node:worker_threads');
4
4
  const { createServer: createSocketServer } = require('node:net');
5
- const { unlinkSync, existsSync, mkdirSync } = require('fs');
6
- const { join } = require('path');
5
+ const { unlinkSync, existsSync } = require('fs');
7
6
  let componentsLoadedResolve;
8
7
  exports.whenComponentsLoaded = new Promise((resolve) => {
9
8
  componentsLoadedResolve = resolve;
@@ -145,8 +144,6 @@ function startServers() {
145
144
  }, 5000).unref();
146
145
  });
147
146
  }
148
- // Clean up per-thread UDS socket and metadata files
149
- httpComponent.cleanupUdsFiles();
150
147
  if (debugThreads || process.env.DEV_MODE) {
151
148
  try {
152
149
  require('inspector').close();
@@ -175,8 +172,6 @@ function startServers() {
175
172
  });
176
173
  });
177
174
  componentsLoadedResolve(loaded);
178
- // Clean up UDS files on unexpected process exit
179
- process.on('exit', () => httpComponent.cleanupUdsFiles());
180
175
  return loaded;
181
176
  }
182
177
  function listenOnPorts() {
@@ -185,7 +180,7 @@ function listenOnPorts() {
185
180
  const server = SERVERS[port];
186
181
 
187
182
  // If server is unix domain socket
188
- if (port.includes?.('/')) {
183
+ if (port.includes?.('/') && getWorkerIndex() == 0) {
189
184
  if (existsSync(port)) unlinkSync(port);
190
185
  listening.push(
191
186
  new Promise((resolve, reject) => {
@@ -270,29 +265,6 @@ function onSocket(listener, options) {
270
265
  );
271
266
  SNICallback.initialize(socketServer);
272
267
  SERVERS[options.securePort] = socketServer;
273
-
274
- // Create a corresponding Unix Domain Socket mirror for the secure socket
275
- if (env.get(terms.CONFIG_PARAMS.TLS_UNIXDOMAINSOCKETS)) {
276
- const socketsDir = join(env.getHdbBasePath(), 'sockets');
277
- mkdirSync(socketsDir, { recursive: true });
278
- const socketName = `${getWorkerIndex()}-${options.securePort}`;
279
- const udsPath = join(socketsDir, `${socketName}.sock`);
280
- const yamlPath = join(socketsDir, `${socketName}.yaml`);
281
-
282
- const udsServer = createSocketServer(listener, {
283
- noDelay: true,
284
- keepAlive: true,
285
- keepAliveInitialDelay: 600,
286
- });
287
-
288
- udsServer.isPerThreadSocket = true;
289
- SERVERS[udsPath] = udsServer;
290
- httpComponent.registerUdsCleanupPaths(udsPath, yamlPath);
291
-
292
- const writeMetadata = () => httpComponent.writeUdsMetadata(yamlPath, options.securePort, socketServer);
293
- SNICallback.ready.then(writeMetadata);
294
- socketServer.secureContextsListeners.push(writeMetadata);
295
- }
296
268
  }
297
269
  if (options.port) {
298
270
  setPortServerMap(options.port, { protocol_name: 'TCP', name: getComponentName() });