@wabot-dev/framework 0.9.2 → 0.9.5

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 (37) hide show
  1. package/dist/src/addon/async/pg/PgCronJobRepository.js +7 -5
  2. package/dist/src/addon/async/pg/PgJobRepository.js +7 -5
  3. package/dist/src/addon/async/pg/PgTransactionAdapter.js +4 -4
  4. package/dist/src/addon/auth/api-key/ApiKey.js +4 -4
  5. package/dist/src/addon/auth/api-key/PgApiKeyRepository.js +9 -8
  6. package/dist/src/addon/auth/jwt/JwtRefreshToken.js +4 -4
  7. package/dist/src/addon/auth/jwt/PgJwtRefreshTokenRepository.js +6 -5
  8. package/dist/src/addon/chat-bot/pg/PgChatMemory.js +8 -7
  9. package/dist/src/addon/chat-bot/pg/PgChatRepository.js +7 -6
  10. package/dist/src/addon/chat-controller/cmd/@cmd.js +7 -2
  11. package/dist/src/addon/chat-controller/cmd/CmdChannel.js +85 -61
  12. package/dist/src/addon/chat-controller/cmd/CmdChannelConfig.js +8 -0
  13. package/dist/src/addon/chat-controller/cmd/CmdChannelServer.js +169 -0
  14. package/dist/src/addon/chat-controller/cmd/cmdChannelSocketPath.js +16 -0
  15. package/dist/src/addon/chat-controller/cmd/runCmdClient.js +226 -0
  16. package/dist/src/core/repository/CrudRepository.js +25 -0
  17. package/dist/src/feature/pg/@pgExtension.js +33 -0
  18. package/dist/src/feature/pg/PgJsonRepositoryAdapter.js +50 -0
  19. package/dist/src/feature/pg/index.js +4 -7
  20. package/dist/src/feature/project-runner/ProjectRunner.js +33 -10
  21. package/dist/src/feature/repository/@memoryExtension.js +29 -0
  22. package/dist/src/feature/repository/@query.js +22 -0
  23. package/dist/src/feature/repository/@queryExtension.js +21 -0
  24. package/dist/src/feature/repository/@repository.js +170 -0
  25. package/dist/src/feature/repository/MemoryRepositoryAdapter.js +110 -0
  26. package/dist/src/feature/repository/RepositoryAdapterRegistry.js +27 -0
  27. package/dist/src/feature/repository/RepositoryMetadataStore.js +102 -0
  28. package/dist/src/feature/repository/evaluateQueryAst.js +134 -0
  29. package/dist/src/index.d.ts +195 -47
  30. package/dist/src/index.js +18 -7
  31. package/package.json +4 -2
  32. package/dist/src/feature/pg/query/@pgJsonRepository.js +0 -73
  33. package/dist/src/feature/pg/query/@query.js +0 -14
  34. package/dist/src/feature/pg/query/PgJsonRepository.js +0 -23
  35. package/dist/src/feature/pg/query/PgRepositoryMetadataStore.js +0 -44
  36. /package/dist/src/feature/pg/{query/buildQuerySql.js → buildQuerySql.js} +0 -0
  37. /package/dist/src/feature/{pg/query → repository}/parseQueryMethodName.js +0 -0
@@ -1,14 +1,16 @@
1
1
  import { __decorate, __metadata } from 'tslib';
2
2
  import { singleton } from '../../../core/injection/index.js';
3
3
  import { CronJob } from '../../../feature/async/CronJob.js';
4
- import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
5
- import '../../../feature/pg/PgLocker.js';
4
+ import 'short-uuid';
5
+ import '../../../core/error/setupErrorHandlers.js';
6
+ import '../../../feature/repository/RepositoryMetadataStore.js';
7
+ import '../../../feature/repository/RepositoryAdapterRegistry.js';
8
+ import '../../../feature/pg/withPgClient.js';
6
9
  import 'debug';
10
+ import '../../../feature/pg/PgLocker.js';
11
+ import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
7
12
  import 'node:crypto';
8
- import '../../../feature/pg/withPgClient.js';
9
13
  import '../../../feature/pg/pgStorage.js';
10
- import '../../../feature/pg/query/PgJsonRepository.js';
11
- import '../../../feature/pg/query/PgRepositoryMetadataStore.js';
12
14
  import { Pool } from 'pg';
13
15
 
14
16
  let PgCronJobRepository = class PgCronJobRepository extends PgCrudRepository {
@@ -1,14 +1,16 @@
1
1
  import { __decorate, __metadata } from 'tslib';
2
2
  import { Pool } from 'pg';
3
3
  import { singleton } from '../../../core/injection/index.js';
4
- import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
5
- import '../../../feature/pg/PgLocker.js';
4
+ import 'short-uuid';
5
+ import '../../../core/error/setupErrorHandlers.js';
6
+ import '../../../feature/repository/RepositoryMetadataStore.js';
7
+ import '../../../feature/repository/RepositoryAdapterRegistry.js';
8
+ import { withPgClient } from '../../../feature/pg/withPgClient.js';
6
9
  import 'debug';
10
+ import '../../../feature/pg/PgLocker.js';
11
+ import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
7
12
  import 'node:crypto';
8
- import { withPgClient } from '../../../feature/pg/withPgClient.js';
9
13
  import '../../../feature/pg/pgStorage.js';
10
- import '../../../feature/pg/query/PgJsonRepository.js';
11
- import '../../../feature/pg/query/PgRepositoryMetadataStore.js';
12
14
  import '../../../feature/async/AsyncMetadataStore.js';
13
15
  import '../../../feature/async/TransactionMetadataStore.js';
14
16
  import '../../../feature/async/Async.js';
@@ -1,13 +1,13 @@
1
+ import '../../../core/injection/index.js';
1
2
  import 'short-uuid';
3
+ import '../../../core/error/setupErrorHandlers.js';
4
+ import '../../../feature/repository/RepositoryMetadataStore.js';
5
+ import '../../../feature/repository/RepositoryAdapterRegistry.js';
2
6
  import { withPgClient } from '../../../feature/pg/withPgClient.js';
3
7
  import 'debug';
4
8
  import '../../../feature/pg/PgLocker.js';
5
- import '../../../core/error/setupErrorHandlers.js';
6
9
  import 'node:crypto';
7
10
  import '../../../feature/pg/pgStorage.js';
8
- import '../../../core/injection/index.js';
9
- import '../../../feature/pg/query/PgJsonRepository.js';
10
- import '../../../feature/pg/query/PgRepositoryMetadataStore.js';
11
11
  import { withPgTransaction } from '../../../feature/pg/withPgTransaction.js';
12
12
 
13
13
  class PgTransactionAdapter {
@@ -1,12 +1,12 @@
1
1
  import { Entity } from '../../../core/entity/Entity.js';
2
2
  import { CustomError } from '../../../core/error/CustomError.js';
3
3
  import '../../../core/error/setupErrorHandlers.js';
4
- import crypto from 'node:crypto';
4
+ import crypto__default from 'node:crypto';
5
5
 
6
6
  class ApiKey extends Entity {
7
7
  static PREFIX = 'sk_';
8
8
  static hashSecret(secret) {
9
- return crypto.createHash('sha256').update(secret).digest('hex');
9
+ return crypto__default.createHash('sha256').update(secret).digest('hex');
10
10
  }
11
11
  get authInfo() {
12
12
  return this.data.authInfo;
@@ -24,7 +24,7 @@ class ApiKey extends Entity {
24
24
  if (this.data.secretHash) {
25
25
  throw new Error('This API key already has a secret');
26
26
  }
27
- const secret = `${ApiKey.PREFIX}${crypto.randomBytes(32).toString('hex')}`;
27
+ const secret = `${ApiKey.PREFIX}${crypto__default.randomBytes(32).toString('hex')}`;
28
28
  this.data.secretHash = ApiKey.hashSecret(secret);
29
29
  return secret;
30
30
  }
@@ -37,7 +37,7 @@ class ApiKey extends Entity {
37
37
  const stored = this.data.secretHash;
38
38
  const hashedBuf = Buffer.from(hashed, 'hex');
39
39
  const storedBuf = Buffer.from(stored, 'hex');
40
- return hashedBuf.length === storedBuf.length && crypto.timingSafeEqual(hashedBuf, storedBuf);
40
+ return hashedBuf.length === storedBuf.length && crypto__default.timingSafeEqual(hashedBuf, storedBuf);
41
41
  }
42
42
  validateSecret(secret) {
43
43
  if (!this.isValidSecret(secret)) {
@@ -1,17 +1,18 @@
1
1
  import { __decorate, __metadata } from 'tslib';
2
- import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
3
- import '../../../feature/pg/PgLocker.js';
4
- import 'debug';
2
+ import { singleton } from '../../../core/injection/index.js';
3
+ import 'short-uuid';
5
4
  import { CustomError } from '../../../core/error/CustomError.js';
6
- import 'node:crypto';
5
+ import '../../../core/error/setupErrorHandlers.js';
6
+ import '../../../feature/repository/RepositoryMetadataStore.js';
7
+ import '../../../feature/repository/RepositoryAdapterRegistry.js';
7
8
  import '../../../feature/pg/withPgClient.js';
9
+ import 'debug';
10
+ import '../../../feature/pg/PgLocker.js';
11
+ import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
12
+ import 'node:crypto';
8
13
  import '../../../feature/pg/pgStorage.js';
9
- import { singleton } from '../../../core/injection/index.js';
10
- import '../../../feature/pg/query/PgJsonRepository.js';
11
- import '../../../feature/pg/query/PgRepositoryMetadataStore.js';
12
14
  import { Pool } from 'pg';
13
15
  import { ApiKey } from './ApiKey.js';
14
- import '../../../core/error/setupErrorHandlers.js';
15
16
 
16
17
  let PgApiKeyRepository = class PgApiKeyRepository extends PgCrudRepository {
17
18
  constructor(pool) {
@@ -1,12 +1,12 @@
1
1
  import { Entity } from '../../../core/entity/Entity.js';
2
2
  import { CustomError } from '../../../core/error/CustomError.js';
3
3
  import '../../../core/error/setupErrorHandlers.js';
4
- import crypto from 'node:crypto';
4
+ import crypto__default from 'node:crypto';
5
5
 
6
6
  class JwtRefreshToken extends Entity {
7
7
  static PREFIX = 'rt_';
8
8
  static hashSecret(secret) {
9
- return crypto.createHash('sha256').update(secret).digest('hex');
9
+ return crypto__default.createHash('sha256').update(secret).digest('hex');
10
10
  }
11
11
  get authInfo() {
12
12
  return this.data.authInfo;
@@ -30,7 +30,7 @@ class JwtRefreshToken extends Entity {
30
30
  if (this.data.secretHash) {
31
31
  throw new Error('This Token key already has a secret');
32
32
  }
33
- const secret = `${JwtRefreshToken.PREFIX}${crypto.randomBytes(32).toString('hex')}`;
33
+ const secret = `${JwtRefreshToken.PREFIX}${crypto__default.randomBytes(32).toString('hex')}`;
34
34
  this.data.secretHash = JwtRefreshToken.hashSecret(secret);
35
35
  return secret;
36
36
  }
@@ -43,7 +43,7 @@ class JwtRefreshToken extends Entity {
43
43
  const stored = this.data.secretHash;
44
44
  const hashedBuf = Buffer.from(hashed, 'hex');
45
45
  const storedBuf = Buffer.from(stored, 'hex');
46
- return hashedBuf.length === storedBuf.length && crypto.timingSafeEqual(hashedBuf, storedBuf);
46
+ return hashedBuf.length === storedBuf.length && crypto__default.timingSafeEqual(hashedBuf, storedBuf);
47
47
  }
48
48
  isValidToken(secret) {
49
49
  if (this.isExpired())
@@ -2,14 +2,15 @@ import { __decorate, __metadata } from 'tslib';
2
2
  import { singleton } from '../../../core/injection/index.js';
3
3
  import { CustomError } from '../../../core/error/CustomError.js';
4
4
  import '../../../core/error/setupErrorHandlers.js';
5
- import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
6
- import '../../../feature/pg/PgLocker.js';
5
+ import 'short-uuid';
6
+ import '../../../feature/repository/RepositoryMetadataStore.js';
7
+ import '../../../feature/repository/RepositoryAdapterRegistry.js';
8
+ import '../../../feature/pg/withPgClient.js';
7
9
  import 'debug';
10
+ import '../../../feature/pg/PgLocker.js';
11
+ import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
8
12
  import 'node:crypto';
9
- import '../../../feature/pg/withPgClient.js';
10
13
  import '../../../feature/pg/pgStorage.js';
11
- import '../../../feature/pg/query/PgJsonRepository.js';
12
- import '../../../feature/pg/query/PgRepositoryMetadataStore.js';
13
14
  import { Pool } from 'pg';
14
15
  import { JwtRefreshToken } from './JwtRefreshToken.js';
15
16
 
@@ -1,12 +1,14 @@
1
- import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
2
- import '../../../feature/pg/PgLocker.js';
1
+ import '../../../core/injection/index.js';
2
+ import 'short-uuid';
3
+ import '../../../core/error/setupErrorHandlers.js';
4
+ import '../../../feature/repository/RepositoryMetadataStore.js';
5
+ import '../../../feature/repository/RepositoryAdapterRegistry.js';
6
+ import '../../../feature/pg/withPgClient.js';
3
7
  import 'debug';
8
+ import '../../../feature/pg/PgLocker.js';
9
+ import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
4
10
  import 'node:crypto';
5
- import '../../../feature/pg/withPgClient.js';
6
11
  import '../../../feature/pg/pgStorage.js';
7
- import '../../../core/injection/index.js';
8
- import '../../../feature/pg/query/PgJsonRepository.js';
9
- import '../../../feature/pg/query/PgRepositoryMetadataStore.js';
10
12
  import '../../../feature/chat-bot/ChatAdapterRegistry.js';
11
13
  import '../../../feature/chat-bot/ChatBot.js';
12
14
  import { ChatItem } from '../../../feature/chat-bot/ChatItem.js';
@@ -15,7 +17,6 @@ import '../../../feature/chat-bot/UnionChatAdapter.js';
15
17
  import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
16
18
  import 'uuid';
17
19
  import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
18
- import '../../../core/error/setupErrorHandlers.js';
19
20
 
20
21
  class PgChatMemory extends PgCrudRepository {
21
22
  chatId;
@@ -2,14 +2,16 @@ import { __decorate, __metadata } from 'tslib';
2
2
  import { Pool } from 'pg';
3
3
  import { PgChatMemory } from './PgChatMemory.js';
4
4
  import { singleton } from '../../../core/injection/index.js';
5
- import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
6
- import '../../../feature/pg/PgLocker.js';
5
+ import 'short-uuid';
6
+ import '../../../core/error/setupErrorHandlers.js';
7
+ import '../../../feature/repository/RepositoryMetadataStore.js';
8
+ import '../../../feature/repository/RepositoryAdapterRegistry.js';
9
+ import '../../../feature/pg/withPgClient.js';
7
10
  import 'debug';
11
+ import '../../../feature/pg/PgLocker.js';
12
+ import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
8
13
  import 'node:crypto';
9
- import '../../../feature/pg/withPgClient.js';
10
14
  import '../../../feature/pg/pgStorage.js';
11
- import '../../../feature/pg/query/PgJsonRepository.js';
12
- import '../../../feature/pg/query/PgRepositoryMetadataStore.js';
13
15
  import { Chat } from '../../../feature/chat-bot/Chat.js';
14
16
  import '../../../feature/chat-bot/ChatAdapterRegistry.js';
15
17
  import '../../../feature/chat-bot/ChatBot.js';
@@ -18,7 +20,6 @@ import '../../../feature/chat-bot/UnionChatAdapter.js';
18
20
  import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
19
21
  import 'uuid';
20
22
  import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
21
- import '../../../core/error/setupErrorHandlers.js';
22
23
 
23
24
  let PgChatRepository = class PgChatRepository extends PgCrudRepository {
24
25
  constructor(pool) {
@@ -3,14 +3,19 @@ import { ControllerMetadataStore } from '../../../feature/chat-controller/metada
3
3
  import '../../../feature/chat-controller/ChatResolver.js';
4
4
  import '../../../feature/chat-controller/runChatControllers.js';
5
5
  import { CmdChannel } from './CmdChannel.js';
6
+ import { CmdChannelConfig } from './CmdChannelConfig.js';
6
7
 
7
8
  function cmd() {
8
9
  return function (target, propertyKey) {
9
10
  const store = container.resolve(ControllerMetadataStore);
11
+ const controllerConstructor = target.constructor;
12
+ const functionName = propertyKey.toString();
13
+ const route = `${controllerConstructor.name}.${functionName}`;
10
14
  store.saveChannelMetadata({
11
15
  channelConstructor: CmdChannel,
12
- functionName: propertyKey.toString(),
13
- controllerConstructor: target.constructor,
16
+ functionName,
17
+ controllerConstructor,
18
+ channelConfig: new CmdChannelConfig(route),
14
19
  });
15
20
  };
16
21
  }
@@ -1,97 +1,121 @@
1
1
  import { __decorate, __metadata } from 'tslib';
2
- import { injectable } from '../../../core/injection/index.js';
3
- import { Logger } from '../../../core/logger/Logger.js';
4
- import { cmdChannelName } from './cmdChannelName.js';
5
- import * as readline from 'node:readline';
6
2
  import * as fs from 'node:fs';
7
3
  import * as path from 'node:path';
4
+ import { injectable } from '../../../core/injection/index.js';
5
+ import { Logger } from '../../../core/logger/Logger.js';
8
6
  import { Random } from '../../../core/random/Random.js';
9
7
  import { Auth } from '../../../core/auth/Auth.js';
8
+ import { CmdChannelConfig } from './CmdChannelConfig.js';
9
+ import { CmdChannelServer } from './CmdChannelServer.js';
10
+ import { cmdChannelName } from './cmdChannelName.js';
10
11
 
11
12
  var CmdChannel_1;
12
- const chatIdPath = '.cmd-channel/id.json';
13
- const authInfoPath = '.cmd-channel/auth-info.json';
13
+ const fileLogger = new Logger('wabot:cmd-channel');
14
14
  let CmdChannel = class CmdChannel {
15
15
  static { CmdChannel_1 = this; }
16
16
  auth;
17
- chatId = undefined;
18
- rl = readline.createInterface({
19
- input: process.stdin,
20
- output: process.stdout,
21
- });
17
+ server;
18
+ config;
22
19
  static channelName = cmdChannelName;
20
+ chatId = undefined;
23
21
  callBack = null;
24
- constructor(auth) {
22
+ constructor(auth, server, config) {
25
23
  this.auth = auth;
24
+ this.server = server;
25
+ this.config = config;
26
26
  }
27
27
  listen(callback) {
28
28
  this.callBack = callback;
29
29
  }
30
30
  disconnect() {
31
- this.rl.removeAllListeners('line');
32
- this.rl.close();
31
+ this.server.unregister(this.config.route);
33
32
  this.callBack = null;
34
33
  }
35
34
  connect() {
36
- this.rl.on('line', async (input) => {
37
- const trimmedInput = input.trim();
38
- if (!trimmedInput) {
39
- this.rl.prompt();
40
- return;
41
- }
42
- if (trimmedInput.toLowerCase() === 'exit') {
43
- this.rl.close();
44
- return;
45
- }
46
- if (this.chatId === undefined) {
47
- this.chatId = readJsonFromFile(chatIdPath) ?? undefined;
48
- if (!this.chatId) {
49
- this.chatId = Random.alphaNumericLowerCase(10);
50
- writeJsonToFile(chatIdPath, this.chatId);
51
- }
52
- }
53
- const chatConnection = {
54
- id: this.chatId,
55
- chatType: 'PRIVATE',
56
- channelName: CmdChannel_1.channelName,
57
- };
58
- if (!this.callBack)
59
- return;
60
- if (!this.auth.isAssigned()) {
61
- const authInfo = readJsonFromFile(authInfoPath);
62
- if (authInfo) {
63
- this.auth.assign(authInfo);
64
- }
65
- }
66
- await this.callBack({
67
- channel: cmdChannelName,
68
- chatConnection,
69
- message: {
70
- text: trimmedInput,
71
- },
72
- reply: async (message) => {
73
- console.log(`\n[${message.senderName}]: ${message.text}\n`);
74
- this.rl.prompt();
75
- if (this.auth.isAssigned()) {
76
- writeJsonToFile(authInfoPath, this.auth.require());
77
- }
78
- },
79
- injectInstances: [[Auth, this.auth]],
80
- });
35
+ this.server.register(this.config.route, {
36
+ onMessage: async (text, reply) => {
37
+ if (!this.callBack)
38
+ return;
39
+ this.ensureChatId();
40
+ this.ensureAuthLoaded();
41
+ const chatConnection = {
42
+ id: this.chatId,
43
+ chatType: 'PRIVATE',
44
+ channelName: CmdChannel_1.channelName,
45
+ };
46
+ await this.callBack({
47
+ channel: cmdChannelName,
48
+ chatConnection,
49
+ message: { text },
50
+ reply: async (message) => {
51
+ reply({
52
+ senderName: message.senderName,
53
+ text: extractDisplayText(message),
54
+ });
55
+ if (this.auth.isAssigned()) {
56
+ writeJsonToFile(this.authInfoPath(), this.auth.require());
57
+ }
58
+ },
59
+ injectInstances: [[Auth, this.auth]],
60
+ });
61
+ },
81
62
  });
82
63
  }
64
+ routeSlug() {
65
+ return this.config.route.replace(/[^a-zA-Z0-9._-]/g, '_');
66
+ }
67
+ chatIdPath() {
68
+ return path.join('.cmd-channel', this.routeSlug(), 'id.json');
69
+ }
70
+ authInfoPath() {
71
+ return path.join('.cmd-channel', this.routeSlug(), 'auth-info.json');
72
+ }
73
+ ensureChatId() {
74
+ if (this.chatId !== undefined)
75
+ return;
76
+ this.chatId = readJsonFromFile(this.chatIdPath()) ?? undefined;
77
+ if (!this.chatId) {
78
+ this.chatId = Random.alphaNumericLowerCase(10);
79
+ writeJsonToFile(this.chatIdPath(), this.chatId);
80
+ }
81
+ }
82
+ ensureAuthLoaded() {
83
+ if (this.auth.isAssigned())
84
+ return;
85
+ const authInfo = readJsonFromFile(this.authInfoPath());
86
+ if (authInfo) {
87
+ this.auth.assign(authInfo);
88
+ }
89
+ }
83
90
  };
84
91
  CmdChannel = CmdChannel_1 = __decorate([
85
92
  injectable(),
86
- __metadata("design:paramtypes", [Auth])
93
+ __metadata("design:paramtypes", [Auth,
94
+ CmdChannelServer,
95
+ CmdChannelConfig])
87
96
  ], CmdChannel);
97
+ function extractDisplayText(message) {
98
+ const raw = message.text ?? '';
99
+ const trimmed = raw.trim();
100
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('['))
101
+ return raw;
102
+ try {
103
+ const parsed = JSON.parse(trimmed);
104
+ if (parsed && typeof parsed === 'object' && typeof parsed.text === 'string') {
105
+ return parsed.text;
106
+ }
107
+ }
108
+ catch {
109
+ // not JSON — fall through and return raw
110
+ }
111
+ return raw;
112
+ }
88
113
  function writeJsonToFile(filename, data) {
89
114
  const filePath = path.resolve(process.cwd(), filename);
90
115
  const dir = path.dirname(filePath);
91
116
  fs.mkdirSync(dir, { recursive: true });
92
117
  fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
93
118
  }
94
- const fileLogger = new Logger('wabot:cmd-channel');
95
119
  function readJsonFromFile(filename) {
96
120
  const filePath = path.resolve(process.cwd(), filename);
97
121
  if (!fs.existsSync(filePath)) {
@@ -0,0 +1,8 @@
1
+ class CmdChannelConfig {
2
+ route;
3
+ constructor(route) {
4
+ this.route = route;
5
+ }
6
+ }
7
+
8
+ export { CmdChannelConfig };
@@ -0,0 +1,169 @@
1
+ import { __decorate } from 'tslib';
2
+ import * as fs from 'node:fs';
3
+ import * as net from 'node:net';
4
+ import * as path from 'node:path';
5
+ import { singleton } from '../../../core/injection/index.js';
6
+ import { Logger } from '../../../core/logger/Logger.js';
7
+ import { cmdChannelSocketPath } from './cmdChannelSocketPath.js';
8
+
9
+ const logger = new Logger('wabot:cmd-channel-server');
10
+ let CmdChannelServer = class CmdChannelServer {
11
+ server = null;
12
+ channels = new Map();
13
+ client = null;
14
+ buffer = '';
15
+ activeRoute = null;
16
+ register(route, handlers) {
17
+ this.channels.set(route, handlers);
18
+ this.ensureStarted();
19
+ }
20
+ unregister(route) {
21
+ this.channels.delete(route);
22
+ if (this.activeRoute === route) {
23
+ this.activeRoute = null;
24
+ }
25
+ }
26
+ ensureStarted() {
27
+ if (this.server)
28
+ return;
29
+ const socketPath = cmdChannelSocketPath();
30
+ if (process.platform !== 'win32') {
31
+ try {
32
+ fs.unlinkSync(socketPath);
33
+ }
34
+ catch {
35
+ // ignore: socket file did not exist
36
+ }
37
+ fs.mkdirSync(path.dirname(socketPath), { recursive: true });
38
+ }
39
+ this.server = net.createServer((socket) => this.handleConnection(socket));
40
+ this.server.on('error', (err) => logger.error('cmd channel server error', err));
41
+ this.server.listen(socketPath, () => {
42
+ logger.info(`cmd channel server listening at ${socketPath}`);
43
+ });
44
+ process.once('exit', () => this.shutdown());
45
+ process.once('SIGINT', () => {
46
+ this.shutdown();
47
+ process.exit(0);
48
+ });
49
+ process.once('SIGTERM', () => {
50
+ this.shutdown();
51
+ process.exit(0);
52
+ });
53
+ }
54
+ shutdown() {
55
+ if (this.client) {
56
+ this.client.destroy();
57
+ this.client = null;
58
+ }
59
+ if (this.server) {
60
+ this.server.close();
61
+ this.server = null;
62
+ }
63
+ if (process.platform !== 'win32') {
64
+ try {
65
+ fs.unlinkSync(cmdChannelSocketPath());
66
+ }
67
+ catch {
68
+ // ignore
69
+ }
70
+ }
71
+ }
72
+ handleConnection(socket) {
73
+ if (this.client) {
74
+ this.send(socket, {
75
+ type: 'error',
76
+ message: 'Another cmd client is already connected',
77
+ });
78
+ socket.end();
79
+ return;
80
+ }
81
+ this.client = socket;
82
+ this.buffer = '';
83
+ this.activeRoute = null;
84
+ socket.on('data', (chunk) => this.handleData(chunk.toString()));
85
+ socket.on('close', () => {
86
+ if (this.client === socket) {
87
+ this.client = null;
88
+ this.activeRoute = null;
89
+ this.buffer = '';
90
+ }
91
+ });
92
+ socket.on('error', (err) => {
93
+ logger.warn('cmd client connection error', err);
94
+ if (this.client === socket) {
95
+ this.client = null;
96
+ this.activeRoute = null;
97
+ }
98
+ });
99
+ }
100
+ handleData(data) {
101
+ this.buffer += data;
102
+ let idx;
103
+ while ((idx = this.buffer.indexOf('\n')) !== -1) {
104
+ const line = this.buffer.slice(0, idx);
105
+ this.buffer = this.buffer.slice(idx + 1);
106
+ if (!line)
107
+ continue;
108
+ let msg;
109
+ try {
110
+ msg = JSON.parse(line);
111
+ }
112
+ catch {
113
+ this.sendToClient({ type: 'error', message: 'invalid json' });
114
+ continue;
115
+ }
116
+ this.handleMessage(msg).catch((err) => {
117
+ logger.error('cmd channel server error handling message', err);
118
+ this.sendToClient({ type: 'error', message: err.message });
119
+ });
120
+ }
121
+ }
122
+ async handleMessage(msg) {
123
+ switch (msg.type) {
124
+ case 'hello':
125
+ this.sendToClient({
126
+ type: 'channels',
127
+ list: Array.from(this.channels.keys()).map((route) => ({ route })),
128
+ });
129
+ return;
130
+ case 'select':
131
+ if (!this.channels.has(msg.route)) {
132
+ this.sendToClient({ type: 'error', message: `unknown route: ${msg.route}` });
133
+ return;
134
+ }
135
+ this.activeRoute = msg.route;
136
+ this.sendToClient({ type: 'selected', route: msg.route });
137
+ return;
138
+ case 'message': {
139
+ if (!this.activeRoute) {
140
+ this.sendToClient({ type: 'error', message: 'no channel selected' });
141
+ return;
142
+ }
143
+ const handlers = this.channels.get(this.activeRoute);
144
+ if (!handlers) {
145
+ this.sendToClient({ type: 'error', message: 'channel no longer available' });
146
+ this.activeRoute = null;
147
+ return;
148
+ }
149
+ await handlers.onMessage(msg.text, (reply) => {
150
+ this.sendToClient({ type: 'reply', ...reply });
151
+ });
152
+ return;
153
+ }
154
+ }
155
+ }
156
+ sendToClient(msg) {
157
+ if (!this.client)
158
+ return;
159
+ this.send(this.client, msg);
160
+ }
161
+ send(socket, msg) {
162
+ socket.write(JSON.stringify(msg) + '\n');
163
+ }
164
+ };
165
+ CmdChannelServer = __decorate([
166
+ singleton()
167
+ ], CmdChannelServer);
168
+
169
+ export { CmdChannelServer };
@@ -0,0 +1,16 @@
1
+ import * as crypto from 'node:crypto';
2
+ import * as path from 'node:path';
3
+
4
+ function cmdChannelSocketPath() {
5
+ if (process.platform === 'win32') {
6
+ const cwdHash = crypto
7
+ .createHash('sha1')
8
+ .update(process.cwd())
9
+ .digest('hex')
10
+ .slice(0, 12);
11
+ return `\\\\.\\pipe\\wabot-cmd-channel-${cwdHash}`;
12
+ }
13
+ return path.resolve(process.cwd(), '.cmd-channel/socket');
14
+ }
15
+
16
+ export { cmdChannelSocketPath };