Remove forEach() usage + Fixed OCPI charging station with connectors that cannot charge in parallel
MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -122,7 +122,7 @@ export default class Authorizations {
// Get User's site
const sites = (await UserStorage.getUserSites(tenantID, { userID: user.id },
Constants.DB_PARAMS_MAX_LIMIT)).result;
- sites.forEach((siteUser) => {
+ for (const siteUser of sites) {
if (!Authorizations.isAdmin(user)) {
siteIDs.push(siteUser.site.id);
companyIDs.add(siteUser.site.companyID);
@@ -133,7 +133,7 @@ export default class Authorizations {
if (siteUser.siteOwner) {
siteOwnerIDs.push(siteUser.site.id);
}
- });
+ }
let tenantHashID = Constants.DEFAULT_TENANT;
let activeComponents = [];
let tenantName;

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -347,13 +347,11 @@ export default class AuthorizationsDefinition {
public getScopes(groups: ReadonlyArray<string>): ReadonlyArray<string> {
const scopes = [];
try {
- this.accessControl.allowedResources({ role: groups }).forEach(
- (resource: string): void => {
- this.accessControl.allowedActions({ role: groups, resource: resource }).forEach(
- (action: string): number => scopes.push(`${resource}:${action}`)
- );
+ for (const resource of this.accessControl.allowedResources({ role: groups }) as string[]) {
+ for (const action of this.accessControl.allowedActions({ role: groups, resource: resource }) as string[]) {
+ scopes.push(`${resource}:${action}`);
+ }
}
- );
} catch (error) {
throw new BackendError({
source: Constants.CENTRAL_SERVER,

MODIFY
src/client/websocket/WSClient.ts
src/client/websocket/WSClient.ts
@@ -176,11 +176,11 @@ export default class WSClient {
}
private reinstantiateCbs() {
- ['onopen', 'onerror', 'onclose', 'onmessage'].forEach((method) => {
+ for (const method of ['onopen', 'onerror', 'onclose', 'onmessage']) {
if ('' + this.callbacks[method] !== '' + (() => { })) {
this.ws[method] = this.callbacks[method];
}
- });
+ }
}
private onError(error) {
@@ -250,7 +250,7 @@ export default class WSClient {
* Add the `onopen`, `onerror`, `onclose`, `onmessage`, `onreconnect`
* and `onmaximum` attributes.
*/
-['onopen', 'onerror', 'onclose', 'onmessage'].forEach((method) => {
+for (const method of ['onopen', 'onerror', 'onclose', 'onmessage']) {
Object.defineProperty(WSClient.prototype, method, {
configurable: true,
enumerable: true,
@@ -263,8 +263,8 @@ export default class WSClient {
this.ws[method] = callback;
}
});
-});
-['onreconnect', 'onmaximum'].forEach((method) => {
+}
+for (const method of ['onreconnect', 'onmaximum']) {
Object.defineProperty(WSClient.prototype, method, {
configurable: true,
enumerable: true,
@@ -275,23 +275,16 @@ export default class WSClient {
this.callbacks[method] = callback;
}
});
-});
+}
/**
* Add some ws properties
*/
-[
- 'binaryType',
- 'bufferedAmount',
- 'extensions',
- 'protocol',
- 'readyState'
-].forEach((property) => {
+for (const property of ['binaryType', 'bufferedAmount', 'extensions', 'protocol', 'readyState']) {
Object.defineProperty(WSClient.prototype, property, {
enumerable: true,
get() {
return this.ws[property];
}
});
-});
-
+}

MODIFY
src/server/ocpi/OCPIServer.ts
src/server/ocpi/OCPIServer.ts
@@ -27,7 +27,7 @@ export default class OCPIServer {
this.expressApplication.use(EMSPService.PATH + AbstractOCPIService.VERSIONS_PATH,
(req: Request, res: Response, next: NextFunction) => ocpiServices.getEMSPVersions(req, res, next));
// Register all services in express
- ocpiServices.getOCPIServiceImplementations().forEach((ocpiService) => {
+ for (const ocpiService of ocpiServices.getOCPIServiceImplementations()) {
this.expressApplication.use(ocpiService.getPath(), async (req: TenantIdHoldingRequest, res: Response, next: NextFunction) => {
try {
await ocpiService.restService(req, res, next);
@@ -35,7 +35,7 @@ export default class OCPIServer {
next(error);
}
});
- });
+ }
// Post init
ExpressTools.postInitApplication(this.expressApplication);
}

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/CPOCommandsEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/CPOCommandsEndpoint.ts
@@ -104,12 +104,12 @@ export default class CPOCommandsEndpoint extends AbstractEndpoint {
}, Constants.DB_PARAMS_MAX_LIMIT);
if (chargingStations && chargingStations.result) {
for (const cs of chargingStations.result) {
- cs.connectors.forEach((conn) => {
+ for (const conn of cs.connectors) {
if (startSession.evse_uid === OCPIUtils.buildEvseUID(cs, conn)) {
chargingStation = cs;
connector = conn;
}
- });
+ }
}
}
if (!chargingStation) {

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/EMSPLocationsEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/EMSPLocationsEndpoint.ts
@@ -152,9 +152,9 @@ export default class EMSPLocationsEndpoint extends AbstractEndpoint {
}
chargingStation.ocpiData.evse.status = evse.status;
const status = OCPIMapping.convertOCPIStatus2Status(evse.status);
- chargingStation.connectors.forEach((connector) => {
+ for (const connector of chargingStation.connectors) {
connector.status = status;
- });
+ }
}
if (evse.last_updated) {
chargingStation.lastChangedOn = evse.last_updated;

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIMapping.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIMapping.ts
@@ -69,11 +69,11 @@ export default class OCPIMapping {
static convertEvseToChargingStation(evseId: string, evse: Partial<OCPIEvse>, location?: OCPILocation): ChargingStation {
const chargingStation = {
- id: evseId,
+ id: evse.evse_id,
maximumPower: 0,
issuer: false,
connectors: [],
- chargeBoxSerialNumber: evse.evse_id,
+ chargeBoxSerialNumber: evseId,
ocpiData: {
evse: evse
}
@@ -486,11 +486,21 @@ export default class OCPIMapping {
// Build evses array
const evses: OCPIEvse[] = [];
// Convert charging stations to evse(s)
- siteArea.chargingStations.forEach((chargingStation) => {
+ for (const chargingStation of siteArea.chargingStations) {
if (chargingStation.issuer === true && chargingStation.public) {
- evses.push(...OCPIMapping.convertChargingStation2MultipleEvses(tenant, chargingStation, options));
+ if (!Utils.isEmptyArray(chargingStation.chargePoints)) {
+ for (const chargePoint of chargingStation.chargePoints) {
+ if (chargePoint.cannotChargeInParallel) {
+ evses.push(...OCPIMapping.convertChargingStation2UniqueEvse(tenant, chargingStation, chargePoint, options));
+ } else {
+ evses.push(...OCPIMapping.convertChargingStation2MultipleEvses(tenant, chargingStation, chargePoint, options));
+ }
+ }
+ } else {
+ evses.push(...OCPIMapping.convertChargingStation2MultipleEvses(tenant, chargingStation, null, options));
+ }
+ }
}
- });
// Return evses
return evses;
}
@@ -527,9 +537,14 @@ export default class OCPIMapping {
* @param {*} chargingStation
* @return Array of OCPI EVSEs
*/
- private static convertChargingStation2MultipleEvses(tenant: Tenant, chargingStation: ChargingStation, options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): OCPIEvse[] {
+ private static convertChargingStation2MultipleEvses(tenant: Tenant, chargingStation: ChargingStation, chargePoint: ChargePoint, options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): OCPIEvse[] {
// Loop through connectors and send one evse per connector
- const connectors = chargingStation.connectors.filter((connector) => connector !== null);
+ let connectors: Connector[];
+ if (chargePoint) {
+ connectors = Utils.getConnectorsFromChargePoint(chargingStation, chargePoint);
+ } else {
+ connectors = chargingStation.connectors.filter((connector) => connector !== null);
+ }
const evses = connectors.map((connector) => {
const evseID = OCPIUtils.buildEvseID(options.countryID, options.partyID, chargingStation, connector);
const evse: OCPIEvse = {
@@ -561,18 +576,24 @@ export default class OCPIMapping {
* @param options
* @return OCPI EVSE
*/
- private static convertChargingStation2UniqueEvse(tenant: Tenant, chargingStation: ChargingStation, options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): OCPIEvse[] {
+ private static convertChargingStation2UniqueEvse(tenant: Tenant, chargingStation: ChargingStation, chargePoint: ChargePoint, options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): OCPIEvse[] {
const evseID = OCPIUtils.buildEvseID(options.countryID, options.partyID, chargingStation);
+ let connectors: Connector[];
+ if (chargePoint) {
+ connectors = Utils.getConnectorsFromChargePoint(chargingStation, chargePoint);
+ } else {
+ connectors = chargingStation.connectors.filter((connector) => connector !== null);
+ }
// Get all connectors
- const connectors = chargingStation.connectors.map(
+ const ocpiConnectors = connectors.map(
(connector: Connector) => OCPIMapping.convertConnector2OCPIConnector(tenant, chargingStation, connector, evseID));
// Build evse
const evse: OCPIEvse = {
uid: OCPIUtils.buildEvseUID(chargingStation),
evse_id: evseID,
- status: OCPIMapping.convertStatus2OCPIStatus(OCPIMapping.aggregateConnectorsStatus(chargingStation.connectors)),
+ status: OCPIMapping.convertStatus2OCPIStatus(OCPIMapping.aggregateConnectorsStatus(connectors)),
capabilities: [OCPICapability.REMOTE_START_STOP_CAPABLE, OCPICapability.RFID_READER],
- connectors: connectors,
+ connectors: ocpiConnectors,
last_updated: chargingStation.lastSeen,
coordinates: {
latitude: chargingStation.coordinates[1] ? chargingStation.coordinates[1].toString() : null,

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPISessionsService.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPISessionsService.ts
@@ -80,11 +80,11 @@ export default class OCPISessionsService {
let connectorId = 1;
if (evse.connectors && evse.connectors.length === 1) {
const evseConnectorId = evse.connectors[0].id;
- chargingStation.connectors.forEach((connector) => {
+ for (const connector of chargingStation.connectors) {
if (evseConnectorId === connector.id) {
connectorId = connector.connectorId;
}
- });
+ }
}
transaction = {
issuer: false,

MODIFY
src/server/ocpp/json/WSServer.ts
src/server/ocpp/json/WSServer.ts
@@ -61,11 +61,11 @@ export default class WSServer extends WebSocket.Server {
}
public broadcastToClients(message: any): void {
- this.clients.forEach((client) => {
+ for (const client of this.clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
- });
+ }
}
public start(): void {

MODIFY
src/server/rest/v1/service/RegistrationTokenService.ts
src/server/rest/v1/service/RegistrationTokenService.ts
@@ -231,12 +231,11 @@ export default class RegistrationTokenService {
]
);
// Build OCPP URLs
- registrationTokens.result.forEach((registrationToken) => {
+ for (const registrationToken of registrationTokens.result) {
registrationToken.ocpp15SOAPUrl = Utils.buildOCPPServerURL(req.user.tenantID, OCPPVersion.VERSION_15, OCPPProtocol.SOAP, registrationToken.id);
registrationToken.ocpp16SOAPUrl = Utils.buildOCPPServerURL(req.user.tenantID, OCPPVersion.VERSION_16, OCPPProtocol.SOAP, registrationToken.id);
registrationToken.ocpp16JSONUrl = Utils.buildOCPPServerURL(req.user.tenantID, OCPPVersion.VERSION_16, OCPPProtocol.JSON, registrationToken.id);
- return registrationToken;
- });
+ }
// Ok
res.json(registrationTokens);
next();

MODIFY
src/server/rest/v1/service/SettingService.ts
src/server/rest/v1/service/SettingService.ts
@@ -94,10 +94,10 @@ export default class SettingService {
{ identifier: filteredRequest.Identifier },
{ limit: filteredRequest.Limit, skip: filteredRequest.Skip, sort: filteredRequest.Sort });
// Process the sensitive data if any
- settings.result.forEach((setting) => {
+ for (const setting of settings.result) {
// Hash sensitive data before being sent to the front end
Cypher.hashSensitiveDataInJSON(setting);
- });
+ }
// Return
res.json(settings);
next();

MODIFY
src/storage/mongodb/AssetStorage.ts
src/storage/mongodb/AssetStorage.ts
@@ -245,10 +245,10 @@ export default class AssetStorage {
if (!Utils.isEmptyArray(params.errorType)) {
// Build facet only for one error type
const array = [];
- params.errorType.forEach((type) => {
+ for (const type of params.errorType) {
array.push(`$${type}`);
facets.$facet[type] = AssetStorage.getAssetInErrorFacet(type);
- });
+ }
aggregation.push(facets);
// Manipulate the results to convert it to an array of document on root level
aggregation.push({ $project: { assetsInError: { $setUnion: array } } });

MODIFY
src/storage/mongodb/ChargingStationStorage.ts
src/storage/mongodb/ChargingStationStorage.ts
@@ -431,10 +431,10 @@ export default class ChargingStationStorage {
}
// Build facet only for one error type
const array = [];
- params.errorType.forEach((type) => {
+ for (const type of params.errorType) {
array.push(`$${type}`);
facets.$facet[type] = ChargingStationStorage.getChargerInErrorFacet(type);
- });
+ }
aggregation.push(facets);
// Manipulate the results to convert it to an array of document on root level
aggregation.push({ $project: { chargersInError: { $setUnion: array } } });

MODIFY
src/storage/mongodb/TransactionStorage.ts
src/storage/mongodb/TransactionStorage.ts
@@ -840,10 +840,10 @@ export default class TransactionStorage {
if (!Utils.isEmptyArray(params.errorType)) {
const facets: any = { $facet: {} };
const array = [];
- params.errorType.forEach((type) => {
+ for (const type of params.errorType) {
array.push(`$${type}`);
facets.$facet[type] = this.getTransactionsInErrorFacet(type);
- });
+ }
aggregation.push(facets);
// Manipulate the results to convert it to an array of document on root level
aggregation.push({ $project: { 'allItems': { $setUnion: array } } });

MODIFY
src/utils/Cypher.ts
src/utils/Cypher.ts
@@ -67,7 +67,7 @@ export default class Cypher {
message: 'The property \'sensitiveData\' is not an array'
});
}
- obj.sensitiveData.forEach((property: string) => {
+ for (const property of obj.sensitiveData as string[]) {
// Check that the property does exist otherwise skip to the next property
if (_.has(obj, property)) {
const value = _.get(obj, property);
@@ -76,7 +76,7 @@ export default class Cypher {
_.set(obj, property, Cypher.encrypt(value));
}
}
- });
+ }
} else {
obj.sensitiveData = [];
}
@@ -101,7 +101,7 @@ export default class Cypher {
message: 'The property \'sensitiveData\' is not an array'
});
}
- obj.sensitiveData.forEach((property: string) => {
+ for (const property of obj.sensitiveData as string[]) {
// Check that the property does exist otherwise skip to the next property
if (_.has(obj, property)) {
const value = _.get(obj, property);
@@ -110,7 +110,7 @@ export default class Cypher {
_.set(obj, property, Cypher.decrypt(value));
}
}
- });
+ }
}
}
@@ -133,7 +133,7 @@ export default class Cypher {
message: 'The property \'sensitiveData\' is not an array'
});
}
- obj.sensitiveData.forEach((property: string) => {
+ for (const property of obj.sensitiveData as string[]) {
// Check that the property does exist otherwise skip to the next property
if (_.has(obj, property)) {
const value = _.get(obj, property);
@@ -142,7 +142,7 @@ export default class Cypher {
_.set(obj, property, Cypher.hash(value));
}
}
- });
+ }
}
}
}

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -34,6 +34,20 @@ import validator from 'validator';
const MODULE_NAME = 'Utils';
export default class Utils {
+ public static getConnectorsFromChargePoint(chargingStation: ChargingStation, chargePoint: ChargePoint): Connector[] {
+ const connectors: Connector[] = [];
+ if (!chargingStation || !chargePoint || Utils.isEmptyArray(chargePoint.connectorIDs)) {
+ return connectors;
+ }
+ for (const connectorID of chargePoint.connectorIDs) {
+ const connector = Utils.getConnectorFromID(chargingStation, connectorID);
+ if (connector) {
+ connectors.push(connector);
+ }
+ }
+ return connectors;
+ }
+
public static handleAxiosError(axiosError: AxiosError, urlRequest: string, action: ServerAction, module: string, method: string): void {
// Handle Error outside 2xx range
if (axiosError.response) {

Remove forEach() usage + Fixed OCPI charging station with connectors that cannot charge in parallel