@zetra/citrineos-util 1.8.3-fork.1
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.
- package/dist/authorization/ApiAuthPlugin.d.ts +52 -0
- package/dist/authorization/ApiAuthPlugin.js +122 -0
- package/dist/authorization/ApiAuthPlugin.js.map +1 -0
- package/dist/authorization/OidcTokenProvider.d.ts +15 -0
- package/dist/authorization/OidcTokenProvider.js +47 -0
- package/dist/authorization/OidcTokenProvider.js.map +1 -0
- package/dist/authorization/index.d.ts +4 -0
- package/dist/authorization/index.js +8 -0
- package/dist/authorization/index.js.map +1 -0
- package/dist/authorization/provider/LocalByPassAuthProvider.d.ts +34 -0
- package/dist/authorization/provider/LocalByPassAuthProvider.js +62 -0
- package/dist/authorization/provider/LocalByPassAuthProvider.js.map +1 -0
- package/dist/authorization/provider/OIDCAuthProvider.d.ts +62 -0
- package/dist/authorization/provider/OIDCAuthProvider.js +173 -0
- package/dist/authorization/provider/OIDCAuthProvider.js.map +1 -0
- package/dist/authorization/rbac/RbacRulesLoader.d.ts +32 -0
- package/dist/authorization/rbac/RbacRulesLoader.js +105 -0
- package/dist/authorization/rbac/RbacRulesLoader.js.map +1 -0
- package/dist/authorization/rbac/UrlMatcher.d.ts +14 -0
- package/dist/authorization/rbac/UrlMatcher.js +44 -0
- package/dist/authorization/rbac/UrlMatcher.js.map +1 -0
- package/dist/authorizer/RealTimeAuthorizer.d.ts +28 -0
- package/dist/authorizer/RealTimeAuthorizer.js +152 -0
- package/dist/authorizer/RealTimeAuthorizer.js.map +1 -0
- package/dist/authorizer/index.d.ts +1 -0
- package/dist/authorizer/index.js +5 -0
- package/dist/authorizer/index.js.map +1 -0
- package/dist/cache/memory.d.ts +19 -0
- package/dist/cache/memory.js +147 -0
- package/dist/cache/memory.js.map +1 -0
- package/dist/cache/redis.d.ts +16 -0
- package/dist/cache/redis.js +120 -0
- package/dist/cache/redis.js.map +1 -0
- package/dist/certificate/CertificateAuthority.d.ts +38 -0
- package/dist/certificate/CertificateAuthority.js +233 -0
- package/dist/certificate/CertificateAuthority.js.map +1 -0
- package/dist/certificate/CertificateUtil.d.ts +60 -0
- package/dist/certificate/CertificateUtil.js +317 -0
- package/dist/certificate/CertificateUtil.js.map +1 -0
- package/dist/certificate/client/acme.d.ts +37 -0
- package/dist/certificate/client/acme.js +138 -0
- package/dist/certificate/client/acme.js.map +1 -0
- package/dist/certificate/client/hubject.d.ts +41 -0
- package/dist/certificate/client/hubject.js +221 -0
- package/dist/certificate/client/hubject.js.map +1 -0
- package/dist/certificate/client/interface.d.ts +12 -0
- package/dist/certificate/client/interface.js +5 -0
- package/dist/certificate/client/interface.js.map +1 -0
- package/dist/certificate/index.d.ts +2 -0
- package/dist/certificate/index.js +6 -0
- package/dist/certificate/index.js.map +1 -0
- package/dist/files/ftpServer.d.ts +4 -0
- package/dist/files/ftpServer.js +9 -0
- package/dist/files/ftpServer.js.map +1 -0
- package/dist/files/gcpCloudStorage.d.ts +39 -0
- package/dist/files/gcpCloudStorage.js +130 -0
- package/dist/files/gcpCloudStorage.js.map +1 -0
- package/dist/files/localStorage.d.ts +14 -0
- package/dist/files/localStorage.js +57 -0
- package/dist/files/localStorage.js.map +1 -0
- package/dist/files/s3Storage.d.ts +17 -0
- package/dist/files/s3Storage.js +118 -0
- package/dist/files/s3Storage.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/networkconnection/WebsocketNetworkConnection.d.ts +135 -0
- package/dist/networkconnection/WebsocketNetworkConnection.js +474 -0
- package/dist/networkconnection/WebsocketNetworkConnection.js.map +1 -0
- package/dist/networkconnection/authenticator/Authenticator.d.ts +20 -0
- package/dist/networkconnection/authenticator/Authenticator.js +39 -0
- package/dist/networkconnection/authenticator/Authenticator.js.map +1 -0
- package/dist/networkconnection/authenticator/AuthenticatorFilter.d.ts +11 -0
- package/dist/networkconnection/authenticator/AuthenticatorFilter.js +30 -0
- package/dist/networkconnection/authenticator/AuthenticatorFilter.js.map +1 -0
- package/dist/networkconnection/authenticator/BasicAuthenticationFilter.d.ts +17 -0
- package/dist/networkconnection/authenticator/BasicAuthenticationFilter.js +51 -0
- package/dist/networkconnection/authenticator/BasicAuthenticationFilter.js.map +1 -0
- package/dist/networkconnection/authenticator/ConnectedStationFilter.d.ts +14 -0
- package/dist/networkconnection/authenticator/ConnectedStationFilter.js +25 -0
- package/dist/networkconnection/authenticator/ConnectedStationFilter.js.map +1 -0
- package/dist/networkconnection/authenticator/NetworkProfileFilter.d.ts +16 -0
- package/dist/networkconnection/authenticator/NetworkProfileFilter.js +84 -0
- package/dist/networkconnection/authenticator/NetworkProfileFilter.js.map +1 -0
- package/dist/networkconnection/authenticator/UnknownStationFilter.d.ts +16 -0
- package/dist/networkconnection/authenticator/UnknownStationFilter.js +25 -0
- package/dist/networkconnection/authenticator/UnknownStationFilter.js.map +1 -0
- package/dist/networkconnection/authenticator/errors/AuthenticationError.d.ts +6 -0
- package/dist/networkconnection/authenticator/errors/AuthenticationError.js +25 -0
- package/dist/networkconnection/authenticator/errors/AuthenticationError.js.map +1 -0
- package/dist/networkconnection/authenticator/errors/IUpgradeError.d.ts +9 -0
- package/dist/networkconnection/authenticator/errors/IUpgradeError.js +5 -0
- package/dist/networkconnection/authenticator/errors/IUpgradeError.js.map +1 -0
- package/dist/networkconnection/authenticator/errors/UnknownError.d.ts +6 -0
- package/dist/networkconnection/authenticator/errors/UnknownError.js +24 -0
- package/dist/networkconnection/authenticator/errors/UnknownError.js.map +1 -0
- package/dist/networkconnection/index.d.ts +5 -0
- package/dist/networkconnection/index.js +9 -0
- package/dist/networkconnection/index.js.map +1 -0
- package/dist/queue/index.d.ts +4 -0
- package/dist/queue/index.js +8 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/kafka/receiver.d.ts +35 -0
- package/dist/queue/kafka/receiver.js +179 -0
- package/dist/queue/kafka/receiver.js.map +1 -0
- package/dist/queue/kafka/sender.d.ts +53 -0
- package/dist/queue/kafka/sender.js +189 -0
- package/dist/queue/kafka/sender.js.map +1 -0
- package/dist/queue/rabbit-mq/receiver.d.ts +89 -0
- package/dist/queue/rabbit-mq/receiver.js +472 -0
- package/dist/queue/rabbit-mq/receiver.js.map +1 -0
- package/dist/queue/rabbit-mq/sender.d.ts +90 -0
- package/dist/queue/rabbit-mq/sender.js +251 -0
- package/dist/queue/rabbit-mq/sender.js.map +1 -0
- package/dist/security/SignedMeterValuesUtil.d.ts +44 -0
- package/dist/security/SignedMeterValuesUtil.js +135 -0
- package/dist/security/SignedMeterValuesUtil.js.map +1 -0
- package/dist/security/authentication.d.ts +2 -0
- package/dist/security/authentication.js +26 -0
- package/dist/security/authentication.js.map +1 -0
- package/dist/util/RequestOperations.d.ts +14 -0
- package/dist/util/RequestOperations.js +25 -0
- package/dist/util/RequestOperations.js.map +1 -0
- package/dist/util/StringOperations.d.ts +1 -0
- package/dist/util/StringOperations.js +8 -0
- package/dist/util/StringOperations.js.map +1 -0
- package/dist/util/emaidCheckDigitCalculator.d.ts +15 -0
- package/dist/util/emaidCheckDigitCalculator.js +179 -0
- package/dist/util/emaidCheckDigitCalculator.js.map +1 -0
- package/dist/util/idGenerator.d.ts +7 -0
- package/dist/util/idGenerator.js +10 -0
- package/dist/util/idGenerator.js.map +1 -0
- package/dist/util/parser.d.ts +31 -0
- package/dist/util/parser.js +60 -0
- package/dist/util/parser.js.map +1 -0
- package/dist/util/swagger.d.ts +5 -0
- package/dist/util/swagger.js +154 -0
- package/dist/util/swagger.js.map +1 -0
- package/dist/util/validator.d.ts +110 -0
- package/dist/util/validator.js +534 -0
- package/dist/util/validator.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Logger } from 'tslog';
|
|
2
|
+
import { CryptoUtils } from '@citrineos/data';
|
|
3
|
+
import { IncomingMessage } from 'http';
|
|
4
|
+
import { extractBasicCredentials } from '../../util/RequestOperations.js';
|
|
5
|
+
import { AuthenticatorFilter } from './AuthenticatorFilter.js';
|
|
6
|
+
import { OCPP2_0_1 } from '@citrineos/base';
|
|
7
|
+
import { UpgradeAuthenticationError } from './errors/AuthenticationError.js';
|
|
8
|
+
/**
|
|
9
|
+
* Filter used to authenticate incoming HTTP requests based on basic authorization header.
|
|
10
|
+
* It only applies when the security profile is set to 1 or 2.
|
|
11
|
+
*/
|
|
12
|
+
export class BasicAuthenticationFilter extends AuthenticatorFilter {
|
|
13
|
+
_deviceModelRepository;
|
|
14
|
+
constructor(deviceModelRepository, logger) {
|
|
15
|
+
super(logger);
|
|
16
|
+
this._deviceModelRepository = deviceModelRepository;
|
|
17
|
+
}
|
|
18
|
+
shouldFilter(options) {
|
|
19
|
+
return options.securityProfile === 1 || options.securityProfile === 2;
|
|
20
|
+
}
|
|
21
|
+
async filter(tenantId, identifier, request) {
|
|
22
|
+
const { username, password } = extractBasicCredentials(request);
|
|
23
|
+
if (!username || !password) {
|
|
24
|
+
throw new UpgradeAuthenticationError('Auth header missing or incorrectly formatted');
|
|
25
|
+
}
|
|
26
|
+
if (username !== identifier || !(await this._isPasswordValid(tenantId, username, password))) {
|
|
27
|
+
throw new UpgradeAuthenticationError(`Unauthorized ${identifier}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async _isPasswordValid(tenantId, username, password) {
|
|
31
|
+
return await this._deviceModelRepository
|
|
32
|
+
.readAllByQuerystring(tenantId, {
|
|
33
|
+
tenantId,
|
|
34
|
+
stationId: username,
|
|
35
|
+
component_name: 'SecurityCtrlr',
|
|
36
|
+
variable_name: 'BasicAuthPassword',
|
|
37
|
+
type: OCPP2_0_1.AttributeEnumType.Actual,
|
|
38
|
+
})
|
|
39
|
+
.then((r) => {
|
|
40
|
+
if (r && r[0]) {
|
|
41
|
+
const hashedPassword = r[0].value;
|
|
42
|
+
if (hashedPassword) {
|
|
43
|
+
return CryptoUtils.isPasswordMatch(hashedPassword, password);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
this._logger.warn('Has no password', username);
|
|
47
|
+
return false;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=BasicAuthenticationFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BasicAuthenticationFilter.js","sourceRoot":"","sources":["../../../src/networkconnection/authenticator/BasicAuthenticationFilter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE7E;;;GAGG;AACH,MAAM,OAAO,yBAA0B,SAAQ,mBAAmB;IACxD,sBAAsB,CAAyB;IAEvD,YAAY,qBAA6C,EAAE,MAAwB;QACjF,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,sBAAsB,GAAG,qBAAqB,CAAC;IACtD,CAAC;IAES,YAAY,CAAC,OAA8B;QACnD,OAAO,OAAO,CAAC,eAAe,KAAK,CAAC,IAAI,OAAO,CAAC,eAAe,KAAK,CAAC,CAAC;IACxE,CAAC;IAES,KAAK,CAAC,MAAM,CACpB,QAAgB,EAChB,UAAkB,EAClB,OAAwB;QAExB,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;QAChE,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,IAAI,0BAA0B,CAAC,8CAA8C,CAAC,CAAC;QACvF,CAAC;QAED,IAAI,QAAQ,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;YAC5F,MAAM,IAAI,0BAA0B,CAAC,gBAAgB,UAAU,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAAC,QAAgB,EAAE,QAAgB,EAAE,QAAgB;QACjF,OAAO,MAAM,IAAI,CAAC,sBAAsB;aACrC,oBAAoB,CAAC,QAAQ,EAAE;YAC9B,QAAQ;YACR,SAAS,EAAE,QAAQ;YACnB,cAAc,EAAE,eAAe;YAC/B,aAAa,EAAE,mBAAmB;YAClC,IAAI,EAAE,SAAS,CAAC,iBAAiB,CAAC,MAAM;SACzC,CAAC;aACD,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;YACV,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACd,MAAM,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;gBAClC,IAAI,cAAc,EAAE,CAAC;oBACnB,OAAO,WAAW,CAAC,eAAe,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;gBAC/D,CAAC;YACH,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;YAC/C,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;IACP,CAAC;CACF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AuthenticationOptions, ICache } from '@citrineos/base';
|
|
2
|
+
import type { ILogObj } from 'tslog';
|
|
3
|
+
import { Logger } from 'tslog';
|
|
4
|
+
import { IncomingMessage } from 'http';
|
|
5
|
+
import { AuthenticatorFilter } from './AuthenticatorFilter.js';
|
|
6
|
+
/**
|
|
7
|
+
* Filter used to prevent multiple simultaneous connections for the same charging station.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ConnectedStationFilter extends AuthenticatorFilter {
|
|
10
|
+
private _cache;
|
|
11
|
+
constructor(cache: ICache, logger?: Logger<ILogObj>);
|
|
12
|
+
protected shouldFilter(_options: AuthenticationOptions): boolean;
|
|
13
|
+
protected filter(tenantId: number, identifier: string, _request: IncomingMessage): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CacheNamespace, notNull } from '@citrineos/base';
|
|
2
|
+
import { Logger } from 'tslog';
|
|
3
|
+
import { IncomingMessage } from 'http';
|
|
4
|
+
import { AuthenticatorFilter } from './AuthenticatorFilter.js';
|
|
5
|
+
import { UpgradeAuthenticationError } from './errors/AuthenticationError.js';
|
|
6
|
+
/**
|
|
7
|
+
* Filter used to prevent multiple simultaneous connections for the same charging station.
|
|
8
|
+
*/
|
|
9
|
+
export class ConnectedStationFilter extends AuthenticatorFilter {
|
|
10
|
+
_cache;
|
|
11
|
+
constructor(cache, logger) {
|
|
12
|
+
super(logger);
|
|
13
|
+
this._cache = cache;
|
|
14
|
+
}
|
|
15
|
+
shouldFilter(_options) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
async filter(tenantId, identifier, _request) {
|
|
19
|
+
const isAlreadyConnected = notNull(await this._cache.get(identifier, CacheNamespace.Connections));
|
|
20
|
+
if (isAlreadyConnected) {
|
|
21
|
+
throw new UpgradeAuthenticationError(`New connection attempted for already connected identifier ${identifier}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=ConnectedStationFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConnectedStationFilter.js","sourceRoot":"","sources":["../../../src/networkconnection/authenticator/ConnectedStationFilter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE7E;;GAEG;AACH,MAAM,OAAO,sBAAuB,SAAQ,mBAAmB;IACrD,MAAM,CAAS;IAEvB,YAAY,KAAa,EAAE,MAAwB;QACjD,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAES,YAAY,CAAC,QAA+B;QACpD,OAAO,IAAI,CAAC;IACd,CAAC;IAES,KAAK,CAAC,MAAM,CACpB,QAAgB,EAChB,UAAkB,EAClB,QAAyB;QAEzB,MAAM,kBAAkB,GAAG,OAAO,CAChC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,cAAc,CAAC,WAAW,CAAC,CAC9D,CAAC;QACF,IAAI,kBAAkB,EAAE,CAAC;YACvB,MAAM,IAAI,0BAA0B,CAClC,6DAA6D,UAAU,EAAE,CAC1E,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ILogObj } from 'tslog';
|
|
2
|
+
import { Logger } from 'tslog';
|
|
3
|
+
import type { IDeviceModelRepository } from '@citrineos/data';
|
|
4
|
+
import { IncomingMessage } from 'http';
|
|
5
|
+
import { AuthenticatorFilter } from './AuthenticatorFilter.js';
|
|
6
|
+
import type { AuthenticationOptions } from '@citrineos/base';
|
|
7
|
+
/**
|
|
8
|
+
* Filter used to block connections when charging stations attempt to connect to disallowed security profiles
|
|
9
|
+
*/
|
|
10
|
+
export declare class NetworkProfileFilter extends AuthenticatorFilter {
|
|
11
|
+
private _deviceModelRepository;
|
|
12
|
+
constructor(deviceModelRepository: IDeviceModelRepository, logger?: Logger<ILogObj>);
|
|
13
|
+
protected shouldFilter(_options: AuthenticationOptions): boolean;
|
|
14
|
+
protected filter(tenantId: number, identifier: string, request: IncomingMessage, options: AuthenticationOptions): Promise<void>;
|
|
15
|
+
private _isConfigurationSlotAllowed;
|
|
16
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Logger } from 'tslog';
|
|
2
|
+
import { ChargingStationNetworkProfile, ServerNetworkProfile } from '@citrineos/data';
|
|
3
|
+
import { IncomingMessage } from 'http';
|
|
4
|
+
import { AuthenticatorFilter } from './AuthenticatorFilter.js';
|
|
5
|
+
import { OCPP2_0_1 } from '@citrineos/base';
|
|
6
|
+
import { UpgradeAuthenticationError } from './errors/AuthenticationError.js';
|
|
7
|
+
/**
|
|
8
|
+
* Filter used to block connections when charging stations attempt to connect to disallowed security profiles
|
|
9
|
+
*/
|
|
10
|
+
export class NetworkProfileFilter extends AuthenticatorFilter {
|
|
11
|
+
_deviceModelRepository;
|
|
12
|
+
constructor(deviceModelRepository, logger) {
|
|
13
|
+
super(logger);
|
|
14
|
+
this._deviceModelRepository = deviceModelRepository;
|
|
15
|
+
}
|
|
16
|
+
shouldFilter(_options) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
async filter(tenantId, identifier, request, options) {
|
|
20
|
+
const isConfigurationSlotAllowed = await this._isConfigurationSlotAllowed(tenantId, identifier, options.securityProfile);
|
|
21
|
+
if (!isConfigurationSlotAllowed) {
|
|
22
|
+
throw new UpgradeAuthenticationError(`SecurityProfile not allowed ${options.securityProfile}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async _isConfigurationSlotAllowed(tenantId, identifier, securityProfile) {
|
|
26
|
+
const r = await this._deviceModelRepository.readAllByQuerystring(tenantId, {
|
|
27
|
+
tenantId,
|
|
28
|
+
stationId: identifier,
|
|
29
|
+
component_name: 'OCPPCommCtrlr',
|
|
30
|
+
variable_name: 'NetworkConfigurationPriority',
|
|
31
|
+
type: OCPP2_0_1.AttributeEnumType.Actual,
|
|
32
|
+
});
|
|
33
|
+
if (r && r[0]) {
|
|
34
|
+
const configurationSlotsString = r[0].value;
|
|
35
|
+
if (configurationSlotsString && configurationSlotsString.trim() !== '') {
|
|
36
|
+
// Split the string by commas to get an array of string numbers
|
|
37
|
+
const configurationSlotStringsArray = configurationSlotsString.split(',');
|
|
38
|
+
// Parse the array into numbers and filter out the rest
|
|
39
|
+
const configurationSlotsArray = configurationSlotStringsArray
|
|
40
|
+
.map((configurationSlotString) => parseInt(configurationSlotString, 10))
|
|
41
|
+
.filter((configurationSlotId) => {
|
|
42
|
+
if (isNaN(configurationSlotId)) {
|
|
43
|
+
this._logger.error('NetworkConfigurationPriority elements must be integers: ' +
|
|
44
|
+
configurationSlotsString);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
if (configurationSlotsArray.length == 0) {
|
|
52
|
+
this._logger.debug('No valid configuration slots to check: ' + configurationSlotsString);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
let securityProfileAllowed = false;
|
|
57
|
+
for (const configurationSlot of configurationSlotsArray) {
|
|
58
|
+
const chargingStationNetworkProfile = await ChargingStationNetworkProfile.findOne({
|
|
59
|
+
where: { stationId: identifier, configurationSlot: configurationSlot },
|
|
60
|
+
});
|
|
61
|
+
if (chargingStationNetworkProfile) {
|
|
62
|
+
const serverNetworkProfile = await ServerNetworkProfile.findByPk(chargingStationNetworkProfile.websocketServerConfigId);
|
|
63
|
+
if (serverNetworkProfile && securityProfile >= serverNetworkProfile.securityProfile) {
|
|
64
|
+
this._logger.debug('Security profile allowed');
|
|
65
|
+
securityProfileAllowed = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
this._logger.warn('Unknown configuration slot; skipping security profile network profile check.');
|
|
70
|
+
securityProfileAllowed = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!securityProfileAllowed) {
|
|
74
|
+
this._logger.warn(`Station ${identifier} unable to connect with security profile ${securityProfile}`);
|
|
75
|
+
}
|
|
76
|
+
return securityProfileAllowed;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this._logger.warn('Has no configuration slots configured');
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=NetworkProfileFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NetworkProfileFilter.js","sourceRoot":"","sources":["../../../src/networkconnection/authenticator/NetworkProfileFilter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ILogObj } from 'tslog';
|
|
2
|
+
import { Logger } from 'tslog';
|
|
3
|
+
import type { ILocationRepository } from '@citrineos/data';
|
|
4
|
+
import { IncomingMessage } from 'http';
|
|
5
|
+
import { AuthenticatorFilter } from './AuthenticatorFilter.js';
|
|
6
|
+
import type { AuthenticationOptions } from '@citrineos/base';
|
|
7
|
+
/**
|
|
8
|
+
* Filter used to block connections from charging stations that are not recognized in the system.
|
|
9
|
+
* It only applies when unknown charging stations are not allowed.
|
|
10
|
+
*/
|
|
11
|
+
export declare class UnknownStationFilter extends AuthenticatorFilter {
|
|
12
|
+
private _locationRepository;
|
|
13
|
+
constructor(locationRepository: ILocationRepository, logger?: Logger<ILogObj>);
|
|
14
|
+
protected shouldFilter(options: AuthenticationOptions): boolean;
|
|
15
|
+
protected filter(tenantId: number, identifier: string, _request: IncomingMessage): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Logger } from 'tslog';
|
|
2
|
+
import { IncomingMessage } from 'http';
|
|
3
|
+
import { AuthenticatorFilter } from './AuthenticatorFilter.js';
|
|
4
|
+
import { UpgradeUnknownError } from './errors/UnknownError.js';
|
|
5
|
+
/**
|
|
6
|
+
* Filter used to block connections from charging stations that are not recognized in the system.
|
|
7
|
+
* It only applies when unknown charging stations are not allowed.
|
|
8
|
+
*/
|
|
9
|
+
export class UnknownStationFilter extends AuthenticatorFilter {
|
|
10
|
+
_locationRepository;
|
|
11
|
+
constructor(locationRepository, logger) {
|
|
12
|
+
super(logger);
|
|
13
|
+
this._locationRepository = locationRepository;
|
|
14
|
+
}
|
|
15
|
+
shouldFilter(options) {
|
|
16
|
+
return !options.allowUnknownChargingStations;
|
|
17
|
+
}
|
|
18
|
+
async filter(tenantId, identifier, _request) {
|
|
19
|
+
const isStationKnown = await this._locationRepository.doesChargingStationExistByStationId(tenantId, identifier);
|
|
20
|
+
if (!isStationKnown) {
|
|
21
|
+
throw new UpgradeUnknownError(`Unknown identifier ${identifier}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=UnknownStationFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnknownStationFilter.js","sourceRoot":"","sources":["../../../src/networkconnection/authenticator/UnknownStationFilter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D;;;GAGG;AACH,MAAM,OAAO,oBAAqB,SAAQ,mBAAmB;IACnD,mBAAmB,CAAsB;IAEjD,YAAY,kBAAuC,EAAE,MAAwB;QAC3E,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,mBAAmB,GAAG,kBAAkB,CAAC;IAChD,CAAC;IAES,YAAY,CAAC,OAA8B;QACnD,OAAO,CAAC,OAAO,CAAC,4BAA4B,CAAC;IAC/C,CAAC;IAES,KAAK,CAAC,MAAM,CACpB,QAAgB,EAChB,UAAkB,EAClB,QAAyB;QAEzB,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,mCAAmC,CACvF,QAAQ,EACR,UAAU,CACX,CAAC;QACF,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,IAAI,mBAAmB,CAAC,sBAAsB,UAAU,EAAE,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { Duplex } from 'stream';
|
|
5
|
+
export class UpgradeAuthenticationError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'UpgradeAuthenticationError';
|
|
9
|
+
}
|
|
10
|
+
terminateConnection(socket) {
|
|
11
|
+
try {
|
|
12
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\n');
|
|
13
|
+
socket.write('WWW-Authenticate: Basic realm="Access to the WebSocket", charset="UTF-8"\r\n');
|
|
14
|
+
socket.write('\r\n');
|
|
15
|
+
socket.end();
|
|
16
|
+
socket.destroy();
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
this.message = error.message;
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=AuthenticationError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthenticationError.js","sourceRoot":"","sources":["../../../../src/networkconnection/authenticator/errors/AuthenticationError.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AAEtC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACnD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC3C,CAAC;IAED,mBAAmB,CAAC,MAAc;QAChC,IAAI,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;YAC9C,MAAM,CAAC,KAAK,CAAC,8EAA8E,CAAC,CAAC;YAC7F,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACrB,MAAM,CAAC,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,GAAI,KAAe,CAAC,OAAO,CAAC;YACxC,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Duplex } from 'stream';
|
|
2
|
+
export interface IUpgradeError {
|
|
3
|
+
/**
|
|
4
|
+
* Terminates the WebSocket connection by sending an error response and closing the socket.
|
|
5
|
+
* @param {Duplex} socket - The WebSocket duplex stream.
|
|
6
|
+
* @returns {boolean} True if the connection was terminated successfully, false otherwise.
|
|
7
|
+
*/
|
|
8
|
+
terminateConnection(socket: Duplex): boolean;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IUpgradeError.js","sourceRoot":"","sources":["../../../../src/networkconnection/authenticator/errors/IUpgradeError.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AAEtC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { Duplex } from 'stream';
|
|
5
|
+
export class UpgradeUnknownError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'UpgradeUnknownError';
|
|
9
|
+
}
|
|
10
|
+
terminateConnection(socket) {
|
|
11
|
+
try {
|
|
12
|
+
socket.write('HTTP/1.1 404 Not Found\r\n');
|
|
13
|
+
socket.write('\r\n');
|
|
14
|
+
socket.end();
|
|
15
|
+
socket.destroy();
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
this.message = error.message;
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=UnknownError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnknownError.js","sourceRoot":"","sources":["../../../../src/networkconnection/authenticator/errors/UnknownError.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AAEtC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;IAED,mBAAmB,CAAC,MAAc;QAChC,IAAI,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACrB,MAAM,CAAC,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,GAAI,KAAe,CAAC,OAAO,CAAC;YACxC,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Authenticator } from './authenticator/Authenticator.js';
|
|
2
|
+
export { WebsocketNetworkConnection } from './WebsocketNetworkConnection.js';
|
|
3
|
+
export { BasicAuthenticationFilter } from './authenticator/BasicAuthenticationFilter.js';
|
|
4
|
+
export { ConnectedStationFilter } from './authenticator/ConnectedStationFilter.js';
|
|
5
|
+
export { UnknownStationFilter } from './authenticator/UnknownStationFilter.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
export { Authenticator } from './authenticator/Authenticator.js';
|
|
5
|
+
export { WebsocketNetworkConnection } from './WebsocketNetworkConnection.js';
|
|
6
|
+
export { BasicAuthenticationFilter } from './authenticator/BasicAuthenticationFilter.js';
|
|
7
|
+
export { ConnectedStationFilter } from './authenticator/ConnectedStationFilter.js';
|
|
8
|
+
export { UnknownStationFilter } from './authenticator/UnknownStationFilter.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/networkconnection/index.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,8CAA8C,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
export { RabbitMqReceiver } from './rabbit-mq/receiver.js';
|
|
5
|
+
export { RabbitMqSender } from './rabbit-mq/sender.js';
|
|
6
|
+
export { KafkaReceiver } from './kafka/receiver.js';
|
|
7
|
+
export { KafkaSender } from './kafka/sender.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/queue/index.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AAEtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CallAction, IMessageHandler, IModule, SystemConfig } from '@citrineos/base';
|
|
2
|
+
import { AbstractMessageHandler, CircuitBreaker } from '@citrineos/base';
|
|
3
|
+
import type { ILogObj } from 'tslog';
|
|
4
|
+
import { Logger } from 'tslog';
|
|
5
|
+
/**
|
|
6
|
+
* Implementation of a {@link IMessageHandler} using Kafka as the underlying transport.
|
|
7
|
+
*/
|
|
8
|
+
export declare class KafkaReceiver extends AbstractMessageHandler implements IMessageHandler {
|
|
9
|
+
/**
|
|
10
|
+
* Fields
|
|
11
|
+
*/
|
|
12
|
+
private _client;
|
|
13
|
+
private _topicName;
|
|
14
|
+
private _consumerMap;
|
|
15
|
+
private _circuitBreaker;
|
|
16
|
+
private _reconnectInterval?;
|
|
17
|
+
constructor(config: SystemConfig, logger?: Logger<ILogObj>, module?: IModule, circuitBreaker?: CircuitBreaker);
|
|
18
|
+
private _initAdmin;
|
|
19
|
+
subscribe(identifier: string, actions?: CallAction[], filter?: {
|
|
20
|
+
[k: string]: string;
|
|
21
|
+
}): Promise<boolean>;
|
|
22
|
+
unsubscribe(identifier: string): Promise<boolean>;
|
|
23
|
+
shutdown(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Private Methods
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Underlying Kafka message handler.
|
|
29
|
+
*
|
|
30
|
+
* @param message The kafka message to process
|
|
31
|
+
*/
|
|
32
|
+
private _onMessage;
|
|
33
|
+
private _onCircuitBreakerStateChange;
|
|
34
|
+
initConnection(): Promise<void>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { AbstractMessageHandler, CircuitBreaker, Message, OcppError, RetryMessageError, } from '@citrineos/base';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { Kafka } from 'kafkajs';
|
|
4
|
+
import { Logger } from 'tslog';
|
|
5
|
+
/**
|
|
6
|
+
* Implementation of a {@link IMessageHandler} using Kafka as the underlying transport.
|
|
7
|
+
*/
|
|
8
|
+
export class KafkaReceiver extends AbstractMessageHandler {
|
|
9
|
+
/**
|
|
10
|
+
* Fields
|
|
11
|
+
*/
|
|
12
|
+
_client;
|
|
13
|
+
_topicName;
|
|
14
|
+
_consumerMap;
|
|
15
|
+
_circuitBreaker;
|
|
16
|
+
_reconnectInterval;
|
|
17
|
+
constructor(config, logger, module, circuitBreaker) {
|
|
18
|
+
super(config, logger, module);
|
|
19
|
+
this._circuitBreaker = circuitBreaker ?? new CircuitBreaker();
|
|
20
|
+
this._circuitBreaker.onStateChange(this._onCircuitBreakerStateChange.bind(this));
|
|
21
|
+
this._consumerMap = new Map();
|
|
22
|
+
this._client = new Kafka({
|
|
23
|
+
brokers: this._config.util.messageBroker.kafka?.brokers || [],
|
|
24
|
+
ssl: true,
|
|
25
|
+
sasl: {
|
|
26
|
+
mechanism: 'plain',
|
|
27
|
+
username: this._config.util.messageBroker.kafka?.sasl.username || '',
|
|
28
|
+
password: this._config.util.messageBroker.kafka?.sasl.password || '',
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
this._topicName = `${this._config.util.messageBroker.kafka?.topicPrefix}-${this._config.util.messageBroker.kafka?.topicName}`;
|
|
32
|
+
this._initAdmin();
|
|
33
|
+
}
|
|
34
|
+
_initAdmin() {
|
|
35
|
+
const admin = this._client.admin();
|
|
36
|
+
admin
|
|
37
|
+
.connect()
|
|
38
|
+
.then(() => admin.listTopics())
|
|
39
|
+
.then((topics) => {
|
|
40
|
+
this._logger.debug('Topics:', topics);
|
|
41
|
+
if (!topics || topics.filter((topic) => topic === this._topicName).length === 0) {
|
|
42
|
+
this._client
|
|
43
|
+
.admin()
|
|
44
|
+
.createTopics({ topics: [{ topic: this._topicName }] })
|
|
45
|
+
.then(() => {
|
|
46
|
+
this._logger.debug(`Topic ${this._topicName} created.`);
|
|
47
|
+
this._circuitBreaker.triggerSuccess();
|
|
48
|
+
})
|
|
49
|
+
.catch((err) => {
|
|
50
|
+
this._logger.error('Error creating topic', err);
|
|
51
|
+
this._circuitBreaker.triggerFailure(err?.message);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
this._logger.debug(`Topic ${this._topicName} already exists.`);
|
|
56
|
+
this._circuitBreaker.triggerSuccess();
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
.then(() => admin.disconnect())
|
|
60
|
+
.catch((err) => {
|
|
61
|
+
this._logger.error(err);
|
|
62
|
+
this._circuitBreaker.triggerFailure(err?.message);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
subscribe(identifier, actions, filter) {
|
|
66
|
+
if (this._circuitBreaker.state === 'CLOSED') {
|
|
67
|
+
this._logger.error('Circuit breaker is CLOSED. Cannot subscribe to Kafka topic.');
|
|
68
|
+
return Promise.resolve(false);
|
|
69
|
+
}
|
|
70
|
+
this._logger.debug(`Subscribing to ${this._topicName}...`, identifier, actions, filter);
|
|
71
|
+
const consumer = this._client.consumer({ groupId: 'test-group' });
|
|
72
|
+
return consumer
|
|
73
|
+
.connect()
|
|
74
|
+
.then(() => consumer.subscribe({ topic: this._topicName, fromBeginning: false }))
|
|
75
|
+
.then(() => consumer.run({
|
|
76
|
+
autoCommit: false,
|
|
77
|
+
eachMessage: (payload) => this._onMessage(payload, consumer),
|
|
78
|
+
}))
|
|
79
|
+
.then(() => this._consumerMap.set(identifier, consumer))
|
|
80
|
+
.then(() => true)
|
|
81
|
+
.catch((err) => {
|
|
82
|
+
this._logger.error(err);
|
|
83
|
+
this._circuitBreaker.triggerFailure(err?.message);
|
|
84
|
+
return false;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
unsubscribe(identifier) {
|
|
88
|
+
const consumer = this._consumerMap.get(identifier);
|
|
89
|
+
if (!consumer) {
|
|
90
|
+
this._logger.error('Consumer not found', identifier);
|
|
91
|
+
return Promise.resolve(false);
|
|
92
|
+
}
|
|
93
|
+
return consumer
|
|
94
|
+
.disconnect()
|
|
95
|
+
.then(() => this._consumerMap.delete(identifier))
|
|
96
|
+
.catch((err) => {
|
|
97
|
+
this._logger.error(err);
|
|
98
|
+
return false;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async shutdown() {
|
|
102
|
+
for (const consumer of this._consumerMap.values()) {
|
|
103
|
+
await consumer.disconnect();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Private Methods
|
|
108
|
+
*/
|
|
109
|
+
/**
|
|
110
|
+
* Underlying Kafka message handler.
|
|
111
|
+
*
|
|
112
|
+
* @param message The kafka message to process
|
|
113
|
+
*/
|
|
114
|
+
async _onMessage({ topic, partition, message }, consumer) {
|
|
115
|
+
this._logger.debug(`Received message ${message.value?.toString()} on topic ${topic} partition ${partition}`);
|
|
116
|
+
try {
|
|
117
|
+
const messageValue = message.value;
|
|
118
|
+
if (messageValue) {
|
|
119
|
+
const parsed = plainToInstance((Message), messageValue.toString());
|
|
120
|
+
await this.handle(parsed, message.key?.toString());
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error instanceof RetryMessageError) {
|
|
125
|
+
this._logger.warn('Retrying message: ', error.message);
|
|
126
|
+
// Retryable error, usually ongoing call with station when trying to send new call
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this._logger.error('Error while processing message:', error, message);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
await consumer.commitOffsets([{ topic, partition, offset: message.offset }]);
|
|
134
|
+
}
|
|
135
|
+
_onCircuitBreakerStateChange(state, reason) {
|
|
136
|
+
this._logger.info(`[CircuitBreaker] State changed to ${state}${reason ? `: ${reason}` : ''}`);
|
|
137
|
+
switch (state) {
|
|
138
|
+
case 'FAILING':
|
|
139
|
+
this._logger.warn('Circuit breaker is FAILING. Kafka sender will not send messages until recovery. Reason:', reason);
|
|
140
|
+
break;
|
|
141
|
+
case 'CLOSED': {
|
|
142
|
+
this._logger.error('Circuit breaker is CLOSED. Shutting down Kafka receiver. Reason:', reason);
|
|
143
|
+
void this.shutdown();
|
|
144
|
+
if (this._reconnectInterval) {
|
|
145
|
+
clearInterval(this._reconnectInterval);
|
|
146
|
+
}
|
|
147
|
+
const delay = (this._config.maxReconnectDelay || 30) * 1000;
|
|
148
|
+
this._logger.warn(`Starting continuous reconnect attempts every ${delay / 1000} seconds while circuit breaker is CLOSED.`);
|
|
149
|
+
this._reconnectInterval = setInterval(() => {
|
|
150
|
+
this._logger.info('Attempting Kafka reconnect due to circuit breaker CLOSED...');
|
|
151
|
+
try {
|
|
152
|
+
this._initAdmin();
|
|
153
|
+
this._logger.info('Kafka reconnect attempt finished (success or already open).');
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
this._logger.error('Kafka reconnect attempt failed.', err);
|
|
157
|
+
}
|
|
158
|
+
}, delay);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case 'OPEN':
|
|
162
|
+
this._logger.info('Circuit breaker is OPEN. Will attempt to (re)initialize Kafka admin connection.');
|
|
163
|
+
if (this._reconnectInterval) {
|
|
164
|
+
this._logger.info('Clearing reconnect interval as circuit breaker is now OPEN.');
|
|
165
|
+
clearInterval(this._reconnectInterval);
|
|
166
|
+
this._reconnectInterval = undefined;
|
|
167
|
+
}
|
|
168
|
+
this._initAdmin();
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
this._logger.warn('Unknown circuit breaker state:', state);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
initConnection() {
|
|
176
|
+
return Promise.resolve(undefined);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=receiver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receiver.js","sourceRoot":"","sources":["../../../src/queue/kafka/receiver.ts"],"names":[],"mappings":"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"}
|