]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp): integrate unified auth as sole authorization mechanism (#1764)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 30 Mar 2026 20:02:25 +0000 (22:02 +0200)
committerGitHub <noreply@github.com>
Mon, 30 Mar 2026 20:02:25 +0000 (22:02 +0200)
* refactor(ocpp): route v1.6 auth through unified system, remove legacy functions

* refactor(ocpp): replace OCPP 2.0 inline auth with unified auth service

* test(ocpp): update OCPP 1.6 authorization tests for unified auth path

* test(ocpp): add OCPP 2.0 unified auth integration tests

* chore: pass all quality gates

* [autofix.ci] apply automated fixes

* refactor(ocpp): pass correct AuthContext to isIdTagAuthorized callers

REMOTE_START for RemoteStartTransaction, RESERVATION for ReserveNow.
ATG keeps the default TRANSACTION_START.

* refactor(ocpp): use auth barrel index instead of deep imports

* refactor(ocpp): use auth barrel for all external imports

* refactor(ocpp): remove redundant 'unified' naming from auth system

* refactor(ocpp): extract authorizeToken helper and fix rejection test assertions

* test(ocpp): add coverage for auth error and context parameter paths

* test(ocpp): remove redundant tests and unnecessary JSDoc from authorization tests

* refactor(ocpp): address review feedback — explicit switch cases, parameterize context, merge imports

* fix(ocpp): preserve OCPP 2.0 token type in authorizeToken per C01.FR.25

authorizeToken was hardcoding IdentifierType.ID_TAG, causing all
Authorize.req to carry type 'Local' regardless of the actual token
type (ISO14443, eMAID, etc.). Pass the OCPP 2.0 token type through
and use mapOCPP20TokenType for proper mapping.

Also fix log message attribution and deduplicate OCPPAuthServiceFactory
from the dynamic import (already available via static import).

* refactor(ocpp): eliminate unnecessary dynamic imports and synchronize auth factory

Extract isIdTagAuthorized to IdTagAuthorization.ts to break the
OCPPServiceUtils ESM evaluation cycle, enabling static imports from
the auth barrel. Convert AuthComponentFactory, OCPPAuthServiceFactory,
and OCPPAuthServiceImpl to fully synchronous APIs. Remove the
globalThis Symbol hack that was needed for cross-module-instance
sharing. Move getMessageTypeString to utils/Utils.ts where it belongs.

- 9 dynamic imports eliminated (20 → 11, remaining are inheritance cycles)
- Auth factory chain fully sync (getInstance, createAdapter, initialize)
- IdTagAuthorization.ts imports auth/index.js barrel statically
- Tests relocated to follow moved code
- All barrel exports consolidated between components

* build: add skott for type-aware circular dependency detection

Add skott as devDependency with a circular-deps script that scans
the entire src/ directory using --no-trackTypeOnlyDependencies to
ignore import type (unlike madge which reports false positives).
Exit code 0 for now since known cycles exist — switch to 1 when
integrated in CI.

* build: override effect>=3.20.0 to fix CVE-2026-32887 (skott transitive dep)

* refactor: break UI server circular dependency via IBootstrap DIP

Introduce IBootstrap interface to decouple AbstractUIService from the
Bootstrap singleton import. Bootstrap implements IBootstrap and is
injected through the UIServerFactory → AbstractUIServer → UIService
chain. This structurally eliminates the cycle:
AbstractUIServer → UIServiceFactory → AbstractUIService → Bootstrap →
UIServerFactory → UIHttpServer → extends AbstractUIServer.

Also removes the cross-component re-export workaround in
charging-station/index.ts (getMessageTypeString from utils/) that
was only needed to preserve ESM evaluation order around this cycle.

- Circular dependency paths: 177 → 39 (type-aware, skott)
- Short cycles (depth ≤ 4): 7 → 4
- ErrorUtils now imports getMessageTypeString from its own component

* test: fix mock isolation and type safety in UI server tests

Move mockBootstrap to beforeEach for per-test isolation.
Remove as-never cast by providing all SimulatorState fields.
Remove unnecessary JSDoc on self-documenting mock factory.

* fix: align auth barrel section headers with exports, use barrel for CertificateAuthStrategy import

* refactor(auth): eliminate code duplication, dead code, and type casts

- DRY: Replace duplicated mappers in OCPP20AuthAdapter with shared
  mapToOCPP20TokenType/mapOCPP20TokenType from AuthTypes
- DRY: Extract duplicated enhanceResult from Local/Remote strategies
  into shared enhanceAuthResult in AuthTypes
- Dead code: Remove unused parseIntegerVariable from OCPP20AuthAdapter
- YAGNI: Remove unused AuthHelpers from barrel export
- Type safety: Add getAuthCache() to AuthStrategy interface, eliminating
  3 concrete as-LocalAuthStrategy casts in OCPPAuthServiceImpl

* fix(auth): preserve OCPP 2.0 Central/Local token type in adapter mapping

Remove post-processing that collapsed Central/Local to ID_TAG in
OCPP20AuthAdapter.convertToIdentifier. The shared mapOCPP20TokenType
correctly returns CENTRAL/LOCAL, which downstream code uses for spec
compliance (C03.FR.01 skip Authorize.req for Central tokens, C11
cache exclusion for Central identifiers).

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
70 files changed:
package.json
pnpm-lock.yaml
pnpm-workspace.yaml
src/charging-station/Bootstrap.ts
src/charging-station/ChargingStation.ts
src/charging-station/Helpers.ts
src/charging-station/IBootstrap.ts [new file with mode: 0644]
src/charging-station/index.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20ResponseService.ts
src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts
src/charging-station/ocpp/IdTagAuthorization.ts [new file with mode: 0644]
src/charging-station/ocpp/OCPPRequestService.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.ts
src/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.ts
src/charging-station/ocpp/auth/factories/AuthComponentFactory.ts
src/charging-station/ocpp/auth/index.ts
src/charging-station/ocpp/auth/interfaces/OCPPAuthService.ts
src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.ts
src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.ts
src/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.ts
src/charging-station/ocpp/auth/strategies/LocalAuthStrategy.ts
src/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.ts
src/charging-station/ocpp/auth/types/AuthTypes.ts
src/charging-station/ocpp/auth/utils/AuthHelpers.ts
src/charging-station/ocpp/auth/utils/AuthValidators.ts
src/charging-station/ocpp/auth/utils/index.ts
src/charging-station/ocpp/index.ts
src/charging-station/ui-server/AbstractUIServer.ts
src/charging-station/ui-server/UIHttpServer.ts
src/charging-station/ui-server/UIMCPServer.ts
src/charging-station/ui-server/UIServerFactory.ts
src/charging-station/ui-server/UIWebSocketServer.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/utils/ErrorUtils.ts
src/utils/Utils.ts
src/utils/index.ts
tests/TEST_STYLE_GUIDE.md
tests/charging-station/Helpers.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-ClearCache.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GroupIdStop.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-MasterPass.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStopTransaction.test.ts
tests/charging-station/ocpp/2.0/OCPP20Integration-Certificate.test.ts
tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts
tests/charging-station/ocpp/2.0/OCPP20ResponseService-CacheUpdate.test.ts
tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts
tests/charging-station/ocpp/IdTagAuthorization.test.ts [new file with mode: 0644]
tests/charging-station/ocpp/OCPPServiceUtils-authorization.test.ts [deleted file]
tests/charging-station/ocpp/auth/OCPPAuthIntegration.test.ts
tests/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.test.ts
tests/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.test.ts
tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts
tests/charging-station/ocpp/auth/helpers/MockFactories.ts
tests/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.test.ts
tests/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.test.ts
tests/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.test.ts
tests/charging-station/ocpp/auth/types/AuthTypes.test.ts
tests/charging-station/ocpp/auth/utils/AuthHelpers.test.ts
tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts
tests/charging-station/ui-server/UIHttpServer.test.ts
tests/charging-station/ui-server/UIMCPServer.integration.test.ts
tests/charging-station/ui-server/UIMCPServer.test.ts
tests/charging-station/ui-server/UIServerFactory.test.ts
tests/charging-station/ui-server/UIServerTestUtils.ts
tests/helpers/OCPPAuthIntegrationTest.ts
tests/utils/Utils.test.ts

index 9c9806166b2f3b662706c0228a4c207ee8f4aa90..01642ed07ea000e4397b2661e3d36323ce42613a 100644 (file)
@@ -65,6 +65,7 @@
     "clean:dist": "pnpm exec rimraf dist",
     "clean:node_modules": "pnpm exec rimraf node_modules",
     "typecheck": "tsc --noEmit --skipLibCheck",
+    "circular-deps": "skott --no-trackTypeOnlyDependencies --showCircularDependencies --exitCodeOnCircularDependencies 0 --displayMode=raw --fileExtensions=.ts --cwd=src",
     "lint": "cross-env TIMING=1 eslint --cache src tests scripts ./*.js ./*.ts",
     "lint:fix": "cross-env TIMING=1 eslint --cache --fix src tests scripts ./*.js ./*.ts",
     "format": "prettier --cache --write .; eslint --cache --fix src tests scripts ./*.js ./*.ts",
     "prettier": "^3.8.1",
     "rimraf": "^6.1.3",
     "semver": "^7.7.4",
+    "skott": "^0.35.8",
     "ts-node": "^10.9.2",
     "tsx": "^4.21.0",
     "typescript": "~6.0.2",
index 4bebe160f917809834aaab12d8651bc37a232f99..c0a357c0e24acc64a867558a30c61db96bc0c321 100644 (file)
@@ -8,10 +8,12 @@ overrides:
   bn.js@<5.2.3: '>=5.2.3'
   brace-expansion@<1.1.13: '>=1.1.13'
   d3-color@<3.1.0: '>=3.1.0'
+  effect@<3.20.0: '>=3.20.0'
   form-data@<2.5.4: '>=2.5.4'
   got@<11.8.5: '>=11.8.5'
   minimatch@>=10.0.0 <10.2.3: '>=10.2.3'
   minimatch@>=9.0.0 <9.0.7: '>=9.0.7'
+  path-to-regexp@>=8.0.0 <8.4.0: '>=8.4.0'
   picomatch@<2.3.2: '>=2.3.2'
   qs@<6.14.1: '>=6.14.1'
   smol-toml@<1.6.1: '>=1.6.1'
@@ -154,6 +156,9 @@ importers:
       semver:
         specifier: ^7.7.4
         version: 7.7.4
+      skott:
+        specifier: ^0.35.8
+        version: 0.35.8
       ts-node:
         specifier: ^10.9.2
         version: 10.9.2(@types/node@24.12.0)(typescript@6.0.2)
@@ -248,6 +253,10 @@ packages:
     engines: {node: '>=8.5.0'}
     hasBin: true
 
+  '@arr/every@1.0.1':
+    resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==}
+    engines: {node: '>=4'}
+
   '@asamuzakjp/css-color@5.0.1':
     resolution: {integrity: sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==}
     engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
@@ -1136,6 +1145,94 @@ packages:
   '@oxc-project/types@0.122.0':
     resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==}
 
+  '@parcel/watcher-android-arm64@2.5.4':
+    resolution: {integrity: sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [android]
+
+  '@parcel/watcher-darwin-arm64@2.5.4':
+    resolution: {integrity: sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@parcel/watcher-darwin-x64@2.5.4':
+    resolution: {integrity: sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@parcel/watcher-freebsd-x64@2.5.4':
+    resolution: {integrity: sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@parcel/watcher-linux-arm-glibc@2.5.4':
+    resolution: {integrity: sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm]
+    os: [linux]
+    libc: [glibc]
+
+  '@parcel/watcher-linux-arm-musl@2.5.4':
+    resolution: {integrity: sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm]
+    os: [linux]
+    libc: [musl]
+
+  '@parcel/watcher-linux-arm64-glibc@2.5.4':
+    resolution: {integrity: sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@parcel/watcher-linux-arm64-musl@2.5.4':
+    resolution: {integrity: sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@parcel/watcher-linux-x64-glibc@2.5.4':
+    resolution: {integrity: sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@parcel/watcher-linux-x64-musl@2.5.4':
+    resolution: {integrity: sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@parcel/watcher-win32-arm64@2.5.4':
+    resolution: {integrity: sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@parcel/watcher-win32-ia32@2.5.4':
+    resolution: {integrity: sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@parcel/watcher-win32-x64@2.5.4':
+    resolution: {integrity: sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [win32]
+
+  '@parcel/watcher@2.5.4':
+    resolution: {integrity: sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==}
+    engines: {node: '>= 10.0.0'}
+
   '@pkgjs/parseargs@0.11.0':
     resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
     engines: {node: '>=14'}
@@ -1144,6 +1241,12 @@ packages:
     resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
     engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
 
+  '@polka/url@0.5.0':
+    resolution: {integrity: sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==}
+
+  '@polka/url@1.0.0-next.29':
+    resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+
   '@rolldown/binding-android-arm64@1.0.0-rc.12':
     resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==}
     engines: {node: ^20.19.0 || >=22.12.0}
@@ -1334,12 +1437,18 @@ packages:
   '@types/long@4.0.2':
     resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==}
 
+  '@types/minimatch@3.0.5':
+    resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==}
+
   '@types/node@24.12.0':
     resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==}
 
   '@types/offscreencanvas@2019.3.0':
     resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==}
 
+  '@types/parse-json@4.0.2':
+    resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
+
   '@types/seedrandom@2.4.34':
     resolution: {integrity: sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==}
 
@@ -1379,6 +1488,12 @@ packages:
       eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
       typescript: '>=4.8.4 <6.0.0'
 
+  '@typescript-eslint/project-service@8.53.0':
+    resolution: {integrity: sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      typescript: '>=4.8.4 <6.0.0'
+
   '@typescript-eslint/project-service@8.57.1':
     resolution: {integrity: sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1389,6 +1504,12 @@ packages:
     resolution: {integrity: sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@typescript-eslint/tsconfig-utils@8.53.0':
+    resolution: {integrity: sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      typescript: '>=4.8.4 <6.0.0'
+
   '@typescript-eslint/tsconfig-utils@8.57.1':
     resolution: {integrity: sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1402,10 +1523,20 @@ packages:
       eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
       typescript: '>=4.8.4 <6.0.0'
 
+  '@typescript-eslint/types@8.53.0':
+    resolution: {integrity: sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@typescript-eslint/types@8.57.1':
     resolution: {integrity: sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@typescript-eslint/typescript-estree@8.53.0':
+    resolution: {integrity: sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      typescript: '>=4.8.4 <6.0.0'
+
   '@typescript-eslint/typescript-estree@8.57.1':
     resolution: {integrity: sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1419,6 +1550,10 @@ packages:
       eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
       typescript: '>=4.8.4 <6.0.0'
 
+  '@typescript-eslint/visitor-keys@8.53.0':
+    resolution: {integrity: sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@typescript-eslint/visitor-keys@8.57.1':
     resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1699,6 +1834,9 @@ packages:
   arg@4.1.3:
     resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
 
+  argparse@1.0.10:
+    resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
   argparse@2.0.1:
     resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
 
@@ -1706,6 +1844,10 @@ packages:
     resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
     engines: {node: '>= 0.4'}
 
+  array-differ@3.0.0:
+    resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==}
+    engines: {node: '>=8'}
+
   array-flatten@3.0.0:
     resolution: {integrity: sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==}
 
@@ -1746,6 +1888,10 @@ packages:
     resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
     engines: {node: '>= 0.4'}
 
+  arrify@2.0.1:
+    resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==}
+    engines: {node: '>=8'}
+
   asn1.js@4.10.1:
     resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==}
 
@@ -1817,6 +1963,9 @@ packages:
       react-native-b4a:
         optional: true
 
+  balanced-match@1.0.2:
+    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
   balanced-match@4.0.4:
     resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
     engines: {node: 18 || 20 || >=22}
@@ -1872,6 +2021,9 @@ packages:
     resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==}
     engines: {node: '>=10'}
 
+  brace-expansion@2.0.3:
+    resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==}
+
   brace-expansion@5.0.5:
     resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
     engines: {node: 18 || 20 || >=22}
@@ -1985,6 +2137,9 @@ packages:
     resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
     engines: {node: '>= 0.4'}
 
+  callsite@1.0.0:
+    resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==}
+
   callsites@3.1.0:
     resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
     engines: {node: '>=6'}
@@ -2106,6 +2261,9 @@ packages:
   cliui@5.0.0:
     resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==}
 
+  cliui@7.0.4:
+    resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+
   cliui@8.0.1:
     resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
     engines: {node: '>=12'}
@@ -2171,6 +2329,10 @@ packages:
     resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
     engines: {node: '>=14'}
 
+  commander@11.1.0:
+    resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
+    engines: {node: '>=16'}
+
   commander@14.0.3:
     resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
     engines: {node: '>=20'}
@@ -2192,6 +2354,14 @@ packages:
   compare-func@2.0.0:
     resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==}
 
+  compressible@2.0.18:
+    resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
+    engines: {node: '>= 0.6'}
+
+  compression@1.8.1:
+    resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==}
+    engines: {node: '>= 0.8.0'}
+
   concat-stream@1.6.2:
     resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
     engines: {'0': node >= 0.8}
@@ -2282,6 +2452,10 @@ packages:
       cosmiconfig: '>=9'
       typescript: '>=5'
 
+  cosmiconfig@7.1.0:
+    resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
+    engines: {node: '>=10'}
+
   cosmiconfig@9.0.1:
     resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==}
     engines: {node: '>=14'}
@@ -2480,6 +2654,14 @@ packages:
   debounce@1.2.1:
     resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
 
+  debug@2.6.9:
+    resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
   debug@4.3.4:
     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
     engines: {node: '>=6.0'}
@@ -2546,10 +2728,18 @@ packages:
     resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
     engines: {node: '>=0.10'}
 
+  depcheck@1.4.7:
+    resolution: {integrity: sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==}
+    engines: {node: '>=10'}
+    hasBin: true
+
   depd@2.0.0:
     resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
     engines: {node: '>= 0.8'}
 
+  deps-regex@0.2.0:
+    resolution: {integrity: sha512-PwuBojGMQAYbWkMXOY9Pd/NWCDNHVH12pnS7WHqZkTSeMESe4hwnKKRp0yR87g37113x4JPbo/oIvXY+s/f56Q==}
+
   deps-sort@2.0.1:
     resolution: {integrity: sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==}
     hasBin: true
@@ -2557,6 +2747,10 @@ packages:
   des.js@1.1.0:
     resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==}
 
+  detect-file@1.0.0:
+    resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==}
+    engines: {node: '>=0.10.0'}
+
   detect-libc@2.1.2:
     resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
     engines: {node: '>=8'}
@@ -2573,6 +2767,10 @@ packages:
   diffie-hellman@5.0.3:
     resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==}
 
+  digraph-js@2.2.4:
+    resolution: {integrity: sha512-K42PmnGfSO73gOUDwq/kM7DLZaCQJpEKImEuz/aiSbmzEwoEUWSfkDNGfYcIFVZuNgn3tCkyEvzXJOoluP3bSQ==}
+    engines: {node: '>=16.0.0'}
+
   dir-glob@3.0.1:
     resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
     engines: {node: '>=8'}
@@ -2627,6 +2825,9 @@ packages:
   ee-first@1.1.1:
     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
 
+  effect@3.21.0:
+    resolution: {integrity: sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==}
+
   electron-to-chromium@1.5.321:
     resolution: {integrity: sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==}
 
@@ -2964,6 +3165,10 @@ packages:
     resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
     engines: {node: '>=6'}
 
+  expand-tilde@2.0.2:
+    resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==}
+    engines: {node: '>=0.10.0'}
+
   expect-type@1.3.0:
     resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
     engines: {node: '>=12.0.0'}
@@ -2995,6 +3200,10 @@ packages:
     resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==}
     engines: {'0': node >=0.6.0}
 
+  fast-check@3.23.2:
+    resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==}
+    engines: {node: '>=8.0.0'}
+
   fast-deep-equal@3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
 
@@ -3072,6 +3281,10 @@ packages:
     resolution: {integrity: sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==}
     engines: {node: '>=20'}
 
+  findup-sync@5.0.0:
+    resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==}
+    engines: {node: '>= 10.13.0'}
+
   flame-gradient@1.0.0:
     resolution: {integrity: sha512-9ejk16/DqvQJ4dHsh68W/4N0zmVQ60zukyUuEHrTbf5pJvP4JqlIdke86Z9174PZokRCXAntY5+H1txSyC7mUA==}
 
@@ -3111,6 +3324,9 @@ packages:
     resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
     engines: {node: '>= 0.6'}
 
+  fp-ts@2.5.0:
+    resolution: {integrity: sha512-xkC9ZKl/i2cU+8FAsdyLcTvPRXphp42FcK5WmZpB47VXb4gggC3DHlVDKNLdbC+U8zz6yp1b0bj0mZg0axmZYQ==}
+
   fresh@2.0.0:
     resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
     engines: {node: '>= 0.8'}
@@ -3136,6 +3352,9 @@ packages:
     resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
     engines: {node: '>=14.14'}
 
+  fs-tree-structure@0.0.5:
+    resolution: {integrity: sha512-827ACYnAMC1DQRvhLUzZH0fCPhBJLo9P7WfxxwP4cibIzlrSzbD+Fh9W4FxFtSU+p9GlX0BoQUWLJ2LFJuoKuQ==}
+
   fs.realpath@1.0.0:
     resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
 
@@ -3257,6 +3476,14 @@ packages:
     resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
     engines: {node: '>=10'}
 
+  global-modules@1.0.0:
+    resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==}
+    engines: {node: '>=0.10.0'}
+
+  global-prefix@1.0.2:
+    resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==}
+    engines: {node: '>=0.10.0'}
+
   globals@14.0.0:
     resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
     engines: {node: '>=18'}
@@ -3375,6 +3602,10 @@ packages:
   hmac-drbg@1.0.1:
     resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==}
 
+  homedir-polyfill@1.0.3:
+    resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
+    engines: {node: '>=0.10.0'}
+
   hono@4.12.8:
     resolution: {integrity: sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==}
     engines: {node: '>=16.9.0'}
@@ -3456,6 +3687,10 @@ packages:
   ieee754@1.2.1:
     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
 
+  ignore-walk@6.0.5:
+    resolution: {integrity: sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==}
+    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
   ignore@5.3.2:
     resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
     engines: {node: '>= 4'}
@@ -3534,6 +3769,11 @@ packages:
     resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==}
     engines: {node: '>= 0.10'}
 
+  io-ts@2.2.22:
+    resolution: {integrity: sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==}
+    peerDependencies:
+      fp-ts: ^2.5.0
+
   iota-array@1.0.0:
     resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==}
 
@@ -3610,6 +3850,11 @@ packages:
     engines: {node: '>=8'}
     hasBin: true
 
+  is-docker@3.0.0:
+    resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    hasBin: true
+
   is-extglob@2.1.1:
     resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
     engines: {node: '>=0.10.0'}
@@ -3642,6 +3887,11 @@ packages:
     resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
     engines: {node: '>=0.10.0'}
 
+  is-inside-container@1.0.0:
+    resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
+    engines: {node: '>=14.16'}
+    hasBin: true
+
   is-installed-globally@0.4.0:
     resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==}
     engines: {node: '>=10'}
@@ -3750,6 +4000,10 @@ packages:
     resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
     engines: {node: '>= 0.4'}
 
+  is-windows@1.0.2:
+    resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
+    engines: {node: '>=0.10.0'}
+
   is-wsl@1.1.0:
     resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==}
     engines: {node: '>=4'}
@@ -3758,6 +4012,10 @@ packages:
     resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
     engines: {node: '>=8'}
 
+  is-wsl@3.1.1:
+    resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
+    engines: {node: '>=16'}
+
   is-yarn-global@0.3.0:
     resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==}
 
@@ -3814,6 +4072,10 @@ packages:
   js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
 
+  js-yaml@3.14.2:
+    resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
+    hasBin: true
+
   js-yaml@4.1.1:
     resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
     hasBin: true
@@ -3897,6 +4159,10 @@ packages:
   keyv@5.6.0:
     resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==}
 
+  kleur@4.1.5:
+    resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+    engines: {node: '>=6'}
+
   knex@3.1.0:
     resolution: {integrity: sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==}
     engines: {node: '>=16'}
@@ -4049,6 +4315,9 @@ packages:
     resolution: {integrity: sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==}
     engines: {node: '>=20'}
 
+  lodash-es@4.17.23:
+    resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==}
+
   lodash.camelcase@4.3.0:
     resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
 
@@ -4082,6 +4351,9 @@ packages:
   lodash.startcase@4.4.0:
     resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
 
+  lodash.uniqwith@4.5.0:
+    resolution: {integrity: sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==}
+
   lodash.upperfirst@4.3.1:
     resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==}
 
@@ -4162,6 +4434,10 @@ packages:
     resolution: {integrity: sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==}
     engines: {node: '>= 14'}
 
+  matchit@1.1.0:
+    resolution: {integrity: sha512-+nGYoOlfHmxe5BW5tE0EMJppXEwdSf8uBA1GTZC7Q77kbT35+VKLYJMzVNWCHSsga1ps1tPYFtFyvxvKzWVmMA==}
+    engines: {node: '>=6'}
+
   math-intrinsics@1.1.0:
     resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
     engines: {node: '>= 0.4'}
@@ -4197,6 +4473,10 @@ packages:
     resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
     engines: {node: '>= 8'}
 
+  meriyah@4.5.0:
+    resolution: {integrity: sha512-Rbiu0QPIxTXgOXwiIpRVJfZRQ2FWyfzYrOGBs9SN5RbaXg1CN5ELn/plodwWwluX93yzc4qO/bNIen1ThGFCxw==}
+    engines: {node: '>=10.4.0'}
+
   micromatch@4.0.8:
     resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
     engines: {node: '>=8.6'}
@@ -4266,6 +4546,10 @@ packages:
   minimatch@3.1.5:
     resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
 
+  minimatch@7.4.9:
+    resolution: {integrity: sha512-Brg/fp/iAVDOQoHxkuN5bEYhyQlZhxddI78yWsCbeEwTHXQjlNLtiJDUsp1GIptVqMI7/gkJMz4vVAc01mpoBw==}
+    engines: {node: '>=10'}
+
   minimist@1.2.8:
     resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
 
@@ -4360,6 +4644,13 @@ packages:
   morphdom@2.7.8:
     resolution: {integrity: sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg==}
 
+  mrmime@2.0.1:
+    resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+    engines: {node: '>=10'}
+
+  ms@2.0.0:
+    resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+
   ms@2.1.2:
     resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
 
@@ -4369,6 +4660,10 @@ packages:
   muggle-string@0.4.1:
     resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
 
+  multimatch@5.0.0:
+    resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==}
+    engines: {node: '>=10'}
+
   multistream@2.1.1:
     resolution: {integrity: sha512-xasv76hl6nr1dEy3lPvy7Ej7K/Lx3O/FCvwge8PeVJpciPPoNCbaANcNiBug3IpdvTveZUcAV0DJzdnUDMesNQ==}
 
@@ -4393,6 +4688,9 @@ packages:
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
+  nanospinner@1.2.2:
+    resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==}
+
   napi-build-utils@2.0.0:
     resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
 
@@ -4430,6 +4728,10 @@ packages:
   ndarray@1.0.19:
     resolution: {integrity: sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==}
 
+  negotiator@0.6.4:
+    resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
+    engines: {node: '>= 0.6'}
+
   negotiator@1.0.0:
     resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
     engines: {node: '>= 0.6'}
@@ -4451,6 +4753,9 @@ packages:
     resolution: {integrity: sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==}
     engines: {node: '>=10'}
 
+  node-addon-api@7.1.1:
+    resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
   node-exports-info@1.6.0:
     resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==}
     engines: {node: '>= 0.4'}
@@ -4547,6 +4852,10 @@ packages:
     resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
     engines: {node: '>= 0.8'}
 
+  on-headers@1.1.0:
+    resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==}
+    engines: {node: '>= 0.8'}
+
   on-net-listen@1.1.2:
     resolution: {integrity: sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==}
     engines: {node: '>=9.4.0 || ^8.9.4'}
@@ -4665,6 +4974,10 @@ packages:
     resolution: {integrity: sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==}
     engines: {node: '>= 0.10'}
 
+  parse-gitignore@2.0.0:
+    resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==}
+    engines: {node: '>=14'}
+
   parse-imports-exports@0.2.4:
     resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==}
 
@@ -4672,6 +4985,10 @@ packages:
     resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
     engines: {node: '>=8'}
 
+  parse-passwd@1.0.0:
+    resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==}
+    engines: {node: '>=0.10.0'}
+
   parse-statements@1.0.11:
     resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==}
 
@@ -4719,8 +5036,8 @@ packages:
     resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
     engines: {node: 18 || 20 || >=22}
 
-  path-to-regexp@8.3.0:
-    resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
+  path-to-regexp@8.4.0:
+    resolution: {integrity: sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==}
 
   path-type@4.0.0:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
@@ -4771,6 +5088,12 @@ packages:
     resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
     engines: {node: '>=8'}
 
+  please-upgrade-node@3.2.0:
+    resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==}
+
+  polka@0.5.2:
+    resolution: {integrity: sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==}
+
   poolifier@5.3.2:
     resolution: {integrity: sha512-5Cu+3i+m5s56mHYPS7OXmsl5Eqs8aoTALPTrhGKcRVoHQgQrySukBHoDmHx3yH6f29tS8JfSse/58Tl987ztow==}
     engines: {node: '>=20.11.0', pnpm: '>=9.0.0'}
@@ -4874,6 +5197,9 @@ packages:
     resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==}
     engines: {node: '>=8'}
 
+  pure-rand@6.1.0:
+    resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
+
   qs@6.15.0:
     resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}
     engines: {node: '>=0.6'}
@@ -4986,6 +5312,9 @@ packages:
   require-main-filename@2.0.0:
     resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
 
+  require-package-name@2.0.1:
+    resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==}
+
   reserved-identifiers@1.2.0:
     resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==}
     engines: {node: '>=18'}
@@ -4993,6 +5322,10 @@ packages:
   resolve-alpn@1.2.1:
     resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
 
+  resolve-dir@1.0.1:
+    resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==}
+    engines: {node: '>=0.10.0'}
+
   resolve-from@4.0.0:
     resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
     engines: {node: '>=4'}
@@ -5112,6 +5445,9 @@ packages:
   seedrandom@3.0.5:
     resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
 
+  semver-compare@1.0.0:
+    resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
+
   semver-diff@3.1.1:
     resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==}
     engines: {node: '>=8'}
@@ -5220,6 +5556,17 @@ packages:
   sinusoidal-decimal@1.0.0:
     resolution: {integrity: sha512-KPUi1ZqLocV64n0AuV+g4VDjAM+tEEY66nUd+rYaVBHIfeheGGUvIxe9bf7Mpc1PonDTVW2uRr9nigQa9odvuA==}
 
+  sirv@2.0.4:
+    resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
+    engines: {node: '>= 10'}
+
+  skott-webapp@2.3.0:
+    resolution: {integrity: sha512-nmt+ilxGOqX5zN2WDKv1Y5gLfxy/lceHgbB8HM/ym/Cm8572ypD1s2S+pcN+jOw13xqoavHJPonX1WT2QvkpDg==}
+
+  skott@0.35.8:
+    resolution: {integrity: sha512-PRh9cIAZfWn1ry0mXDXJW5xqCCI4ly/EwsLFF/+/RLf5qT+QdrEhAbBHeSgjtds/cqkyUSsYkiAvo0EGIvHd0w==}
+    hasBin: true
+
   slash@3.0.0:
     resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
     engines: {node: '>=8'}
@@ -5274,6 +5621,9 @@ packages:
     resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
     engines: {node: '>= 10.x'}
 
+  sprintf-js@1.0.3:
+    resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
   sqlstring-sqlite@0.1.1:
     resolution: {integrity: sha512-9CAYUJ0lEUPYJrswqiqdINNSfq3jqWo/bFJ7tufdoNeSK0Fy+d1kFTxjqO9PIqza0Kri+ZtYMfPVf1aZaFOvrQ==}
     engines: {node: '>= 0.6'}
@@ -5559,6 +5909,10 @@ packages:
     resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
     engines: {node: '>=0.6'}
 
+  totalist@3.0.1:
+    resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+    engines: {node: '>=6'}
+
   tough-cookie@6.0.1:
     resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==}
     engines: {node: '>=16'}
@@ -5581,6 +5935,10 @@ packages:
     resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
     engines: {node: '>= 14.0.0'}
 
+  trouter@2.0.1:
+    resolution: {integrity: sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==}
+    engines: {node: '>=6'}
+
   ts-api-utils@2.5.0:
     resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
     engines: {node: '>=18.12'}
@@ -5697,6 +6055,11 @@ packages:
       eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
       typescript: '>=4.8.4 <6.0.0'
 
+  typescript@5.9.3:
+    resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
   typescript@6.0.2:
     resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==}
     engines: {node: '>=14.17'}
@@ -6003,6 +6366,10 @@ packages:
     resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
     engines: {node: '>= 0.4'}
 
+  which@1.3.1:
+    resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
+    hasBin: true
+
   which@2.0.2:
     resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
     engines: {node: '>= 8'}
@@ -6110,6 +6477,10 @@ packages:
     resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
     engines: {node: '>=18'}
 
+  yaml@1.10.3:
+    resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==}
+    engines: {node: '>= 6'}
+
   yaml@2.8.3:
     resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
     engines: {node: '>= 14.6'}
@@ -6118,6 +6489,10 @@ packages:
   yargs-parser@15.0.3:
     resolution: {integrity: sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA==}
 
+  yargs-parser@20.2.9:
+    resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
+    engines: {node: '>=10'}
+
   yargs-parser@21.1.1:
     resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
     engines: {node: '>=12'}
@@ -6125,6 +6500,10 @@ packages:
   yargs@14.2.3:
     resolution: {integrity: sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==}
 
+  yargs@16.2.0:
+    resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
+    engines: {node: '>=10'}
+
   yargs@17.7.2:
     resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
     engines: {node: '>=12'}
@@ -6184,6 +6563,8 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@arr/every@1.0.1': {}
+
   '@asamuzakjp/css-color@5.0.1':
     dependencies:
       '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
@@ -7238,11 +7619,75 @@ snapshots:
 
   '@oxc-project/types@0.122.0': {}
 
+  '@parcel/watcher-android-arm64@2.5.4':
+    optional: true
+
+  '@parcel/watcher-darwin-arm64@2.5.4':
+    optional: true
+
+  '@parcel/watcher-darwin-x64@2.5.4':
+    optional: true
+
+  '@parcel/watcher-freebsd-x64@2.5.4':
+    optional: true
+
+  '@parcel/watcher-linux-arm-glibc@2.5.4':
+    optional: true
+
+  '@parcel/watcher-linux-arm-musl@2.5.4':
+    optional: true
+
+  '@parcel/watcher-linux-arm64-glibc@2.5.4':
+    optional: true
+
+  '@parcel/watcher-linux-arm64-musl@2.5.4':
+    optional: true
+
+  '@parcel/watcher-linux-x64-glibc@2.5.4':
+    optional: true
+
+  '@parcel/watcher-linux-x64-musl@2.5.4':
+    optional: true
+
+  '@parcel/watcher-win32-arm64@2.5.4':
+    optional: true
+
+  '@parcel/watcher-win32-ia32@2.5.4':
+    optional: true
+
+  '@parcel/watcher-win32-x64@2.5.4':
+    optional: true
+
+  '@parcel/watcher@2.5.4':
+    dependencies:
+      detect-libc: 2.1.2
+      is-glob: 4.0.3
+      node-addon-api: 7.1.1
+      picomatch: 4.0.4
+    optionalDependencies:
+      '@parcel/watcher-android-arm64': 2.5.4
+      '@parcel/watcher-darwin-arm64': 2.5.4
+      '@parcel/watcher-darwin-x64': 2.5.4
+      '@parcel/watcher-freebsd-x64': 2.5.4
+      '@parcel/watcher-linux-arm-glibc': 2.5.4
+      '@parcel/watcher-linux-arm-musl': 2.5.4
+      '@parcel/watcher-linux-arm64-glibc': 2.5.4
+      '@parcel/watcher-linux-arm64-musl': 2.5.4
+      '@parcel/watcher-linux-x64-glibc': 2.5.4
+      '@parcel/watcher-linux-x64-musl': 2.5.4
+      '@parcel/watcher-win32-arm64': 2.5.4
+      '@parcel/watcher-win32-ia32': 2.5.4
+      '@parcel/watcher-win32-x64': 2.5.4
+
   '@pkgjs/parseargs@0.11.0':
     optional: true
 
   '@pkgr/core@0.2.9': {}
 
+  '@polka/url@0.5.0': {}
+
+  '@polka/url@1.0.0-next.29': {}
+
   '@rolldown/binding-android-arm64@1.0.0-rc.12':
     optional: true
 
@@ -7391,12 +7836,16 @@ snapshots:
 
   '@types/long@4.0.2': {}
 
+  '@types/minimatch@3.0.5': {}
+
   '@types/node@24.12.0':
     dependencies:
       undici-types: 7.16.0
 
   '@types/offscreencanvas@2019.3.0': {}
 
+  '@types/parse-json@4.0.2': {}
+
   '@types/seedrandom@2.4.34': {}
 
   '@types/semver@7.7.1': {}
@@ -7445,6 +7894,15 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/project-service@8.53.0(typescript@5.9.3)':
+    dependencies:
+      '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3)
+      '@typescript-eslint/types': 8.57.1
+      debug: 4.4.3
+      typescript: 5.9.3
+    transitivePeerDependencies:
+      - supports-color
+
   '@typescript-eslint/project-service@8.57.1(typescript@6.0.2)':
     dependencies:
       '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@6.0.2)
@@ -7459,6 +7917,14 @@ snapshots:
       '@typescript-eslint/types': 8.57.1
       '@typescript-eslint/visitor-keys': 8.57.1
 
+  '@typescript-eslint/tsconfig-utils@8.53.0(typescript@5.9.3)':
+    dependencies:
+      typescript: 5.9.3
+
+  '@typescript-eslint/tsconfig-utils@8.57.1(typescript@5.9.3)':
+    dependencies:
+      typescript: 5.9.3
+
   '@typescript-eslint/tsconfig-utils@8.57.1(typescript@6.0.2)':
     dependencies:
       typescript: 6.0.2
@@ -7475,8 +7941,25 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/types@8.53.0': {}
+
   '@typescript-eslint/types@8.57.1': {}
 
+  '@typescript-eslint/typescript-estree@8.53.0(typescript@5.9.3)':
+    dependencies:
+      '@typescript-eslint/project-service': 8.53.0(typescript@5.9.3)
+      '@typescript-eslint/tsconfig-utils': 8.53.0(typescript@5.9.3)
+      '@typescript-eslint/types': 8.53.0
+      '@typescript-eslint/visitor-keys': 8.53.0
+      debug: 4.4.3
+      minimatch: 10.2.4
+      semver: 7.7.4
+      tinyglobby: 0.2.15
+      ts-api-utils: 2.5.0(typescript@5.9.3)
+      typescript: 5.9.3
+    transitivePeerDependencies:
+      - supports-color
+
   '@typescript-eslint/typescript-estree@8.57.1(typescript@6.0.2)':
     dependencies:
       '@typescript-eslint/project-service': 8.57.1(typescript@6.0.2)
@@ -7503,6 +7986,11 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/visitor-keys@8.53.0':
+    dependencies:
+      '@typescript-eslint/types': 8.53.0
+      eslint-visitor-keys: 4.2.1
+
   '@typescript-eslint/visitor-keys@8.57.1':
     dependencies:
       '@typescript-eslint/types': 8.57.1
@@ -7829,6 +8317,10 @@ snapshots:
 
   arg@4.1.3: {}
 
+  argparse@1.0.10:
+    dependencies:
+      sprintf-js: 1.0.3
+
   argparse@2.0.1: {}
 
   array-buffer-byte-length@1.0.2:
@@ -7836,6 +8328,8 @@ snapshots:
       call-bound: 1.0.4
       is-array-buffer: 3.0.5
 
+  array-differ@3.0.0: {}
+
   array-flatten@3.0.0: {}
 
   array-from@2.1.1: {}
@@ -7898,6 +8392,8 @@ snapshots:
       get-intrinsic: 1.3.0
       is-array-buffer: 3.0.5
 
+  arrify@2.0.1: {}
+
   asn1.js@4.10.1:
     dependencies:
       bn.js: 5.2.3
@@ -7983,6 +8479,8 @@ snapshots:
 
   b4a@1.8.0: {}
 
+  balanced-match@1.0.2: {}
+
   balanced-match@4.0.4: {}
 
   base64-js@1.5.1: {}
@@ -8049,6 +8547,10 @@ snapshots:
       widest-line: 3.1.0
       wrap-ansi: 7.0.0
 
+  brace-expansion@2.0.3:
+    dependencies:
+      balanced-match: 1.0.2
+
   brace-expansion@5.0.5:
     dependencies:
       balanced-match: 4.0.4
@@ -8244,6 +8746,8 @@ snapshots:
       call-bind-apply-helpers: 1.0.2
       get-intrinsic: 1.3.0
 
+  callsite@1.0.0: {}
+
   callsites@3.1.0: {}
 
   camel-case@3.0.0:
@@ -8387,6 +8891,12 @@ snapshots:
       strip-ansi: 5.2.0
       wrap-ansi: 5.1.0
 
+  cliui@7.0.4:
+    dependencies:
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wrap-ansi: 7.0.0
+
   cliui@8.0.1:
     dependencies:
       string-width: 4.2.3
@@ -8445,6 +8955,8 @@ snapshots:
 
   commander@10.0.1: {}
 
+  commander@11.1.0: {}
+
   commander@14.0.3: {}
 
   commander@2.20.3: {}
@@ -8466,6 +8978,22 @@ snapshots:
       array-ify: 1.0.0
       dot-prop: 5.3.0
 
+  compressible@2.0.18:
+    dependencies:
+      mime-db: 1.54.0
+
+  compression@1.8.1:
+    dependencies:
+      bytes: 3.1.2
+      compressible: 2.0.18
+      debug: 2.6.9
+      negotiator: 0.6.4
+      on-headers: 1.1.0
+      safe-buffer: 5.2.1
+      vary: 1.1.2
+    transitivePeerDependencies:
+      - supports-color
+
   concat-stream@1.6.2:
     dependencies:
       buffer-from: 1.1.2
@@ -8562,6 +9090,14 @@ snapshots:
       jiti: 2.6.1
       typescript: 6.0.2
 
+  cosmiconfig@7.1.0:
+    dependencies:
+      '@types/parse-json': 4.0.2
+      import-fresh: 3.3.1
+      parse-json: 5.2.0
+      path-type: 4.0.0
+      yaml: 1.10.3
+
   cosmiconfig@9.0.1(typescript@6.0.2):
     dependencies:
       env-paths: 2.2.1
@@ -8833,6 +9369,10 @@ snapshots:
 
   debounce@1.2.1: {}
 
+  debug@2.6.9:
+    dependencies:
+      ms: 2.0.0
+
   debug@4.3.4:
     dependencies:
       ms: 2.1.2
@@ -8890,8 +9430,38 @@ snapshots:
 
   denque@2.1.0: {}
 
+  depcheck@1.4.7:
+    dependencies:
+      '@babel/parser': 7.29.2
+      '@babel/traverse': 7.29.0
+      '@vue/compiler-sfc': 3.5.31
+      callsite: 1.0.0
+      camelcase: 6.3.0
+      cosmiconfig: 7.1.0
+      debug: 4.4.3
+      deps-regex: 0.2.0
+      findup-sync: 5.0.0
+      ignore: 5.3.2
+      is-core-module: 2.16.1
+      js-yaml: 3.14.2
+      json5: 2.2.3
+      lodash: 4.17.23
+      minimatch: 7.4.9
+      multimatch: 5.0.0
+      please-upgrade-node: 3.2.0
+      readdirp: 3.6.0
+      require-package-name: 2.0.1
+      resolve: 1.22.11
+      resolve-from: 5.0.0
+      semver: 7.7.4
+      yargs: 16.2.0
+    transitivePeerDependencies:
+      - supports-color
+
   depd@2.0.0: {}
 
+  deps-regex@0.2.0: {}
+
   deps-sort@2.0.1:
     dependencies:
       JSONStream: 1.3.5
@@ -8904,6 +9474,8 @@ snapshots:
       inherits: 2.0.4
       minimalistic-assert: 1.0.1
 
+  detect-file@1.0.0: {}
+
   detect-libc@2.1.2: {}
 
   detective@5.2.1:
@@ -8920,6 +9492,10 @@ snapshots:
       miller-rabin: 4.0.1
       randombytes: 2.1.0
 
+  digraph-js@2.2.4:
+    dependencies:
+      lodash.uniqwith: 4.5.0
+
   dir-glob@3.0.1:
     dependencies:
       path-type: 4.0.0
@@ -8979,6 +9555,11 @@ snapshots:
 
   ee-first@1.1.1: {}
 
+  effect@3.21.0:
+    dependencies:
+      '@standard-schema/spec': 1.1.0
+      fast-check: 3.23.2
+
   electron-to-chromium@1.5.321: {}
 
   elliptic@6.6.1:
@@ -9489,6 +10070,10 @@ snapshots:
 
   expand-template@2.0.3: {}
 
+  expand-tilde@2.0.2:
+    dependencies:
+      homedir-polyfill: 1.0.3
+
   expect-type@1.3.0: {}
 
   express-rate-limit@8.3.1(express@5.2.1):
@@ -9545,6 +10130,10 @@ snapshots:
 
   extsprintf@1.3.0: {}
 
+  fast-check@3.23.2:
+    dependencies:
+      pure-rand: 6.1.0
+
   fast-deep-equal@3.1.3: {}
 
   fast-equals@6.0.0: {}
@@ -9622,6 +10211,13 @@ snapshots:
       locate-path: 8.0.0
       unicorn-magic: 0.3.0
 
+  findup-sync@5.0.0:
+    dependencies:
+      detect-file: 1.0.0
+      is-glob: 4.0.3
+      micromatch: 4.0.8
+      resolve-dir: 1.0.1
+
   flame-gradient@1.0.0:
     dependencies:
       sinusoidal-decimal: 1.0.0
@@ -9660,6 +10256,8 @@ snapshots:
 
   forwarded@0.2.0: {}
 
+  fp-ts@2.5.0: {}
+
   fresh@2.0.0: {}
 
   from2-string@1.1.0:
@@ -9691,6 +10289,10 @@ snapshots:
       jsonfile: 6.2.0
       universalify: 2.0.1
 
+  fs-tree-structure@0.0.5:
+    dependencies:
+      lodash-es: 4.17.23
+
   fs.realpath@1.0.0: {}
 
   fsevents@2.3.3:
@@ -9828,6 +10430,20 @@ snapshots:
     dependencies:
       ini: 2.0.0
 
+  global-modules@1.0.0:
+    dependencies:
+      global-prefix: 1.0.2
+      is-windows: 1.0.2
+      resolve-dir: 1.0.1
+
+  global-prefix@1.0.2:
+    dependencies:
+      expand-tilde: 2.0.2
+      homedir-polyfill: 1.0.3
+      ini: 1.3.8
+      is-windows: 1.0.2
+      which: 1.3.1
+
   globals@14.0.0: {}
 
   globals@15.15.0: {}
@@ -9954,6 +10570,10 @@ snapshots:
       minimalistic-assert: 1.0.1
       minimalistic-crypto-utils: 1.0.1
 
+  homedir-polyfill@1.0.3:
+    dependencies:
+      parse-passwd: 1.0.0
+
   hono@4.12.8: {}
 
   hookable@5.5.3: {}
@@ -10029,6 +10649,10 @@ snapshots:
 
   ieee754@1.2.1: {}
 
+  ignore-walk@6.0.5:
+    dependencies:
+      minimatch: 10.2.4
+
   ignore@5.3.2: {}
 
   ignore@7.0.5: {}
@@ -10118,6 +10742,10 @@ snapshots:
 
   interpret@2.2.0: {}
 
+  io-ts@2.2.22(fp-ts@2.5.0):
+    dependencies:
+      fp-ts: 2.5.0
+
   iota-array@1.0.0: {}
 
   ip-address@10.1.0: {}
@@ -10189,6 +10817,8 @@ snapshots:
 
   is-docker@2.2.1: {}
 
+  is-docker@3.0.0: {}
+
   is-extglob@2.1.1: {}
 
   is-finalizationregistry@1.1.1:
@@ -10219,6 +10849,10 @@ snapshots:
     dependencies:
       is-extglob: 2.1.1
 
+  is-inside-container@1.0.0:
+    dependencies:
+      is-docker: 3.0.0
+
   is-installed-globally@0.4.0:
     dependencies:
       global-dirs: 3.0.1
@@ -10302,12 +10936,18 @@ snapshots:
       call-bound: 1.0.4
       get-intrinsic: 1.3.0
 
+  is-windows@1.0.2: {}
+
   is-wsl@1.1.0: {}
 
   is-wsl@2.2.0:
     dependencies:
       is-docker: 2.2.1
 
+  is-wsl@3.1.1:
+    dependencies:
+      is-inside-container: 1.0.0
+
   is-yarn-global@0.3.0: {}
 
   isarray@1.0.0: {}
@@ -10364,6 +11004,11 @@ snapshots:
 
   js-tokens@4.0.0: {}
 
+  js-yaml@3.14.2:
+    dependencies:
+      argparse: 1.0.10
+      esprima: 4.0.1
+
   js-yaml@4.1.1:
     dependencies:
       argparse: 2.0.1
@@ -10456,6 +11101,8 @@ snapshots:
     dependencies:
       '@keyv/serialize': 1.1.1
 
+  kleur@4.1.5: {}
+
   knex@3.1.0(better-sqlite3@11.10.0):
     dependencies:
       colorette: 2.0.19
@@ -10588,6 +11235,8 @@ snapshots:
     dependencies:
       p-locate: 6.0.0
 
+  lodash-es@4.17.23: {}
+
   lodash.camelcase@4.3.0: {}
 
   lodash.chunk@4.2.0: {}
@@ -10610,6 +11259,8 @@ snapshots:
 
   lodash.startcase@4.4.0: {}
 
+  lodash.uniqwith@4.5.0: {}
+
   lodash.upperfirst@4.3.1: {}
 
   lodash@4.17.23: {}
@@ -10698,6 +11349,10 @@ snapshots:
       iconv-lite: 0.6.3
       lru-cache: 10.4.3
 
+  matchit@1.1.0:
+    dependencies:
+      '@arr/every': 1.0.1
+
   math-intrinsics@1.1.0: {}
 
   md5.js@1.3.5:
@@ -10724,6 +11379,8 @@ snapshots:
 
   merge2@1.4.1: {}
 
+  meriyah@4.5.0: {}
+
   micromatch@4.0.8:
     dependencies:
       braces: 3.0.3
@@ -10781,6 +11438,10 @@ snapshots:
     dependencies:
       brace-expansion: 5.0.5
 
+  minimatch@7.4.9:
+    dependencies:
+      brace-expansion: 2.0.3
+
   minimist@1.2.8: {}
 
   minipass@7.1.3: {}
@@ -10875,12 +11536,24 @@ snapshots:
 
   morphdom@2.7.8: {}
 
+  mrmime@2.0.1: {}
+
+  ms@2.0.0: {}
+
   ms@2.1.2: {}
 
   ms@2.1.3: {}
 
   muggle-string@0.4.1: {}
 
+  multimatch@5.0.0:
+    dependencies:
+      '@types/minimatch': 3.0.5
+      array-differ: 3.0.0
+      array-union: 2.1.0
+      arrify: 2.0.1
+      minimatch: 3.1.5
+
   multistream@2.1.1:
     dependencies:
       inherits: 2.0.4
@@ -10917,6 +11590,10 @@ snapshots:
 
   nanoid@3.3.11: {}
 
+  nanospinner@1.2.2:
+    dependencies:
+      picocolors: 1.1.1
+
   napi-build-utils@2.0.0: {}
 
   natural-compare@1.4.0: {}
@@ -10964,6 +11641,8 @@ snapshots:
       iota-array: 1.0.0
       is-buffer: 1.1.6
 
+  negotiator@0.6.4: {}
+
   negotiator@1.0.0: {}
 
   neostandard@0.13.0(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2):
@@ -10992,6 +11671,8 @@ snapshots:
     dependencies:
       semver: 7.7.4
 
+  node-addon-api@7.1.1: {}
+
   node-exports-info@1.6.0:
     dependencies:
       array.prototype.flatmap: 1.3.3
@@ -11078,6 +11759,8 @@ snapshots:
     dependencies:
       ee-first: 1.1.1
 
+  on-headers@1.1.0: {}
+
   on-net-listen@1.1.2: {}
 
   once@1.4.0:
@@ -11217,6 +11900,8 @@ snapshots:
       pbkdf2: 3.1.5
       safe-buffer: 5.2.1
 
+  parse-gitignore@2.0.0: {}
+
   parse-imports-exports@0.2.4:
     dependencies:
       parse-statements: 1.0.11
@@ -11228,6 +11913,8 @@ snapshots:
       json-parse-even-better-errors: 2.3.1
       lines-and-columns: 1.2.4
 
+  parse-passwd@1.0.0: {}
+
   parse-statements@1.0.11: {}
 
   parse5@7.3.0:
@@ -11264,7 +11951,7 @@ snapshots:
       lru-cache: 11.2.7
       minipass: 7.1.3
 
-  path-to-regexp@8.3.0: {}
+  path-to-regexp@8.4.0: {}
 
   path-type@4.0.0: {}
 
@@ -11311,6 +11998,15 @@ snapshots:
     dependencies:
       find-up: 3.0.0
 
+  please-upgrade-node@3.2.0:
+    dependencies:
+      semver-compare: 1.0.0
+
+  polka@0.5.2:
+    dependencies:
+      '@polka/url': 0.5.0
+      trouter: 2.0.1
+
   poolifier@5.3.2: {}
 
   possible-typed-array-names@1.1.0: {}
@@ -11428,6 +12124,8 @@ snapshots:
     dependencies:
       escape-goat: 2.1.1
 
+  pure-rand@6.1.0: {}
+
   qs@6.15.0:
     dependencies:
       side-channel: 1.1.0
@@ -11572,10 +12270,17 @@ snapshots:
 
   require-main-filename@2.0.0: {}
 
+  require-package-name@2.0.1: {}
+
   reserved-identifiers@1.2.0: {}
 
   resolve-alpn@1.2.1: {}
 
+  resolve-dir@1.0.1:
+    dependencies:
+      expand-tilde: 2.0.2
+      global-modules: 1.0.0
+
   resolve-from@4.0.0: {}
 
   resolve-from@5.0.0: {}
@@ -11663,7 +12368,7 @@ snapshots:
       depd: 2.0.0
       is-promise: 4.0.0
       parseurl: 1.3.3
-      path-to-regexp: 8.3.0
+      path-to-regexp: 8.4.0
     transitivePeerDependencies:
       - supports-color
 
@@ -11722,6 +12427,8 @@ snapshots:
 
   seedrandom@3.0.5: {}
 
+  semver-compare@1.0.0: {}
+
   semver-diff@3.1.1:
     dependencies:
       semver: 6.3.1
@@ -11857,6 +12564,45 @@ snapshots:
 
   sinusoidal-decimal@1.0.0: {}
 
+  sirv@2.0.4:
+    dependencies:
+      '@polka/url': 1.0.0-next.29
+      mrmime: 2.0.1
+      totalist: 3.0.1
+
+  skott-webapp@2.3.0:
+    dependencies:
+      digraph-js: 2.2.4
+
+  skott@0.35.8:
+    dependencies:
+      '@parcel/watcher': 2.5.4
+      '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.9.3)
+      commander: 11.1.0
+      compression: 1.8.1
+      depcheck: 1.4.7
+      digraph-js: 2.2.4
+      effect: 3.21.0
+      estree-walker: 3.0.3
+      fp-ts: 2.5.0
+      fs-tree-structure: 0.0.5
+      ignore-walk: 6.0.5
+      io-ts: 2.2.22(fp-ts@2.5.0)
+      is-wsl: 3.1.1
+      json5: 2.2.3
+      kleur: 4.1.5
+      lodash-es: 4.17.23
+      meriyah: 4.5.0
+      minimatch: 10.2.4
+      nanospinner: 1.2.2
+      parse-gitignore: 2.0.0
+      polka: 0.5.2
+      sirv: 2.0.4
+      skott-webapp: 2.3.0
+      typescript: 5.9.3
+    transitivePeerDependencies:
+      - supports-color
+
   slash@3.0.0: {}
 
   slice-ansi@7.1.2:
@@ -11904,6 +12650,8 @@ snapshots:
 
   split2@4.2.0: {}
 
+  sprintf-js@1.0.3: {}
+
   sqlstring-sqlite@0.1.1: {}
 
   sqlstring@2.3.3: {}
@@ -12242,6 +12990,8 @@ snapshots:
 
   toidentifier@1.0.1: {}
 
+  totalist@3.0.1: {}
+
   tough-cookie@6.0.1:
     dependencies:
       tldts: 7.0.27
@@ -12268,6 +13018,14 @@ snapshots:
 
   triple-beam@1.4.1: {}
 
+  trouter@2.0.1:
+    dependencies:
+      matchit: 1.1.0
+
+  ts-api-utils@2.5.0(typescript@5.9.3):
+    dependencies:
+      typescript: 5.9.3
+
   ts-api-utils@2.5.0(typescript@6.0.2):
     dependencies:
       typescript: 6.0.2
@@ -12412,6 +13170,8 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  typescript@5.9.3: {}
+
   typescript@6.0.2: {}
 
   ufo@1.6.3: {}
@@ -12722,6 +13482,10 @@ snapshots:
       gopd: 1.2.0
       has-tostringtag: 1.0.2
 
+  which@1.3.1:
+    dependencies:
+      isexe: 2.0.0
+
   which@2.0.2:
     dependencies:
       isexe: 2.0.0
@@ -12827,6 +13591,8 @@ snapshots:
 
   yallist@5.0.0: {}
 
+  yaml@1.10.3: {}
+
   yaml@2.8.3: {}
 
   yargs-parser@15.0.3:
@@ -12834,6 +13600,8 @@ snapshots:
       camelcase: 5.3.1
       decamelize: 1.2.0
 
+  yargs-parser@20.2.9: {}
+
   yargs-parser@21.1.1: {}
 
   yargs@14.2.3:
@@ -12850,6 +13618,16 @@ snapshots:
       y18n: 4.0.3
       yargs-parser: 15.0.3
 
+  yargs@16.2.0:
+    dependencies:
+      cliui: 7.0.4
+      escalade: 3.2.0
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      string-width: 4.2.3
+      y18n: 5.0.8
+      yargs-parser: 20.2.9
+
   yargs@17.7.2:
     dependencies:
       cliui: 8.0.1
index e2b42a073db71f8e94f33fb9e65c52ef1abf25f9..0d1a4107b80217e92ab34c42dc5d1f8014ccc035 100644 (file)
@@ -6,10 +6,12 @@ overrides:
   bn.js@<5.2.3: '>=5.2.3'
   brace-expansion@<1.1.13: '>=1.1.13'
   d3-color@<3.1.0: '>=3.1.0'
+  effect@<3.20.0: '>=3.20.0'
   form-data@<2.5.4: '>=2.5.4'
   got@<11.8.5: '>=11.8.5'
   minimatch@>=10.0.0 <10.2.3: '>=10.2.3'
   minimatch@>=9.0.0 <9.0.7: '>=9.0.7'
+  path-to-regexp@>=8.0.0 <8.4.0: '>=8.4.0'
   picomatch@<2.3.2: '>=2.3.2'
   qs@<6.14.1: '>=6.14.1'
   smol-toml@<1.6.1: '>=1.6.1'
index ed0817a2e9e8a4026ab826fb969f833049f8fc8a..acc6388da924a8136e319d0924b711585df00370 100644 (file)
@@ -10,6 +10,7 @@ import { fileURLToPath } from 'node:url'
 import { isMainThread } from 'node:worker_threads'
 import { availableParallelism, type MessageHandler } from 'poolifier'
 
+import type { IBootstrap } from './IBootstrap.js'
 import type { AbstractUIServer } from './ui-server/AbstractUIServer.js'
 
 import packageJson from '../../package.json' with { type: 'json' }
@@ -64,7 +65,7 @@ enum exitCodes {
   gracefulShutdownError = 4,
 }
 
-export class Bootstrap extends EventEmitter {
+export class Bootstrap extends EventEmitter implements IBootstrap {
   private static instance: Bootstrap | null = null
   public get numberOfChargingStationTemplates (): number {
     return this.templateStatistics.size
@@ -122,7 +123,8 @@ export class Bootstrap extends EventEmitter {
     this.uiServerStarted = false
     this.templateStatistics = new Map<string, TemplateStatistics>()
     this.uiServer = UIServerFactory.getUIServerImplementation(
-      Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
+      Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer),
+      this
     )
     this.initializeCounters()
     this.initializeWorkerImplementation(
index a14addfb67eca7ac924641fc6684a1c4663ebbf4..8f96d7b398090a612f80d9d97473adc9f9d79fdc 100644 (file)
@@ -88,6 +88,7 @@ import {
   formatDurationMilliSeconds,
   formatDurationSeconds,
   getErrorMessage,
+  getMessageTypeString,
   getWebSocketCloseEventStatusString,
   handleFileException,
   isEmpty,
@@ -132,7 +133,6 @@ import {
   getHashId,
   getIdTagsFile,
   getMaxNumberOfEvses,
-  getMessageTypeString,
   getPhaseRotationValue,
   hasFeatureProfile,
   hasReservationExpired,
index f162d7c41f79450655e8c627cd547b7735ead6e8..ae29991ef597e7e9206a7587aefade6f4b330e8a 100644 (file)
@@ -45,7 +45,6 @@ import {
   ConnectorStatusEnum,
   CurrentType,
   type EvseTemplate,
-  MessageType,
   OCPPVersion,
   RecurrencyKindType,
   type Reservation,
@@ -1480,16 +1479,3 @@ const getRandomSerialNumberSuffix = (params?: {
   }
   return randomSerialNumberSuffix
 }
-
-export const getMessageTypeString = (messageType: MessageType | undefined): string => {
-  switch (messageType) {
-    case MessageType.CALL_ERROR_MESSAGE:
-      return 'error'
-    case MessageType.CALL_MESSAGE:
-      return 'request'
-    case MessageType.CALL_RESULT_MESSAGE:
-      return 'response'
-    default:
-      return 'unknown'
-  }
-}
diff --git a/src/charging-station/IBootstrap.ts b/src/charging-station/IBootstrap.ts
new file mode 100644 (file)
index 0000000..b12cbcb
--- /dev/null
@@ -0,0 +1,19 @@
+import type {
+  ChargingStationInfo,
+  ChargingStationOptions,
+  SimulatorState,
+  Statistics,
+} from '../types/index.js'
+
+export interface IBootstrap {
+  addChargingStation(
+    index: number,
+    templateFile: string,
+    options?: ChargingStationOptions
+  ): Promise<ChargingStationInfo | undefined>
+  getLastContiguousIndex(templateName: string): number
+  getPerformanceStatistics(): IterableIterator<Statistics> | undefined
+  getState(): SimulatorState
+  start(): Promise<void>
+  stop(): Promise<void>
+}
index 0d25e222e7c8213ff46b976e4364b5c4c68f4045..c1eb1836e093591018c57d8e6b0ecfb26c699bc2 100644 (file)
@@ -11,7 +11,6 @@ export {
   checkChargingStationState,
   getConnectorChargingProfiles,
   getIdTagsFile,
-  getMessageTypeString,
   hasFeatureProfile,
   hasPendingReservation,
   hasPendingReservations,
@@ -21,3 +20,4 @@ export {
   resetAuthorizeConnectorStatus,
   resetConnectorStatus,
 } from './Helpers.js'
+export type { IBootstrap } from './IBootstrap.js'
index 33b802c22dc4ed3923255696c71a9e3b41535963..a71f4f1c7166e6f8247dc64247db4a09c536b3bc 100644 (file)
@@ -115,9 +115,11 @@ import {
   sleep,
   truncateId,
 } from '../../../utils/index.js'
+import { AuthContext } from '../auth/index.js'
+import { isIdTagAuthorized } from '../IdTagAuthorization.js'
 import { OCPPConstants } from '../OCPPConstants.js'
 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
-import { buildMeterValue, OCPPServiceUtils } from '../OCPPServiceUtils.js'
+import { buildMeterValue } from '../OCPPServiceUtils.js'
 import { OCPP16Constants } from './OCPP16Constants.js'
 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
 
@@ -1244,10 +1246,11 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     // idTag authorization check required
     if (
       chargingStation.getAuthorizeRemoteTxRequests() &&
-      !(await OCPPServiceUtils.isIdTagAuthorizedUnified(
+      !(await isIdTagAuthorized(
         chargingStation,
         transactionConnectorId,
-        idTag
+        idTag,
+        AuthContext.REMOTE_START
       ))
     ) {
       return this.notifyRemoteStartTransactionRejected(
@@ -1323,7 +1326,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
       return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
     }
-    if (!(await OCPPServiceUtils.isIdTagAuthorizedUnified(chargingStation, connectorId, idTag))) {
+    if (!(await isIdTagAuthorized(chargingStation, connectorId, idTag, AuthContext.RESERVATION))) {
       return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
     }
     const connectorStatus = chargingStation.getConnectorStatus(connectorId)
index 90fa1331181d6973b17d165a478a2c277d72f2a9..fe02790e781c4ea697ab5f2be43c2544dc2384ff 100644 (file)
@@ -3,6 +3,7 @@
 import type { ValidateFunction } from 'ajv'
 
 import type { ChargingStation } from '../../../charging-station/index.js'
+import type { OCPP20IdTokenEnumType } from '../../../types/index.js'
 
 import { OCPPError } from '../../../exception/index.js'
 import {
@@ -136,12 +137,16 @@ import {
 } from '../../../utils/index.js'
 import { buildConfigKey, getConfigurationKey } from '../../ConfigurationKeyUtils.js'
 import {
-  getIdTagsFile,
   hasPendingReservation,
   hasPendingReservations,
   resetConnectorStatus,
 } from '../../Helpers.js'
-import { OCPPAuthServiceFactory } from '../auth/index.js'
+import {
+  AuthContext,
+  AuthorizationStatus,
+  mapOCPP20TokenType,
+  OCPPAuthServiceFactory,
+} from '../auth/index.js'
 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
 import {
   buildMeterValue,
@@ -729,11 +734,9 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
    * @param chargingStation - The charging station instance
    * @returns Promise resolving to ClearCacheResponse
    */
-  protected async handleRequestClearCache (
-    chargingStation: ChargingStation
-  ): Promise<OCPP20ClearCacheResponse> {
+  protected handleRequestClearCache (chargingStation: ChargingStation): OCPP20ClearCacheResponse {
     try {
-      const authService = await OCPPAuthServiceFactory.getInstance(chargingStation)
+      const authService = OCPPAuthServiceFactory.getInstance(chargingStation)
       // C11.FR.04: IF AuthCacheEnabled is false, CS SHALL send ClearCacheResponse with status Rejected
       const config = authService.getConfiguration()
       if (!config.authorizationCacheEnabled) {
@@ -766,6 +769,35 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     )
   }
 
+  private async authorizeToken (
+    chargingStation: ChargingStation,
+    connectorId: number,
+    tokenValue: string,
+    tokenLabel: string,
+    ocpp20TokenType: OCPP20IdTokenEnumType,
+    context?: AuthContext
+  ): Promise<boolean> {
+    const authService = OCPPAuthServiceFactory.getInstance(chargingStation)
+    const authResult = await authService.authorize({
+      allowOffline: false,
+      connectorId,
+      context: context ?? AuthContext.REMOTE_START,
+      identifier: {
+        type: mapOCPP20TokenType(ocpp20TokenType),
+        value: tokenValue,
+      },
+      timestamp: new Date(),
+    })
+
+    if (authResult.status !== AuthorizationStatus.ACCEPTED) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.authorizeToken: ${tokenLabel} ${truncateId(tokenValue)} is not authorized`
+      )
+    }
+
+    return authResult.status === AuthorizationStatus.ACCEPTED
+  }
+
   private buildReportData (
     chargingStation: ChargingStation,
     reportBase: ReportBaseEnumType
@@ -2332,7 +2364,13 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
       }
 
       try {
-        isAuthorized = this.isIdTokenAuthorized(chargingStation, idToken)
+        isAuthorized = await this.authorizeToken(
+          chargingStation,
+          connectorId,
+          idToken.idToken,
+          'IdToken',
+          idToken.type
+        )
       } catch (error) {
         logger.error(
           `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Authorization error for ${truncateId(idToken.idToken)}:`,
@@ -2354,9 +2392,6 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     }
 
     if (!isAuthorized) {
-      logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: IdToken ${truncateId(idToken.idToken)} is not authorized`
-      )
       return {
         status: RequestStartStopStatusEnumType.Rejected,
         statusInfo: {
@@ -2370,7 +2405,13 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     if (groupIdToken != null) {
       let isGroupAuthorized = false
       try {
-        isGroupAuthorized = this.isIdTokenAuthorized(chargingStation, groupIdToken)
+        isGroupAuthorized = await this.authorizeToken(
+          chargingStation,
+          connectorId,
+          groupIdToken.idToken,
+          'GroupIdToken',
+          groupIdToken.type
+        )
       } catch (error) {
         logger.error(
           `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Group authorization error for ${truncateId(groupIdToken.idToken)}:`,
@@ -2385,11 +2426,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
           transactionId: generateUUID(),
         }
       }
-
       if (!isGroupAuthorized) {
-        logger.warn(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: GroupIdToken ${truncateId(groupIdToken.idToken)} is not authorized`
-        )
         return {
           status: RequestStartStopStatusEnumType.Rejected,
           statusInfo: {
@@ -2938,97 +2975,6 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     )
   }
 
-  private isIdTokenAuthorized (
-    chargingStation: ChargingStation,
-    idToken: OCPP20IdTokenType
-  ): boolean {
-    /**
-     * OCPP 2.0 Authorization Logic Implementation
-     *
-     * OCPP 2.0 handles authorization differently from 1.6:
-     * 1. Check if authorization is required (LocalAuthorizationOffline, AuthorizeRemoteStart variables)
-     * 2. Local authorization list validation if enabled
-     * 3. For OCPP 2.0, there's no explicit AuthorizeRequest - authorization is validated
-     *    through configuration variables and local auth lists
-     * 4. Remote validation through TransactionEvent if needed
-     */
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Validating idToken ${truncateId(idToken.idToken)} of type ${idToken.type}`
-    )
-
-    try {
-      const localAuthListEnabled = chargingStation.getLocalAuthListEnabled()
-      const remoteAuthorizationEnabled = chargingStation.stationInfo?.remoteAuthorization ?? true
-
-      if (!localAuthListEnabled && !remoteAuthorizationEnabled) {
-        logger.warn(
-          `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Both local and remote authorization are disabled. Allowing access but this may indicate misconfiguration.`
-        )
-        return true
-      }
-
-      if (localAuthListEnabled) {
-        const isLocalAuthorized = this.isIdTokenLocalAuthorized(chargingStation, idToken.idToken)
-        if (isLocalAuthorized) {
-          logger.debug(
-            `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: IdToken ${truncateId(idToken.idToken)} authorized via local auth list`
-          )
-          return true
-        }
-        logger.debug(
-          `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: IdToken ${truncateId(idToken.idToken)} not found in local auth list`
-        )
-      }
-
-      // In OCPP 2.0, remote authorization happens during TransactionEvent processing
-      if (remoteAuthorizationEnabled) {
-        logger.debug(
-          `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Remote authorization enabled but no explicit remote auth mechanism in OCPP 2.0 - deferring to transaction event validation`
-        )
-        return true
-      }
-
-      logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: IdToken ${truncateId(idToken.idToken)} authorization failed - not found in local list and remote auth not configured`
-      )
-      return false
-    } catch (error) {
-      logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Error during authorization validation for ${truncateId(idToken.idToken)}:`,
-        error
-      )
-      return false
-    }
-  }
-
-  /**
-   * Check if idToken is authorized in local authorization list
-   * @param chargingStation - The charging station instance
-   * @param idTokenString - The ID token string to validate
-   * @returns true if authorized locally, false otherwise
-   */
-  private isIdTokenLocalAuthorized (
-    chargingStation: ChargingStation,
-    idTokenString: string
-  ): boolean {
-    try {
-      const idTagsFile =
-        chargingStation.stationInfo != null ? getIdTagsFile(chargingStation.stationInfo) : undefined
-      return (
-        chargingStation.hasIdTags() &&
-        idTagsFile != null &&
-        chargingStation.idTagsCache.getIdTags(idTagsFile)?.includes(idTokenString) === true
-      )
-    } catch (error) {
-      logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.isIdTokenLocalAuthorized: Error checking local authorization for ${idTokenString}:`,
-        error
-      )
-      return false
-    }
-  }
-
   private isValidFirmwareLocation (location: string): boolean {
     try {
       const url = new URL(location)
index d76b628807f6d5d9257d3bf41f27e10eb9c4e5ed..48f6f8df2be230eb66bded1c45714708936c1b20 100644 (file)
@@ -476,17 +476,15 @@ export class OCPP20ResponseService extends OCPPResponseService {
         const idTokenValue = requestPayload.idToken.idToken
         const idTokenInfo = payload.idTokenInfo
         const identifierType = mapOCPP20TokenType(requestPayload.idToken.type)
-        OCPPAuthServiceFactory.getInstance(chargingStation)
-          .then(authService => {
-            authService.updateCacheEntry(idTokenValue, idTokenInfo, identifierType)
-            return undefined
-          })
-          .catch((error: unknown) => {
-            logger.error(
-              `${chargingStation.logPrefix()} ${moduleName}.handleResponseTransactionEvent: Error updating auth cache:`,
-              error
-            )
-          })
+        try {
+          const authService = OCPPAuthServiceFactory.getInstance(chargingStation)
+          authService.updateCacheEntry(idTokenValue, idTokenInfo, identifierType)
+        } catch (error: unknown) {
+          logger.error(
+            `${chargingStation.logPrefix()} ${moduleName}.handleResponseTransactionEvent: Error updating auth cache:`,
+            error
+          )
+        }
       }
     }
     if (payload.updatedPersonalMessage != null) {
index 6ff837a62d1a0693035e736fe8d7f1dee4a3d4eb..b8109c5b7fa1e717204f21b9e190393670579c95 100644 (file)
@@ -753,7 +753,7 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     component: OCPP20ComponentName.DeviceDataCtrlr,
     dataType: DataEnumType.integer,
     defaultValue: Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString(),
-    description: 'Unified maximum size for any stored or reported value.',
+    description: 'Maximum size for any stored or reported value.',
     max: Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH,
     maxLength: 5,
     min: 1,
diff --git a/src/charging-station/ocpp/IdTagAuthorization.ts b/src/charging-station/ocpp/IdTagAuthorization.ts
new file mode 100644 (file)
index 0000000..0412fdd
--- /dev/null
@@ -0,0 +1,64 @@
+import type { ChargingStation } from '../../charging-station/index.js'
+
+import { logger } from '../../utils/index.js'
+import {
+  AuthContext,
+  AuthenticationMethod,
+  AuthorizationStatus,
+  IdentifierType,
+  OCPPAuthServiceFactory,
+} from './auth/index.js'
+
+export const isIdTagAuthorized = async (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  idTag: string,
+  context?: AuthContext
+): Promise<boolean> => {
+  try {
+    logger.debug(
+      `${chargingStation.logPrefix()} Authorizing idTag '${idTag}' on connector ${connectorId.toString()}`
+    )
+
+    const authService = OCPPAuthServiceFactory.getInstance(chargingStation)
+
+    const authResult = await authService.authorize({
+      allowOffline: false,
+      connectorId,
+      context: context ?? AuthContext.TRANSACTION_START,
+      identifier: {
+        type: IdentifierType.ID_TAG,
+        value: idTag,
+      },
+      timestamp: new Date(),
+    })
+
+    logger.debug(
+      `${chargingStation.logPrefix()} Authorization result for idTag '${idTag}': ${authResult.status} using ${authResult.method} method`
+    )
+
+    if (authResult.status === AuthorizationStatus.ACCEPTED) {
+      const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+      if (connectorStatus != null) {
+        switch (authResult.method) {
+          case AuthenticationMethod.CACHE:
+          case AuthenticationMethod.LOCAL_LIST:
+          case AuthenticationMethod.OFFLINE_FALLBACK:
+            connectorStatus.localAuthorizeIdTag = idTag
+            connectorStatus.idTagLocalAuthorized = true
+            break
+          case AuthenticationMethod.CERTIFICATE_BASED:
+          case AuthenticationMethod.NONE:
+          case AuthenticationMethod.REMOTE_AUTHORIZATION:
+            break
+        }
+      }
+      return true
+    }
+
+    return false
+  } catch (error) {
+    logger.error(`${chargingStation.logPrefix()} Authorization failed`, error)
+    return false
+  }
+}
index f433945aeb9b92adbe5835fdec7557676e1f9ae6..c4cab42d899d5a15e63c8e9071ceafba4efc21ff 100644 (file)
@@ -27,10 +27,10 @@ import {
   ensureError,
   formatDurationMilliSeconds,
   getErrorMessage,
+  getMessageTypeString,
   handleSendMessageError,
   logger,
 } from '../../utils/index.js'
-import { getMessageTypeString } from '../index.js'
 import { OCPPConstants } from './OCPPConstants.js'
 import { ajvErrorsToErrorType, convertDateToISOString } from './OCPPServiceUtils.js'
 
index e915b320194295aeba368ff6b3021231a4d371c8..5ed471666d1c9fd19b6533255291dabc191bc602 100644 (file)
@@ -7,15 +7,10 @@ import { fileURLToPath } from 'node:url'
 
 import type { StopTransactionReason } from '../../types/index.js'
 
-import {
-  type ChargingStation,
-  getConfigurationKey,
-  getIdTagsFile,
-} from '../../charging-station/index.js'
+import { type ChargingStation, getConfigurationKey } from '../../charging-station/index.js'
 import { BaseError, OCPPError } from '../../exception/index.js'
 import {
   AuthorizationStatus,
-  type AuthorizeRequest,
   ChargePointErrorCode,
   ChargingStationEvents,
   type ConfigurationKeyType,
@@ -35,13 +30,11 @@ import {
   MeterValueMeasurand,
   MeterValuePhase,
   MeterValueUnit,
-  type OCPP16AuthorizeResponse,
   type OCPP16MeterValue,
   type OCPP16SampledValue,
   type OCPP16StatusNotificationRequest,
   OCPP16StopTransactionReason,
   OCPP20AuthorizationStatusEnumType,
-  type OCPP20AuthorizeResponse,
   type OCPP20ConnectorStatusEnumType,
   OCPP20IdTokenEnumType,
   type OCPP20MeterValue,
@@ -78,6 +71,8 @@ import {
   min,
   roundTo,
 } from '../../utils/index.js'
+import { OCPP16Constants } from './1.6/OCPP16Constants.js'
+import { OCPP20Constants } from './2.0/OCPP20Constants.js'
 import { OCPPConstants } from './OCPPConstants.js'
 
 const moduleName = 'OCPPServiceUtils'
@@ -141,162 +136,6 @@ export const buildStatusNotificationRequest = (
   }
 }
 
-/**
- * Unified authorization function that uses the new OCPP authentication system
- * when enabled, with automatic fallback to legacy system
- * @param chargingStation - The charging station instance
- * @param connectorId - The connector ID for authorization context
- * @param idTag - The identifier to authorize
- * @returns Promise resolving to authorization result
- */
-export const isIdTagAuthorizedUnified = async (
-  chargingStation: ChargingStation,
-  connectorId: number,
-  idTag: string
-): Promise<boolean> => {
-  switch (chargingStation.stationInfo?.ocppVersion) {
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201:
-      try {
-        logger.debug(
-          `${chargingStation.logPrefix()} Using unified auth system for idTag '${idTag}' on connector ${connectorId.toString()}`
-        )
-
-        // Dynamic import to avoid circular dependencies
-        const { OCPPAuthServiceFactory } = await import('./auth/services/OCPPAuthServiceFactory.js')
-        const {
-          AuthContext,
-          AuthorizationStatus: UnifiedAuthorizationStatus,
-          IdentifierType,
-        } = await import('./auth/types/AuthTypes.js')
-
-        // Get unified auth service
-        const authService = await OCPPAuthServiceFactory.getInstance(chargingStation)
-
-        // Create auth request with unified types
-        const authResult = await authService.authorize({
-          allowOffline: false,
-          connectorId,
-          context: AuthContext.TRANSACTION_START,
-          identifier: {
-            type: IdentifierType.ID_TAG,
-            value: idTag,
-          },
-          timestamp: new Date(),
-        })
-
-        logger.debug(
-          `${chargingStation.logPrefix()} Unified auth result for idTag '${idTag}': ${authResult.status} using ${authResult.method} method`
-        )
-
-        return authResult.status === UnifiedAuthorizationStatus.ACCEPTED
-      } catch (error) {
-        logger.error(`${chargingStation.logPrefix()} Unified auth failed for OCPP 2.0`, error)
-        return false
-      }
-    case OCPPVersion.VERSION_16:
-      logger.debug(
-        `${chargingStation.logPrefix()} Using legacy auth system for idTag '${idTag}' on connector ${connectorId.toString()}`
-      )
-      return isIdTagAuthorized(chargingStation, connectorId, idTag)
-    default:
-      return false
-  }
-}
-
-/**
- * Legacy authorization function - used for OCPP 1.6 only
- * OCPP 2.0+ always uses the unified system via isIdTagAuthorizedUnified
- * @param chargingStation - The charging station instance
- * @param connectorId - The connector ID for authorization context
- * @param idTag - The identifier to authorize
- * @returns Promise resolving to authorization result
- */
-export const isIdTagAuthorized = async (
-  chargingStation: ChargingStation,
-  connectorId: number,
-  idTag: string
-): Promise<boolean> => {
-  switch (chargingStation.stationInfo?.ocppVersion) {
-    case OCPPVersion.VERSION_16: {
-      if (
-        !chargingStation.getLocalAuthListEnabled() &&
-        chargingStation.stationInfo.remoteAuthorization === false
-      ) {
-        logger.warn(
-          `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`
-        )
-      }
-      const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-      if (
-        connectorStatus != null &&
-        chargingStation.getLocalAuthListEnabled() &&
-        isIdTagLocalAuthorized(chargingStation, idTag)
-      ) {
-        connectorStatus.localAuthorizeIdTag = idTag
-        connectorStatus.idTagLocalAuthorized = true
-        return true
-      } else if (chargingStation.stationInfo.remoteAuthorization === true) {
-        return await isIdTagRemoteAuthorized(chargingStation, connectorId, idTag)
-      }
-      return false
-    }
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201:
-      return isIdTagAuthorizedUnified(chargingStation, connectorId, idTag)
-    default:
-      return false
-  }
-}
-
-const isIdTagLocalAuthorized = (chargingStation: ChargingStation, idTag: string): boolean => {
-  const idTagsFile =
-    chargingStation.stationInfo != null ? getIdTagsFile(chargingStation.stationInfo) : undefined
-  return (
-    chargingStation.hasIdTags() &&
-    idTagsFile != null &&
-    isNotEmptyString(chargingStation.idTagsCache.getIdTags(idTagsFile)?.find(tag => tag === idTag))
-  )
-}
-
-const isIdTagRemoteAuthorized = async (
-  chargingStation: ChargingStation,
-  connectorId: number,
-  idTag: string
-): Promise<boolean> => {
-  const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-  if (connectorStatus != null) {
-    connectorStatus.authorizeIdTag = idTag
-  }
-  switch (chargingStation.stationInfo?.ocppVersion) {
-    case OCPPVersion.VERSION_16:
-      return (
-        (
-          await chargingStation.ocppRequestService.requestHandler<
-            AuthorizeRequest,
-            OCPP16AuthorizeResponse
-          >(chargingStation, RequestCommand.AUTHORIZE, {
-            idTag,
-          })
-        ).idTagInfo.status === AuthorizationStatus.ACCEPTED
-      )
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201:
-      return (
-        (
-          await chargingStation.ocppRequestService.requestHandler<
-            AuthorizeRequest,
-            OCPP20AuthorizeResponse
-          >(chargingStation, RequestCommand.AUTHORIZE, {
-            idToken: { idToken: idTag, type: OCPP20IdTokenEnumType.ISO14443 },
-          })
-        ).idTokenInfo.status === AuthorizationStatus.Accepted
-      )
-    default:
-      return false
-  }
-}
-
 export const sendAndSetConnectorStatus = async (
   chargingStation: ChargingStation,
   commandParams: StatusNotificationRequest,
@@ -311,7 +150,7 @@ export const sendAndSetConnectorStatus = async (
     return
   }
   if (options.send) {
-    await checkConnectorStatusTransition(chargingStation, connectorId, status)
+    checkConnectorStatusTransition(chargingStation, connectorId, status)
     await chargingStation.ocppRequestService.requestHandler<
       StatusNotificationRequest,
       StatusNotificationResponse
@@ -623,16 +462,15 @@ export const flushQueuedTransactionMessages = async (
   }
 }
 
-const checkConnectorStatusTransition = async (
+const checkConnectorStatusTransition = (
   chargingStation: ChargingStation,
   connectorId: number,
   status: ConnectorStatusEnum
-): Promise<boolean> => {
+): boolean => {
   const fromStatus = chargingStation.getConnectorStatus(connectorId)?.status
   let transitionAllowed = false
   switch (chargingStation.stationInfo?.ocppVersion) {
     case OCPPVersion.VERSION_16: {
-      const { OCPP16Constants } = await import('./1.6/OCPP16Constants.js')
       if (
         (connectorId === 0 &&
           OCPP16Constants.ChargePointStatusChargingStationTransitions.findIndex(
@@ -649,7 +487,6 @@ const checkConnectorStatusTransition = async (
     }
     case OCPPVersion.VERSION_20:
     case OCPPVersion.VERSION_201: {
-      const { OCPP20Constants } = await import('./2.0/OCPP20Constants.js')
       if (
         (connectorId === 0 &&
           OCPP20Constants.ChargingStationStatusTransitions.findIndex(
@@ -2401,8 +2238,6 @@ const getMeasurandDefaultUnit = (
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class OCPPServiceUtils {
   public static readonly buildTransactionEndMeterValue = buildTransactionEndMeterValue
-  public static readonly isIdTagAuthorized = isIdTagAuthorized
-  public static readonly isIdTagAuthorizedUnified = isIdTagAuthorizedUnified
   public static readonly mapStopReasonToOCPP20 = mapStopReasonToOCPP20
   public static readonly restoreConnectorStatus = restoreConnectorStatus
   public static readonly sendAndSetConnectorStatus = sendAndSetConnectorStatus
index abbb05e7d68c8705b283168276bc9496184bb0d1..9ed202d6bfee169282a236d183598baedd52f876 100644 (file)
@@ -5,7 +5,7 @@ import type {
   AuthConfiguration,
   AuthorizationResult,
   AuthRequest,
-  UnifiedIdentifier,
+  Identifier,
 } from '../types/AuthTypes.js'
 
 import { getConfigurationKey } from '../../../../charging-station/ConfigurationKeyUtils.js'
@@ -33,7 +33,7 @@ const moduleName = 'OCPP16AuthAdapter'
  * OCPP 1.6 Authentication Adapter
  *
  * Handles authentication for OCPP 1.6 charging stations by translating
- * between unified auth types and OCPP 1.6 specific types and protocols.
+ * between auth types and OCPP 1.6 specific types and protocols.
  */
 export class OCPP16AuthAdapter implements OCPPAuthAdapter<string> {
   readonly ocppVersion = OCPPVersion.VERSION_16
@@ -42,13 +42,13 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter<string> {
 
   /**
    * Perform remote authorization using OCPP 1.6 Authorize message
-   * @param identifier - Unified identifier containing the idTag to authorize
+   * @param identifier - Identifier containing the idTag to authorize
    * @param connectorId - Connector ID where authorization is requested
    * @param transactionId - Active transaction ID if authorizing during a transaction
-   * @returns Authorization result with OCPP 1.6 status mapped to unified format
+   * @returns Authorization result with OCPP 1.6 status mapped to auth format
    */
   async authorizeRemote (
-    identifier: UnifiedIdentifier,
+    identifier: Identifier,
     connectorId?: number,
     transactionId?: number | string
   ): Promise<AuthorizationResult> {
@@ -75,7 +75,7 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter<string> {
         idTag: identifier.value,
       })
 
-      // Convert response to unified format
+      // Convert response to auth format
       const result: AuthorizationResult = {
         additionalInfo: {
           connectorId,
@@ -117,40 +117,22 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter<string> {
   }
 
   /**
-   * Convert unified identifier to OCPP 1.6 idTag string
-   * @param identifier - Unified identifier to convert
+   * Convert identifier to OCPP 1.6 idTag string
+   * @param identifier - Identifier to convert
    * @returns OCPP 1.6 idTag string value
    */
-  convertFromUnifiedIdentifier (identifier: UnifiedIdentifier): string {
+  convertFromIdentifier (identifier: Identifier): string {
     // For OCPP 1.6, we always return the string value
     return identifier.value
   }
 
   /**
-   * Convert unified authorization result to OCPP 1.6 response format
-   * @param result - Unified authorization result to convert
-   * @returns OCPP 1.6 AuthorizeResponse with idTagInfo structure
-   */
-  convertToOCPP16Response (result: AuthorizationResult): OCPP16AuthorizeResponse {
-    return {
-      idTagInfo: {
-        expiryDate: result.expiryDate,
-        parentIdTag: result.parentId,
-        status: mapToOCPP16Status(result.status),
-      },
-    }
-  }
-
-  /**
-   * Convert OCPP 1.6 idTag to unified identifier
+   * Convert OCPP 1.6 idTag to identifier
    * @param identifier - OCPP 1.6 idTag string to convert
-   * @param additionalData - Optional metadata to include in unified identifier
-   * @returns Unified identifier with ID_TAG type
+   * @param additionalData - Optional metadata to include in identifier
+   * @returns Identifier with ID_TAG type
    */
-  convertToUnifiedIdentifier (
-    identifier: string,
-    additionalData?: Record<string, unknown>
-  ): UnifiedIdentifier {
+  convertToIdentifier (identifier: string, additionalData?: Record<string, unknown>): Identifier {
     return {
       additionalInfo: additionalData
         ? Object.fromEntries(Object.entries(additionalData).map(([k, v]) => [k, String(v)]))
@@ -161,13 +143,28 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter<string> {
     }
   }
 
+  /**
+   * Convert authorization result to OCPP 1.6 response format
+   * @param result - Authorization result to convert
+   * @returns OCPP 1.6 AuthorizeResponse with idTagInfo structure
+   */
+  convertToOCPP16Response (result: AuthorizationResult): OCPP16AuthorizeResponse {
+    return {
+      idTagInfo: {
+        expiryDate: result.expiryDate,
+        parentIdTag: result.parentId,
+        status: mapToOCPP16Status(result.status),
+      },
+    }
+  }
+
   /**
    * Create authorization request from OCPP 1.6 context
    * @param idTag - OCPP 1.6 idTag string for authorization
    * @param connectorId - Connector where authorization is requested
    * @param transactionId - Transaction ID if in transaction context
    * @param context - Authorization context string (e.g., 'start', 'stop', 'remote_start')
-   * @returns Unified auth request with identifier and context information
+   * @returns Auth request with identifier and context information
    */
   createAuthRequest (
     idTag: string,
@@ -175,7 +172,7 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter<string> {
     transactionId?: number,
     context?: string
   ): AuthRequest {
-    const identifier = this.convertToUnifiedIdentifier(idTag)
+    const identifier = this.convertToIdentifier(idTag)
 
     // Map context string to AuthContext enum
     let authContext: AuthContext
@@ -293,10 +290,10 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter<string> {
 
   /**
    * Check if identifier is valid for OCPP 1.6
-   * @param identifier - Unified identifier to validate
+   * @param identifier - Identifier to validate
    * @returns True if identifier has valid ID_TAG type and length within OCPP 1.6 limits
    */
-  isValidIdentifier (identifier: UnifiedIdentifier): boolean {
+  isValidIdentifier (identifier: Identifier): boolean {
     // OCPP 1.6 idTag validation
     if (!identifier.value || typeof identifier.value !== 'string') {
       return false
index 78ba035debb270f003a6d92dbd479c42b9099d81..514bc9b791eae3fd02444b9078c63f27aebc0ab4 100644 (file)
@@ -11,7 +11,7 @@ import type {
   AuthConfiguration,
   AuthorizationResult,
   AuthRequest,
-  UnifiedIdentifier,
+  Identifier,
 } from '../types/AuthTypes.js'
 
 import { OCPP20VariableManager } from '../../2.0/OCPP20VariableManager.js'
@@ -31,7 +31,9 @@ import {
   AuthorizationStatus,
   IdentifierType,
   mapOCPP20AuthorizationStatus,
+  mapOCPP20TokenType,
   mapToOCPP20Status,
+  mapToOCPP20TokenType,
 } from '../types/AuthTypes.js'
 
 const moduleName = 'OCPP20AuthAdapter'
@@ -40,7 +42,7 @@ const moduleName = 'OCPP20AuthAdapter'
  * OCPP 2.0 Authentication Adapter
  *
  * Handles authentication for OCPP 2.0/2.1 charging stations by translating
- * between unified auth types and OCPP 2.0 specific types and protocols.
+ * between auth types and OCPP 2.0 specific types and protocols.
  */
 export class OCPP20AuthAdapter implements OCPPAuthAdapter {
   readonly ocppVersion = OCPPVersion.VERSION_20
@@ -49,13 +51,13 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
 
   /**
    * Perform remote authorization using OCPP 2.0 Authorize request.
-   * @param identifier - Unified identifier containing the IdToken to authorize
+   * @param identifier - Identifier containing the IdToken to authorize
    * @param connectorId - EVSE/connector ID for the authorization context
    * @param transactionId - Optional existing transaction ID for ongoing transactions
    * @returns Authorization result with status, method, and OCPP 2.0 specific metadata
    */
   async authorizeRemote (
-    identifier: UnifiedIdentifier,
+    identifier: Identifier,
     connectorId?: number,
     transactionId?: number | string
   ): Promise<AuthorizationResult> {
@@ -83,7 +85,7 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
       }
 
       try {
-        const idToken = this.convertFromUnifiedIdentifier(identifier)
+        const idToken = this.convertFromIdentifier(identifier)
 
         // Validate token format
         const isValidToken = this.isValidIdentifier(identifier)
@@ -117,11 +119,11 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
         const authStatus = response.idTokenInfo.status
         const cacheExpiryDateTime = response.idTokenInfo.cacheExpiryDateTime
 
-        // Map OCPP 2.0 authorization status to unified status
-        const unifiedStatus = mapOCPP20AuthorizationStatus(authStatus)
+        // Map OCPP 2.0 authorization status
+        const mappedStatus = mapOCPP20AuthorizationStatus(authStatus)
 
         logger.debug(
-          `${this.chargingStation.logPrefix()} ${moduleName}.${methodName}: Authorization result for ${idToken.idToken}: ${authStatus} (unified: ${unifiedStatus})`
+          `${this.chargingStation.logPrefix()} ${moduleName}.${methodName}: Authorization result for ${idToken.idToken}: ${authStatus} (mapped: ${mappedStatus})`
         )
 
         return {
@@ -135,7 +137,7 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
           },
           isOffline: false,
           method: AuthenticationMethod.REMOTE_AUTHORIZATION,
-          status: unifiedStatus,
+          status: mappedStatus,
           timestamp: new Date(),
         }
       } catch (error) {
@@ -177,15 +179,15 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
   }
 
   /**
-   * Convert unified identifier to OCPP 2.0 IdToken
-   * @param identifier - Unified identifier to convert to OCPP 2.0 format
+   * Convert identifier to OCPP 2.0 IdToken
+   * @param identifier - Identifier to convert to OCPP 2.0 format
    * @returns OCPP 2.0 IdTokenType with mapped type and additionalInfo
    */
-  convertFromUnifiedIdentifier (identifier: UnifiedIdentifier): OCPP20IdTokenType {
-    // Map unified type back to OCPP 2.0 type
-    const ocpp20Type = this.mapFromUnifiedIdentifierType(identifier.type)
+  convertFromIdentifier (identifier: Identifier): OCPP20IdTokenType {
+    // Map type back to OCPP 2.0 type
+    const ocpp20Type = mapToOCPP20TokenType(identifier.type)
 
-    // Convert unified additionalInfo back to OCPP 2.0 format
+    // Convert additionalInfo back to OCPP 2.0 format
     const additionalInfo: AdditionalInfoType[] | undefined = identifier.additionalInfo
       ? Object.entries(identifier.additionalInfo)
         .filter(([key]) => key.startsWith('info_'))
@@ -210,24 +212,15 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
   }
 
   /**
-   * Convert unified authorization result to OCPP 2.0 response format
-   * @param result - Unified authorization result to convert
-   * @returns OCPP 2.0 RequestStartStopStatusEnumType for transaction responses
-   */
-  convertToOCPP20Response (result: AuthorizationResult): RequestStartStopStatusEnumType {
-    return mapToOCPP20Status(result.status)
-  }
-
-  /**
-   * Convert OCPP 2.0 IdToken to unified identifier
+   * Convert OCPP 2.0 IdToken to identifier
    * @param identifier - OCPP 2.0 IdToken or raw string identifier
-   * @param additionalData - Optional metadata to include in the unified identifier
-   * @returns Unified identifier with normalized type and metadata
+   * @param additionalData - Optional metadata to include in the identifier
+   * @returns Identifier with normalized type and metadata
    */
-  convertToUnifiedIdentifier (
+  convertToIdentifier (
     identifier: OCPP20IdTokenType | string,
     additionalData?: Record<string, unknown>
-  ): UnifiedIdentifier {
+  ): Identifier {
     let idToken: OCPP20IdTokenType
 
     // Handle both string and object formats
@@ -241,8 +234,7 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
       idToken = identifier
     }
 
-    // Map OCPP 2.0 IdToken type to unified type
-    const unifiedType = this.mapToUnifiedIdentifierType(idToken.type)
+    const identifierType = mapOCPP20TokenType(idToken.type)
 
     return {
       additionalInfo: {
@@ -260,18 +252,27 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
           : {}),
       },
       parentId: additionalData?.parentId as string | undefined,
-      type: unifiedType,
+      type: identifierType,
       value: idToken.idToken,
     }
   }
 
+  /**
+   * Convert authorization result to OCPP 2.0 response format
+   * @param result - Authorization result to convert
+   * @returns OCPP 2.0 RequestStartStopStatusEnumType for transaction responses
+   */
+  convertToOCPP20Response (result: AuthorizationResult): RequestStartStopStatusEnumType {
+    return mapToOCPP20Status(result.status)
+  }
+
   /**
    * Create authorization request from OCPP 2.0 context
    * @param idTokenOrString - OCPP 2.0 IdToken or raw string identifier
    * @param connectorId - Optional EVSE/connector ID for the request
    * @param transactionId - Optional transaction ID for ongoing transactions
    * @param context - Optional context string (e.g., 'start', 'stop', 'remote_start')
-   * @returns AuthRequest with unified identifier, context, and station metadata
+   * @returns AuthRequest with identifier, context, and station metadata
    */
   createAuthRequest (
     idTokenOrString: OCPP20IdTokenType | string,
@@ -279,7 +280,7 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
     transactionId?: string,
     context?: string
   ): AuthRequest {
-    const identifier = this.convertToUnifiedIdentifier(idTokenOrString)
+    const identifier = this.convertToIdentifier(idTokenOrString)
 
     // Map context string to AuthContext enum
     let authContext: AuthContext
@@ -415,10 +416,10 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
 
   /**
    * Check if identifier is valid for OCPP 2.0
-   * @param identifier - Unified identifier to validate against OCPP 2.0 rules
+   * @param identifier - Identifier to validate against OCPP 2.0 rules
    * @returns True if identifier meets OCPP 2.0 format requirements (max 36 chars, valid type)
    */
-  isValidIdentifier (identifier: UnifiedIdentifier): boolean {
+  isValidIdentifier (identifier: Identifier): boolean {
     // OCPP 2.0 idToken validation
     if (!identifier.value || typeof identifier.value !== 'string') {
       return false
@@ -599,63 +600,6 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
     }
   }
 
-  /**
-   * Map unified identifier type to OCPP 2.0 IdToken type
-   * @param unifiedType - Unified identifier type to convert
-   * @returns Corresponding OCPP 2.0 IdTokenEnumType value
-   */
-  private mapFromUnifiedIdentifierType (unifiedType: IdentifierType): OCPP20IdTokenEnumType {
-    switch (unifiedType) {
-      case IdentifierType.CENTRAL:
-        return OCPP20IdTokenEnumType.Central
-      case IdentifierType.E_MAID:
-        return OCPP20IdTokenEnumType.eMAID
-      case IdentifierType.ID_TAG:
-        return OCPP20IdTokenEnumType.Local
-      case IdentifierType.ISO14443:
-        return OCPP20IdTokenEnumType.ISO14443
-      case IdentifierType.ISO15693:
-        return OCPP20IdTokenEnumType.ISO15693
-      case IdentifierType.KEY_CODE:
-        return OCPP20IdTokenEnumType.KeyCode
-      case IdentifierType.LOCAL:
-        return OCPP20IdTokenEnumType.Local
-      case IdentifierType.MAC_ADDRESS:
-        return OCPP20IdTokenEnumType.MacAddress
-      case IdentifierType.NO_AUTHORIZATION:
-        return OCPP20IdTokenEnumType.NoAuthorization
-      default:
-        return OCPP20IdTokenEnumType.Central
-    }
-  }
-
-  /**
-   * Map OCPP 2.0 IdToken type to unified identifier type
-   * @param ocpp20Type - OCPP 2.0 IdTokenEnumType to convert
-   * @returns Corresponding unified IdentifierType value
-   */
-  private mapToUnifiedIdentifierType (ocpp20Type: OCPP20IdTokenEnumType): IdentifierType {
-    switch (ocpp20Type) {
-      case OCPP20IdTokenEnumType.Central:
-      case OCPP20IdTokenEnumType.Local:
-        return IdentifierType.ID_TAG
-      case OCPP20IdTokenEnumType.eMAID:
-        return IdentifierType.E_MAID
-      case OCPP20IdTokenEnumType.ISO14443:
-        return IdentifierType.ISO14443
-      case OCPP20IdTokenEnumType.ISO15693:
-        return IdentifierType.ISO15693
-      case OCPP20IdTokenEnumType.KeyCode:
-        return IdentifierType.KEY_CODE
-      case OCPP20IdTokenEnumType.MacAddress:
-        return IdentifierType.MAC_ADDRESS
-      case OCPP20IdTokenEnumType.NoAuthorization:
-        return IdentifierType.NO_AUTHORIZATION
-      default:
-        return IdentifierType.ID_TAG
-    }
-  }
-
   /**
    * Parse and validate a boolean variable value
    * @param value - String value to parse ('true', 'false', '1', '0')
@@ -682,49 +626,4 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter {
     )
     return defaultValue
   }
-
-  /**
-   * Parse and validate an integer variable value
-   * @param value - String value to parse as integer
-   * @param defaultValue - Fallback value when parsing fails or value is undefined
-   * @param min - Optional minimum allowed value (clamped if exceeded)
-   * @param max - Optional maximum allowed value (clamped if exceeded)
-   * @returns Parsed integer value clamped to min/max bounds, or defaultValue if parsing fails
-   */
-  private parseIntegerVariable (
-    value: string | undefined,
-    defaultValue: number,
-    min?: number,
-    max?: number
-  ): number {
-    if (value == null) {
-      return defaultValue
-    }
-
-    const parsed = parseInt(value, 10)
-
-    if (Number.isNaN(parsed)) {
-      logger.warn(
-        `${this.chargingStation.logPrefix()} ${moduleName}.parseIntegerVariable: Invalid integer value '${value}', using default: ${defaultValue.toString()}`
-      )
-      return defaultValue
-    }
-
-    // Validate range
-    if (min != null && parsed < min) {
-      logger.warn(
-        `${this.chargingStation.logPrefix()} ${moduleName}.parseIntegerVariable: Integer value ${parsed.toString()} below minimum ${min.toString()}, using minimum`
-      )
-      return min
-    }
-
-    if (max != null && parsed > max) {
-      logger.warn(
-        `${this.chargingStation.logPrefix()} ${moduleName}.parseIntegerVariable: Integer value ${parsed.toString()} above maximum ${max.toString()}, using maximum`
-      )
-      return max
-    }
-
-    return parsed
-  }
 }
index ff1cdb691b7284885c6d27691d997508153816bc..a92421512e2946dc8b366655e96bc69e0d5d7b7d 100644 (file)
@@ -1,4 +1,4 @@
-import type { ChargingStation } from '../../../ChargingStation.js'
+import type { ChargingStation } from '../../../index.js'
 import type {
   AuthCache,
   AuthStrategy,
@@ -9,7 +9,12 @@ import type { AuthConfiguration } from '../types/AuthTypes.js'
 
 import { OCPPError } from '../../../../exception/index.js'
 import { ErrorType, OCPPVersion } from '../../../../types/index.js'
+import { OCPP16AuthAdapter } from '../adapters/OCPP16AuthAdapter.js'
+import { OCPP20AuthAdapter } from '../adapters/OCPP20AuthAdapter.js'
 import { InMemoryAuthCache } from '../cache/InMemoryAuthCache.js'
+import { CertificateAuthStrategy } from '../strategies/CertificateAuthStrategy.js'
+import { LocalAuthStrategy } from '../strategies/LocalAuthStrategy.js'
+import { RemoteAuthStrategy } from '../strategies/RemoteAuthStrategy.js'
 import { AuthConfigValidator } from '../utils/ConfigValidator.js'
 
 /**
@@ -36,7 +41,7 @@ export class AuthComponentFactory {
    * @returns Single version-specific adapter (OCPP 1.6 or 2.0.x)
    * @throws {Error} When OCPP version is not found or unsupported
    */
-  static async createAdapter (chargingStation: ChargingStation): Promise<OCPPAuthAdapter> {
+  static createAdapter (chargingStation: ChargingStation): OCPPAuthAdapter {
     const ocppVersion = chargingStation.stationInfo?.ocppVersion
 
     if (!ocppVersion) {
@@ -44,17 +49,11 @@ export class AuthComponentFactory {
     }
 
     switch (ocppVersion) {
-      case OCPPVersion.VERSION_16: {
-        // Use static import - circular dependency is acceptable here
-        const { OCPP16AuthAdapter } = await import('../adapters/OCPP16AuthAdapter.js')
+      case OCPPVersion.VERSION_16:
         return new OCPP16AuthAdapter(chargingStation)
-      }
       case OCPPVersion.VERSION_20:
-      case OCPPVersion.VERSION_201: {
-        // Use static import - circular dependency is acceptable here
-        const { OCPP20AuthAdapter } = await import('../adapters/OCPP20AuthAdapter.js')
+      case OCPPVersion.VERSION_201:
         return new OCPP20AuthAdapter(chargingStation)
-      }
       default:
         throw new OCPPError(
           ErrorType.INTERNAL_ERROR,
@@ -82,13 +81,11 @@ export class AuthComponentFactory {
    * @param config - Authentication configuration with certificate settings
    * @returns Initialized certificate-based authentication strategy
    */
-  static async createCertificateStrategy (
+  static createCertificateStrategy (
     chargingStation: ChargingStation,
     adapter: OCPPAuthAdapter,
     config: AuthConfiguration
-  ): Promise<AuthStrategy> {
-    // Use static import - circular dependency is acceptable here
-    const { CertificateAuthStrategy } = await import('../strategies/CertificateAuthStrategy.js')
+  ): AuthStrategy {
     const strategy = new CertificateAuthStrategy(chargingStation, adapter)
     strategy.initialize(config)
     return strategy
@@ -121,11 +118,11 @@ export class AuthComponentFactory {
    * @param config - Authentication configuration controlling local auth behavior
    * @returns Local strategy instance or undefined if local auth disabled
    */
-  static async createLocalStrategy (
+  static createLocalStrategy (
     manager: LocalAuthListManager | undefined,
     cache: AuthCache | undefined,
     config: AuthConfiguration
-  ): Promise<AuthStrategy | undefined> {
+  ): AuthStrategy | undefined {
     if (
       !config.localAuthListEnabled &&
       !config.authorizationCacheEnabled &&
@@ -134,8 +131,6 @@ export class AuthComponentFactory {
       return undefined
     }
 
-    // Use static import - circular dependency is acceptable here
-    const { LocalAuthStrategy } = await import('../strategies/LocalAuthStrategy.js')
     const strategy = new LocalAuthStrategy(manager, cache)
     strategy.initialize(config)
     return strategy
@@ -149,18 +144,16 @@ export class AuthComponentFactory {
    * @param localAuthListManager - Optional local auth list manager for C13.FR.01 cache exclusion
    * @returns Remote strategy instance or undefined if remote auth disabled
    */
-  static async createRemoteStrategy (
+  static createRemoteStrategy (
     adapter: OCPPAuthAdapter,
     cache: AuthCache | undefined,
     config: AuthConfiguration,
     localAuthListManager?: LocalAuthListManager
-  ): Promise<AuthStrategy | undefined> {
+  ): AuthStrategy | undefined {
     if (!config.remoteAuthorization) {
       return undefined
     }
 
-    // Use static import - circular dependency is acceptable here
-    const { RemoteAuthStrategy } = await import('../strategies/RemoteAuthStrategy.js')
     const strategy = new RemoteAuthStrategy(adapter, cache, localAuthListManager)
     strategy.initialize(config)
     return strategy
@@ -175,29 +168,29 @@ export class AuthComponentFactory {
    * @param config - Authentication configuration controlling strategy creation
    * @returns Array of initialized strategies sorted by priority (lowest first)
    */
-  static async createStrategies (
+  static createStrategies (
     chargingStation: ChargingStation,
     adapter: OCPPAuthAdapter,
     manager: LocalAuthListManager | undefined,
     cache: AuthCache | undefined,
     config: AuthConfiguration
-  ): Promise<AuthStrategy[]> {
+  ): AuthStrategy[] {
     const strategies: AuthStrategy[] = []
 
     // Add local strategy if enabled
-    const localStrategy = await this.createLocalStrategy(manager, cache, config)
+    const localStrategy = this.createLocalStrategy(manager, cache, config)
     if (localStrategy) {
       strategies.push(localStrategy)
     }
 
     // Add remote strategy if enabled
-    const remoteStrategy = await this.createRemoteStrategy(adapter, cache, config, manager)
+    const remoteStrategy = this.createRemoteStrategy(adapter, cache, config, manager)
     if (remoteStrategy) {
       strategies.push(remoteStrategy)
     }
 
     // Always add certificate strategy
-    const certStrategy = await this.createCertificateStrategy(chargingStation, adapter, config)
+    const certStrategy = this.createCertificateStrategy(chargingStation, adapter, config)
     strategies.push(certStrategy)
 
     // Sort by priority
index 57a79faab4a65907a8f792628fe9b8bf4b870be0..b6aa93eefddb0cdccf5b32ab32be7574a782f63e 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * OCPP Authentication System
  *
- * Unified authentication layer for OCPP 1.6 and 2.0 protocols.
+ * Authentication layer for OCPP 1.6 and 2.0 protocols.
  * This module provides a consistent API for handling authentication
  * across different OCPP versions, with support for multiple authentication
  * strategies including local lists, remote authorization, and certificate-based auth.
@@ -21,7 +21,7 @@ export { OCPP16AuthAdapter } from './adapters/OCPP16AuthAdapter.js'
 export { OCPP20AuthAdapter } from './adapters/OCPP20AuthAdapter.js'
 
 // ============================================================================
-// Type Guards & Mappers (Pure Functions)
+// Adapters
 // ============================================================================
 
 export type {
@@ -37,26 +37,21 @@ export type {
   OCPPAuthAdapter,
   OCPPAuthService,
 } from './interfaces/OCPPAuthService.js'
-
-// ============================================================================
-// Adapters
-// ============================================================================
-
 export { OCPPAuthServiceFactory } from './services/OCPPAuthServiceFactory.js'
-export { OCPPAuthServiceImpl } from './services/OCPPAuthServiceImpl.js'
 
 // ============================================================================
 // Strategies
 // ============================================================================
 
+export { OCPPAuthServiceImpl } from './services/OCPPAuthServiceImpl.js'
 export { CertificateAuthStrategy } from './strategies/CertificateAuthStrategy.js'
 export { LocalAuthStrategy } from './strategies/LocalAuthStrategy.js'
-export { RemoteAuthStrategy } from './strategies/RemoteAuthStrategy.js'
 
 // ============================================================================
 // Services
 // ============================================================================
 
+export { RemoteAuthStrategy } from './strategies/RemoteAuthStrategy.js'
 export {
   type AuthConfiguration,
   AuthContext,
@@ -67,6 +62,7 @@ export {
   AuthorizationStatus,
   type AuthRequest,
   type CertificateHashData,
+  type Identifier,
   IdentifierType,
   isCertificateBased,
   isOCPP16Type,
@@ -78,7 +74,6 @@ export {
   mapToOCPP20Status,
   mapToOCPP20TokenType,
   requiresAdditionalInfo,
-  type UnifiedIdentifier,
 } from './types/AuthTypes.js'
 
 // ============================================================================
index b6e5bc989b4306ce616aa29bea21d2216e2243ea..35536482cbc9d6ba4e6a7f3dedabe0beb22619a2 100644 (file)
@@ -8,7 +8,7 @@ import type {
   AuthConfiguration,
   AuthorizationResult,
   AuthRequest,
-  UnifiedIdentifier,
+  Identifier,
 } from '../types/AuthTypes.js'
 import type { IdentifierType } from '../types/AuthTypes.js'
 
@@ -156,6 +156,12 @@ export interface AuthStrategy {
    */
   configure?(config: Partial<AuthConfiguration>): Promise<void>
 
+  /**
+   * Get the authorization cache if available
+   * @returns The authorization cache, or undefined if not available
+   */
+  getAuthCache?(): AuthCache | undefined
+
   /**
    * Get strategy-specific statistics
    */
@@ -165,7 +171,7 @@ export interface AuthStrategy {
    * Initialize the strategy with configuration
    * @param config - Authentication configuration
    */
-  initialize(config: AuthConfiguration): Promise<void> | void
+  initialize(config: AuthConfiguration): void
 
   /**
    * Strategy name for identification
@@ -324,40 +330,37 @@ export interface LocalAuthListManager {
 /**
  * OCPP version-specific adapter interface
  *
- * Adapters handle the translation between unified auth types
+ * Adapters handle the translation between auth types
  * and version-specific OCPP types and protocols.
  */
 export interface OCPPAuthAdapter<TVersionId = OCPP20IdTokenType | string> {
   /**
    * Perform remote authorization using version-specific protocol
-   * @param identifier - Unified identifier to authorize
+   * @param identifier - Identifier to authorize
    * @param connectorId - Optional connector ID
    * @param transactionId - Optional transaction ID for stop auth
    * @returns Promise resolving to authorization result
    */
   authorizeRemote(
-    identifier: UnifiedIdentifier,
+    identifier: Identifier,
     connectorId?: number,
     transactionId?: number | string
   ): Promise<AuthorizationResult>
 
   /**
-   * Convert unified identifier to version-specific format
-   * @param identifier - Unified identifier
+   * Convert identifier to version-specific format
+   * @param identifier - Identifier
    * @returns Version-specific identifier
    */
-  convertFromUnifiedIdentifier(identifier: UnifiedIdentifier): TVersionId
+  convertFromIdentifier(identifier: Identifier): TVersionId
 
   /**
-   * Convert a version-specific identifier to unified format
+   * Convert a version-specific identifier to common format
    * @param identifier - Version-specific identifier
    * @param additionalData - Optional additional context data
-   * @returns Unified identifier
+   * @returns Identifier
    */
-  convertToUnifiedIdentifier(
-    identifier: TVersionId,
-    additionalData?: Record<string, unknown>
-  ): UnifiedIdentifier
+  convertToIdentifier(identifier: TVersionId, additionalData?: Record<string, unknown>): Identifier
 
   /**
    * Get adapter-specific configuration requirements
@@ -383,7 +386,7 @@ export interface OCPPAuthAdapter<TVersionId = OCPP20IdTokenType | string> {
 /**
  * Main OCPP Authentication Service interface
  *
- * This is the primary interface that provides unified authentication
+ * This is the primary interface that provides authentication
  * capabilities across different OCPP versions and strategies.
  */
 export interface OCPPAuthService {
@@ -413,7 +416,7 @@ export interface OCPPAuthService {
    * Invalidate cached authorization for an identifier
    * @param identifier - Identifier to invalidate
    */
-  invalidateCache(identifier: UnifiedIdentifier): void
+  invalidateCache(identifier: Identifier): void
 
   /**
    * Check if an identifier is locally authorized (cache/local list)
@@ -422,7 +425,7 @@ export interface OCPPAuthService {
    * @returns Promise resolving to local authorization result, undefined if not found
    */
   isLocallyAuthorized(
-    identifier: UnifiedIdentifier,
+    identifier: Identifier,
     connectorId?: number
   ): Promise<AuthorizationResult | undefined>
 
index f3a9272f3fae93260e2fc2d616614a89659beaec..4cde25b23ad27c47d7b3266fbae37008dfd23a59 100644 (file)
@@ -1,4 +1,4 @@
-import type { ChargingStation } from '../../../ChargingStation.js'
+import type { ChargingStation } from '../../../index.js'
 import type { OCPPAuthService } from '../interfaces/OCPPAuthService.js'
 
 import { OCPPError } from '../../../../exception/index.js'
@@ -8,26 +8,6 @@ import { OCPPAuthServiceImpl } from './OCPPAuthServiceImpl.js'
 
 const moduleName = 'OCPPAuthServiceFactory'
 
-/**
- * Global symbol key for sharing auth service instances across module boundaries.
- * This is required because dynamic imports (used in OCPPServiceUtils) create
- * separate module instances, breaking test mock injection.
- * Using globalThis ensures the same Map is shared regardless of import method.
- */
-const INSTANCES_KEY = Symbol.for('OCPPAuthServiceFactory.instances')
-
-/**
- * Get or create the shared instances Map.
- * Uses globalThis to ensure the same Map is used across all module instances,
- * which is critical for test mock injection to work with dynamic imports.
- * @returns The shared instances Map for OCPPAuthService
- */
-const getSharedInstances = (): Map<string, OCPPAuthService> => {
-  const globalAny = globalThis as Record<symbol, Map<string, OCPPAuthService> | undefined>
-  globalAny[INSTANCES_KEY] ??= new Map<string, OCPPAuthService>()
-  return globalAny[INSTANCES_KEY]
-}
-
 /**
  * Factory for creating OCPP Authentication Services with proper adapter configuration
  *
@@ -37,9 +17,7 @@ const getSharedInstances = (): Map<string, OCPPAuthService> => {
  */
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class OCPPAuthServiceFactory {
-  private static get instances (): Map<string, OCPPAuthService> {
-    return getSharedInstances()
-  }
+  private static readonly instances = new Map<string, OCPPAuthService>()
 
   /**
    * Clear all cached instances
@@ -72,13 +50,13 @@ export class OCPPAuthServiceFactory {
    * @param chargingStation - The charging station to create the service for
    * @returns New OCPPAuthService instance (initialized)
    */
-  static async createInstance (chargingStation: ChargingStation): Promise<OCPPAuthService> {
+  static createInstance (chargingStation: ChargingStation): OCPPAuthService {
     logger.debug(
       `${chargingStation.logPrefix()} ${moduleName}.createInstance: Creating new uncached auth service`
     )
 
     const authService = new OCPPAuthServiceImpl(chargingStation)
-    await authService.initialize()
+    authService.initialize()
 
     return authService
   }
@@ -96,7 +74,7 @@ export class OCPPAuthServiceFactory {
    * @param chargingStation - The charging station to create the service for
    * @returns Configured OCPPAuthService instance (initialized)
    */
-  static async getInstance (chargingStation: ChargingStation): Promise<OCPPAuthService> {
+  static getInstance (chargingStation: ChargingStation): OCPPAuthService {
     const stationId = chargingStation.stationInfo?.chargingStationId ?? 'unknown'
 
     // Return existing instance if available
@@ -120,7 +98,7 @@ export class OCPPAuthServiceFactory {
     )
 
     const authService = new OCPPAuthServiceImpl(chargingStation)
-    await authService.initialize()
+    authService.initialize()
 
     // Cache the instance
     this.instances.set(stationId, authService)
index 58e87650a24d3741916d5f946d9d2f00baa7804c..26672e0eeca48bf33a4cd9be80eda53b40d2db71 100644 (file)
@@ -1,6 +1,5 @@
 import type { OCPP20IdTokenInfoType } from '../../../../types/index.js'
 import type { OCPPAuthAdapter } from '../interfaces/OCPPAuthService.js'
-import type { LocalAuthStrategy } from '../strategies/LocalAuthStrategy.js'
 
 import { OCPPError } from '../../../../exception/index.js'
 import { ErrorType, OCPPVersion } from '../../../../types/index.js'
@@ -11,7 +10,7 @@ import {
   logger,
   truncateId,
 } from '../../../../utils/index.js'
-import { type ChargingStation } from '../../../ChargingStation.js'
+import { type ChargingStation } from '../../../index.js'
 import { AuthComponentFactory } from '../factories/AuthComponentFactory.js'
 import {
   type AuthStats,
@@ -25,9 +24,9 @@ import {
   type AuthorizationResult,
   AuthorizationStatus,
   type AuthRequest,
+  type Identifier,
   IdentifierType,
   mapOCPP20AuthorizationStatus,
-  type UnifiedIdentifier,
 } from '../types/AuthTypes.js'
 import { AuthConfigValidator } from '../utils/ConfigValidator.js'
 
@@ -280,8 +279,8 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
     )
 
     // Clear cache in local strategy
-    const localStrategy = this.strategies.get('local') as LocalAuthStrategy | undefined
-    const localAuthCache = localStrategy?.getAuthCache()
+    const localStrategy = this.strategies.get('local')
+    const localAuthCache = localStrategy?.getAuthCache?.()
     if (localAuthCache) {
       localAuthCache.clear()
       logger.info(
@@ -308,7 +307,7 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
     const supportedTypes = new Set<string>()
 
     // Test common identifier types
-    const testIdentifiers: UnifiedIdentifier[] = [
+    const testIdentifiers: Identifier[] = [
       { type: IdentifierType.ISO14443, value: 'test' },
       { type: IdentifierType.ISO15693, value: 'test' },
       { type: IdentifierType.KEY_CODE, value: 'test' },
@@ -413,24 +412,25 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
    * Async initialization of adapters and strategies
    * Must be called after construction
    */
-  public async initialize (): Promise<void> {
-    await this.initializeAdapter()
-    await this.initializeStrategies()
+  public initialize (): void {
+    this.initializeAdapter()
+    this.initializeStrategies()
   }
 
   /**
    * Invalidate cached authorization for an identifier
-   * @param identifier - Unified identifier whose cached authorization should be invalidated
+   * @param identifier - Identifier whose cached authorization should be invalidated
    */
-  public invalidateCache (identifier: UnifiedIdentifier): void {
+  public invalidateCache (identifier: Identifier): void {
     logger.debug(
       `${this.chargingStation.logPrefix()} ${moduleName}.invalidateCache: Invalidating cache for identifier: ${truncateId(identifier.value)}`
     )
 
     // Invalidate in local strategy
-    const localStrategy = this.strategies.get('local') as LocalAuthStrategy | undefined
-    if (localStrategy) {
-      localStrategy.invalidateCache(identifier.value)
+    const localStrategy = this.strategies.get('local')
+    const localAuthCache = localStrategy?.getAuthCache?.()
+    if (localAuthCache) {
+      localAuthCache.remove(identifier.value)
       logger.info(
         `${this.chargingStation.logPrefix()} ${moduleName}.invalidateCache: Cache invalidated for identifier: ${truncateId(identifier.value)}`
       )
@@ -443,12 +443,12 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
 
   /**
    * Check if an identifier is locally authorized (cache/local list)
-   * @param identifier - Unified identifier to check for local authorization
+   * @param identifier - Identifier to check for local authorization
    * @param connectorId - Optional connector ID for context-specific authorization
    * @returns Promise resolving to the authorization result if locally authorized, or undefined if not found
    */
   public async isLocallyAuthorized (
-    identifier: UnifiedIdentifier,
+    identifier: Identifier,
     connectorId?: number
   ): Promise<AuthorizationResult | undefined> {
     // Try local strategy first for quick cache/list lookup
@@ -480,10 +480,10 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
 
   /**
    * Check if authentication is supported for given identifier type
-   * @param identifier - Unified identifier to check for support
+   * @param identifier - Identifier to check for support
    * @returns True if at least one strategy can handle the identifier type, false otherwise
    */
-  public isSupported (identifier: UnifiedIdentifier): boolean {
+  public isSupported (identifier: Identifier): boolean {
     // Create a minimal request to check applicability
     const testRequest: AuthRequest = {
       allowOffline: false,
@@ -542,8 +542,8 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
       return
     }
 
-    const localStrategy = this.strategies.get('local') as LocalAuthStrategy | undefined
-    const authCache = localStrategy?.getAuthCache()
+    const localStrategy = this.strategies.get('local')
+    const authCache = localStrategy?.getAuthCache?.()
     if (authCache == null) {
       logger.debug(
         `${this.chargingStation.logPrefix()} ${moduleName}.updateCacheEntry: No auth cache available`
@@ -551,12 +551,12 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
       return
     }
 
-    const unifiedStatus = mapOCPP20AuthorizationStatus(idTokenInfo.status)
+    const mappedStatus = mapOCPP20AuthorizationStatus(idTokenInfo.status)
 
     const result: AuthorizationResult = {
       isOffline: false,
       method: AuthenticationMethod.REMOTE_AUTHORIZATION,
-      status: unifiedStatus,
+      status: mappedStatus,
       timestamp: new Date(),
     }
 
@@ -576,7 +576,7 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
     authCache.set(identifier, result, ttl)
 
     logger.debug(
-      `${this.chargingStation.logPrefix()} ${moduleName}.updateCacheEntry: Updated cache for ${truncateId(identifier)} status=${unifiedStatus}, ttl=${ttl != null ? ttl.toString() : 'default'}s`
+      `${this.chargingStation.logPrefix()} ${moduleName}.updateCacheEntry: Updated cache for ${truncateId(identifier)} status=${mappedStatus}, ttl=${ttl != null ? ttl.toString() : 'default'}s`
     )
   }
 
@@ -665,14 +665,14 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
   /**
    * Initialize OCPP adapter using AuthComponentFactory
    */
-  private async initializeAdapter (): Promise<void> {
-    this.adapter = await AuthComponentFactory.createAdapter(this.chargingStation)
+  private initializeAdapter (): void {
+    this.adapter = AuthComponentFactory.createAdapter(this.chargingStation)
   }
 
   /**
    * Initialize all authentication strategies using AuthComponentFactory
    */
-  private async initializeStrategies (): Promise<void> {
+  private initializeStrategies (): void {
     const ocppVersion = this.chargingStation.stationInfo?.ocppVersion
 
     if (this.adapter == null) {
@@ -683,7 +683,7 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
     const authCache = AuthComponentFactory.createAuthCache(this.config)
 
     // Create strategies using factory
-    const strategies = await AuthComponentFactory.createStrategies(
+    const strategies = AuthComponentFactory.createStrategies(
       this.chargingStation,
       this.adapter,
       undefined, // manager - delegated to OCPPAuthServiceImpl
index a8482a94fb15dd23d68e43f88a9a75cf85851111..7452a93fda5002207e98ae63e39823ffff049ec0 100644 (file)
@@ -1,11 +1,11 @@
 import type { JsonObject } from '../../../../types/index.js'
-import type { ChargingStation } from '../../../ChargingStation.js'
+import type { ChargingStation } from '../../../index.js'
 import type { AuthStrategy, OCPPAuthAdapter } from '../interfaces/OCPPAuthService.js'
 import type {
   AuthConfiguration,
   AuthorizationResult,
   AuthRequest,
-  UnifiedIdentifier,
+  Identifier,
 } from '../types/AuthTypes.js'
 
 import { OCPPVersion } from '../../../../types/index.js'
@@ -151,10 +151,10 @@ export class CertificateAuthStrategy implements AuthStrategy {
 
   /**
    * Calculate certificate expiry information
-   * @param identifier - Unified identifier containing certificate hash data
+   * @param identifier - Identifier containing certificate hash data
    * @returns Expiry date extracted from certificate, or undefined if not determinable
    */
-  private calculateCertificateExpiry (identifier: UnifiedIdentifier): Date | undefined {
+  private calculateCertificateExpiry (identifier: Identifier): Date | undefined {
     // In a real implementation, this would parse the actual certificate
     // and extract the notAfter field. For simulation, we'll use a placeholder.
 
@@ -175,14 +175,14 @@ export class CertificateAuthStrategy implements AuthStrategy {
    * Create a failure result with consistent format
    * @param status - Authorization status indicating the failure type
    * @param reason - Human-readable description of why authorization failed
-   * @param identifier - Unified identifier from the original request
+   * @param identifier - Identifier from the original request
    * @param startTime - Request start timestamp for response time calculation
    * @returns Authorization result with failure status and diagnostic information
    */
   private createFailureResult (
     status: AuthorizationStatus,
     reason: string,
-    identifier: UnifiedIdentifier,
+    identifier: Identifier,
     startTime: number
   ): AuthorizationResult {
     const result: AuthorizationResult = {
@@ -203,10 +203,10 @@ export class CertificateAuthStrategy implements AuthStrategy {
 
   /**
    * Check if the identifier contains certificate data
-   * @param identifier - Unified identifier to check for certificate hash data
+   * @param identifier - Identifier to check for certificate hash data
    * @returns True if all required certificate hash fields are present and non-empty
    */
-  private hasCertificateData (identifier: UnifiedIdentifier): boolean {
+  private hasCertificateData (identifier: Identifier): boolean {
     const certData = identifier.certificateHashData
     if (!certData) return false
 
@@ -289,10 +289,10 @@ export class CertificateAuthStrategy implements AuthStrategy {
 
   /**
    * Validate certificate data structure and content
-   * @param identifier - Unified identifier containing certificate hash data to validate
+   * @param identifier - Identifier containing certificate hash data to validate
    * @returns Validation result with isValid flag and optional reason on failure
    */
-  private validateCertificateData (identifier: UnifiedIdentifier): {
+  private validateCertificateData (identifier: Identifier): {
     isValid: boolean
     reason?: string
   } {
index 3f0b7bcace5c9b08f2777e2550f9f692a2d3a54d..b7f071f5c73d172471cc0fd9bf114e8db144a006 100644 (file)
@@ -13,6 +13,7 @@ import {
   AuthenticationMethod,
   AuthErrorCode,
   AuthorizationStatus,
+  enhanceAuthResult,
 } from '../types/AuthTypes.js'
 
 const moduleName = 'LocalAuthStrategy'
@@ -87,7 +88,12 @@ export class LocalAuthStrategy implements AuthStrategy {
             )
             return undefined
           }
-          return this.enhanceResult(localResult, AuthenticationMethod.LOCAL_LIST, startTime)
+          return enhanceAuthResult(
+            localResult,
+            AuthenticationMethod.LOCAL_LIST,
+            this.name,
+            startTime
+          )
         }
       }
 
@@ -104,7 +110,7 @@ export class LocalAuthStrategy implements AuthStrategy {
             )
             return undefined
           }
-          return this.enhanceResult(cacheResult, AuthenticationMethod.CACHE, startTime)
+          return enhanceAuthResult(cacheResult, AuthenticationMethod.CACHE, this.name, startTime)
         }
       }
 
@@ -114,7 +120,12 @@ export class LocalAuthStrategy implements AuthStrategy {
         if (offlineResult) {
           logger.debug(`${moduleName}: Offline fallback: ${offlineResult.status}`)
           this.stats.offlineDecisions++
-          return this.enhanceResult(offlineResult, AuthenticationMethod.OFFLINE_FALLBACK, startTime)
+          return enhanceAuthResult(
+            offlineResult,
+            AuthenticationMethod.OFFLINE_FALLBACK,
+            this.name,
+            startTime
+          )
         }
       }
 
@@ -415,32 +426,6 @@ export class LocalAuthStrategy implements AuthStrategy {
     }
   }
 
-  /**
-   * Enhance authorization result with method and timing info
-   * @param result - Original authorization result to enhance
-   * @param method - Authentication method used to obtain the result
-   * @param startTime - Request start timestamp for response time calculation
-   * @returns Enhanced authorization result with strategy metadata and timing
-   */
-  private enhanceResult (
-    result: AuthorizationResult,
-    method: AuthenticationMethod,
-    startTime: number
-  ): AuthorizationResult {
-    const responseTime = Date.now() - startTime
-
-    return {
-      ...result,
-      additionalInfo: {
-        ...result.additionalInfo,
-        responseTimeMs: responseTime,
-        strategy: this.name,
-      },
-      method,
-      timestamp: new Date(),
-    }
-  }
-
   /**
    * Handle offline fallback behavior when remote services unavailable
    * @param request - Authorization request with context information
@@ -490,9 +475,9 @@ export class LocalAuthStrategy implements AuthStrategy {
   }
 
   /**
-   * Map local auth list entry status to unified authorization status
+   * Map local auth list entry status to authorization status
    * @param status - Status string from local auth list entry
-   * @returns Unified authorization status corresponding to the entry status
+   * @returns Authorization status corresponding to the entry status
    */
   private mapEntryStatus (status: string): AuthorizationStatus {
     switch (status.toLowerCase()) {
index f716257b008813e689dfa67c8ae5e109b4367e6d..7a073503f9ea252ff9408cb7be526bc859490c07 100644 (file)
@@ -18,6 +18,7 @@ import {
   AuthenticationError,
   AuthenticationMethod,
   AuthErrorCode,
+  enhanceAuthResult,
   IdentifierType,
 } from '../types/AuthTypes.js'
 
@@ -137,7 +138,12 @@ export class RemoteAuthStrategy implements AuthStrategy {
           )
         }
 
-        return this.enhanceResult(result, startTime)
+        return enhanceAuthResult(
+          result,
+          AuthenticationMethod.REMOTE_AUTHORIZATION,
+          this.name,
+          startTime
+        )
       }
 
       logger.debug(
@@ -406,27 +412,6 @@ export class RemoteAuthStrategy implements AuthStrategy {
     }
   }
 
-  /**
-   * Enhance authorization result with method and timing info
-   * @param result - Original authorization result from remote service
-   * @param startTime - Request start timestamp for response time calculation
-   * @returns Enhanced authorization result with strategy metadata and timing
-   */
-  private enhanceResult (result: AuthorizationResult, startTime: number): AuthorizationResult {
-    const responseTime = Date.now() - startTime
-
-    return {
-      ...result,
-      additionalInfo: {
-        ...result.additionalInfo,
-        responseTimeMs: responseTime,
-        strategy: this.name,
-      },
-      method: AuthenticationMethod.REMOTE_AUTHORIZATION,
-      timestamp: new Date(),
-    }
-  }
-
   /**
    * Perform the actual remote authorization with timeout handling
    * @param request - Authorization request with identifier and context
index a96625d5fdd57debd236a772c08b3b4090edb42d..1e1c1878432a30754dcdd9711a69ed28f076aa5e 100644 (file)
@@ -49,7 +49,7 @@ export enum AuthErrorCode {
 }
 
 /**
- * Unified authorization status combining OCPP 1.6 and 2.0 statuses
+ * Authorization status combining OCPP 1.6 and 2.0 statuses
  */
 export enum AuthorizationStatus {
   // Common statuses across versions
@@ -67,13 +67,13 @@ export enum AuthorizationStatus {
   NOT_ALLOWED_TYPE_EVSE = 'NotAllowedTypeEVSE',
   NOT_AT_THIS_LOCATION = 'NotAtThisLocation',
   NOT_AT_THIS_TIME = 'NotAtThisTime',
-  // Internal statuses for unified handling
+  // Internal statuses
   PENDING = 'Pending',
   UNKNOWN = 'Unknown',
 }
 
 /**
- * Unified identifier types combining OCPP 1.6 and 2.0 token types
+ * Identifier types combining OCPP 1.6 and 2.0 token types
  */
 export enum IdentifierType {
   BIOMETRIC = 'Biometric',
@@ -210,7 +210,7 @@ export interface AuthRequest {
   readonly evseId?: number
 
   /** Identifier to authenticate */
-  readonly identifier: UnifiedIdentifier
+  readonly identifier: Identifier
 
   /** Additional context data */
   readonly metadata?: Record<string, unknown>
@@ -246,9 +246,9 @@ export interface CertificateHashData {
 }
 
 /**
- * Unified identifier that works across OCPP versions
+ * Identifier that works across OCPP versions
  */
-export interface UnifiedIdentifier {
+export interface Identifier {
   /** Additional info for OCPP 2.0 tokens */
   readonly additionalInfo?: Record<string, string>
 
@@ -345,7 +345,7 @@ export const requiresAdditionalInfo = (type: IdentifierType): boolean => {
 /**
  * Type mappers for OCPP version compatibility
  *
- * Provides bidirectional mapping between OCPP version-specific types and unified types.
+ * Provides bidirectional mapping between OCPP version-specific types and auth types.
  * This allows the authentication system to work seamlessly across OCPP 1.6 and 2.0.
  * @remarks
  * **Edge cases and limitations:**
@@ -353,16 +353,16 @@ export const requiresAdditionalInfo = (type: IdentifierType): boolean => {
  *   map to INVALID when converting to OCPP 1.6
  * - OCPP 2.0 IdToken types have more granularity than OCPP 1.6 IdTag
  * - Certificate-based auth (IdentifierType.CERTIFICATE) is only available in OCPP 2.0+
- * - When mapping from unified to OCPP 2.0, unsupported types default to Local
+ * - When mapping to OCPP 2.0, unsupported types default to Local
  */
 
 /**
- * Maps OCPP 1.6 authorization status to unified status
+ * Maps OCPP 1.6 authorization status to authorization status
  * @param status - OCPP 1.6 authorization status
- * @returns Unified authorization status
+ * @returns Authorization status
  * @example
  * ```typescript
- * const unifiedStatus = mapOCPP16Status(OCPP16AuthorizationStatus.ACCEPTED)
+ * const status = mapOCPP16Status(OCPP16AuthorizationStatus.ACCEPTED)
  * // Returns: AuthorizationStatus.ACCEPTED
  * ```
  */
@@ -384,12 +384,12 @@ export const mapOCPP16Status = (status: OCPP16AuthorizationStatus): Authorizatio
 }
 
 /**
- * Maps OCPP 2.0 authorization status enum to unified authorization status
+ * Maps OCPP 2.0 authorization status enum to authorization status
  * @param status - OCPP 2.0 authorization status
- * @returns Unified authorization status
+ * @returns Authorization status
  * @example
  * ```typescript
- * const unifiedStatus = mapOCPP20AuthorizationStatus(OCPP20AuthorizationStatusEnumType.Accepted)
+ * const status = mapOCPP20AuthorizationStatus(OCPP20AuthorizationStatusEnumType.Accepted)
  * // Returns: AuthorizationStatus.ACCEPTED
  * ```
  */
@@ -423,12 +423,12 @@ export const mapOCPP20AuthorizationStatus = (
 }
 
 /**
- * Maps OCPP 2.0 token type to unified identifier type
+ * Maps OCPP 2.0 token type to identifier type
  * @param type - OCPP 2.0 token type
- * @returns Unified identifier type
+ * @returns Identifier type
  * @example
  * ```typescript
- * const unifiedType = mapOCPP20TokenType(OCPP20IdTokenEnumType.ISO14443)
+ * const identifierType = mapOCPP20TokenType(OCPP20IdTokenEnumType.ISO14443)
  * // Returns: IdentifierType.ISO14443
  * ```
  */
@@ -456,8 +456,8 @@ export const mapOCPP20TokenType = (type: OCPP20IdTokenEnumType): IdentifierType
 }
 
 /**
- * Maps unified authorization status to OCPP 1.6 status
- * @param status - Unified authorization status
+ * Maps authorization status to OCPP 1.6 status
+ * @param status - Authorization status
  * @returns OCPP 1.6 authorization status
  * @example
  * ```typescript
@@ -486,8 +486,8 @@ export const mapToOCPP16Status = (status: AuthorizationStatus): OCPP16Authorizat
 }
 
 /**
- * Maps unified authorization status to OCPP 2.0 RequestStartStopStatus
- * @param status - Unified authorization status
+ * Maps authorization status to OCPP 2.0 RequestStartStopStatus
+ * @param status - Authorization status
  * @returns OCPP 2.0 RequestStartStopStatus
  * @example
  * ```typescript
@@ -513,8 +513,8 @@ export const mapToOCPP20Status = (status: AuthorizationStatus): RequestStartStop
 }
 
 /**
- * Maps unified identifier type to OCPP 2.0 token type
- * @param type - Unified identifier type
+ * Maps identifier type to OCPP 2.0 token type
+ * @param type - Identifier type
  * @returns OCPP 2.0 token type
  * @example
  * ```typescript
@@ -545,3 +545,19 @@ export const mapToOCPP20TokenType = (type: IdentifierType): OCPP20IdTokenEnumTyp
       return OCPP20IdTokenEnumType.Local
   }
 }
+
+export const enhanceAuthResult = (
+  result: AuthorizationResult,
+  method: AuthenticationMethod,
+  strategyName: string,
+  startTime: number
+): AuthorizationResult => ({
+  ...result,
+  additionalInfo: {
+    ...result.additionalInfo,
+    responseTimeMs: Date.now() - startTime,
+    strategy: strategyName,
+  },
+  method,
+  timestamp: new Date(),
+})
index 3358c720ca181315d2733b7e5ca08a2ccfb32c3a..de0a5435faeaf431fcac0afe70f62c3d5545b503 100644 (file)
@@ -3,7 +3,7 @@ import type {
   AuthenticationMethod,
   AuthorizationResult,
   AuthRequest,
-  UnifiedIdentifier,
+  Identifier,
 } from '../types/AuthTypes.js'
 
 import { truncateId } from '../../../../utils/index.js'
@@ -30,14 +30,14 @@ function calculateTTL (expiryDate?: Date): number | undefined {
 
 /**
  * Build an AuthRequest with sensible defaults.
- * @param identifier - Unified identifier for the request
+ * @param identifier - Identifier for the request
  * @param context - Authentication context
  * @param connectorId - Optional connector ID
  * @param metadata - Optional additional metadata
  * @returns Fully populated AuthRequest
  */
 function createAuthRequest (
-  identifier: UnifiedIdentifier,
+  identifier: Identifier,
   context: AuthContext,
   connectorId?: number,
   metadata?: Record<string, unknown>
@@ -79,7 +79,7 @@ function createRejectedResult (
  * @param identifier - Identifier involved in the failed auth attempt
  * @returns Formatted error string with truncated identifier
  */
-function formatAuthError (error: Error, identifier: UnifiedIdentifier): string {
+function formatAuthError (error: Error, identifier: Identifier): string {
   return `Authentication failed for identifier ${truncateId(identifier.value)} (${identifier.type}): ${error.message}`
 }
 
index 250f1a1735ce82d4b0cdf0bdaa1904347ad057e6..f3b238a54935b7a8ef484c7853c5e25dc2f07649 100644 (file)
@@ -1,4 +1,4 @@
-import type { AuthConfiguration, UnifiedIdentifier } from '../types/AuthTypes.js'
+import type { AuthConfiguration, Identifier } from '../types/AuthTypes.js'
 
 import { IdentifierType } from '../types/AuthTypes.js'
 
@@ -146,8 +146,8 @@ function validateAuthConfiguration (config: unknown): boolean {
 }
 
 /**
- * Validate unified identifier format and constraints
- * @param identifier - Unified identifier object to validate (may be any type)
+ * Validate identifier format and constraints
+ * @param identifier - Identifier object to validate (may be any type)
  * @returns True if the identifier has a valid type and value within OCPP length constraints, false otherwise
  */
 function validateIdentifier (identifier: unknown): boolean {
@@ -156,14 +156,14 @@ function validateIdentifier (identifier: unknown): boolean {
     return false
   }
 
-  const unifiedIdentifier = identifier as UnifiedIdentifier
+  const typedIdentifier = identifier as Identifier
 
-  if (!unifiedIdentifier.value) {
+  if (!typedIdentifier.value) {
     return false
   }
 
   // Check length constraints based on identifier type
-  switch (unifiedIdentifier.type) {
+  switch (typedIdentifier.type) {
     case IdentifierType.BIOMETRIC:
     // Fallthrough intentional: all these OCPP 2.0 types share the same validation
     case IdentifierType.CENTRAL:
@@ -177,13 +177,9 @@ function validateIdentifier (identifier: unknown): boolean {
     case IdentifierType.MOBILE_APP:
     case IdentifierType.NO_AUTHORIZATION:
       // OCPP 2.0 types - use IdToken max length
-      return (
-        unifiedIdentifier.value.length > 0 && unifiedIdentifier.value.length <= MAX_IDTOKEN_LENGTH
-      )
+      return typedIdentifier.value.length > 0 && typedIdentifier.value.length <= MAX_IDTOKEN_LENGTH
     case IdentifierType.ID_TAG:
-      return (
-        unifiedIdentifier.value.length > 0 && unifiedIdentifier.value.length <= MAX_IDTAG_LENGTH
-      )
+      return typedIdentifier.value.length > 0 && typedIdentifier.value.length <= MAX_IDTAG_LENGTH
 
     default:
       return false
index 86d47678c30ea4bd70995a6059344540a6e2ee52..5af98ac2d7a382daada2e0858d060d19355805c0 100644 (file)
@@ -4,6 +4,5 @@
  * Provides validation and helper functions for authentication operations
  */
 
-export { AuthHelpers } from './AuthHelpers.js'
 export { AuthValidators } from './AuthValidators.js'
 export { AuthConfigValidator } from './ConfigValidator.js'
index bfd92858a07f4435dd47e8a890cd76530e99d003..c1d717668241de47f07f2331c207cfad2829dcf5 100644 (file)
@@ -7,13 +7,13 @@ export { OCPP20RequestService } from './2.0/OCPP20RequestService.js'
 export { OCPP20ResponseService } from './2.0/OCPP20ResponseService.js'
 export { buildTransactionEvent, OCPP20ServiceUtils } from './2.0/OCPP20ServiceUtils.js'
 export { OCPP20VariableManager } from './2.0/OCPP20VariableManager.js'
-export { OCPPAuthServiceFactory } from './auth/services/OCPPAuthServiceFactory.js'
+export { OCPPAuthServiceFactory } from './auth/index.js'
+export { isIdTagAuthorized } from './IdTagAuthorization.js'
 export { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js'
 export { OCPPRequestService } from './OCPPRequestService.js'
 export {
   buildMeterValue,
   buildStatusNotificationRequest,
   buildTransactionEndMeterValue,
-  isIdTagAuthorized,
   sendAndSetConnectorStatus,
 } from './OCPPServiceUtils.js'
index bfc8d03ae216d4511d0dc821866047d3bf580258..4e1c418ef0d34edf72174356839ca535d6b5a88b 100644 (file)
@@ -3,6 +3,7 @@ import type { WebSocket } from 'ws'
 import { type IncomingMessage, Server, type ServerResponse } from 'node:http'
 import { createServer, type Http2Server } from 'node:http2'
 
+import type { IBootstrap } from '../IBootstrap.js'
 import type { AbstractUIService } from './ui-services/AbstractUIService.js'
 
 import { BaseError } from '../../exception/index.js'
@@ -35,11 +36,16 @@ export abstract class AbstractUIServer {
 
   protected readonly uiServices: Map<ProtocolVersion, AbstractUIService>
 
+  private readonly bootstrap: IBootstrap
   private readonly chargingStations: Map<string, ChargingStationData>
   private readonly chargingStationTemplates: Set<string>
   private clientNotificationDebounceTimer: ReturnType<typeof setTimeout> | undefined
 
-  public constructor (protected readonly uiServerConfiguration: UIServerConfiguration) {
+  public constructor (
+    protected readonly uiServerConfiguration: UIServerConfiguration,
+    bootstrap: IBootstrap
+  ) {
+    this.bootstrap = bootstrap
     this.chargingStations = new Map<string, ChargingStationData>()
     this.chargingStationTemplates = new Set<string>()
     switch (this.uiServerConfiguration.version) {
@@ -80,6 +86,10 @@ export abstract class AbstractUIServer {
     return this.chargingStations.delete(hashId)
   }
 
+  public getBootstrap (): IBootstrap {
+    return this.bootstrap
+  }
+
   public getChargingStationData (hashId: string): ChargingStationData | undefined {
     return this.chargingStations.get(hashId)
   }
index 463613b84832ed0dcdd8afeea824bca42fc68e7e..a07f51d3212c75bc712a518d35dc955a13678dae 100644 (file)
@@ -3,6 +3,8 @@ import type { IncomingMessage, ServerResponse } from 'node:http'
 import { StatusCodes } from 'http-status-codes'
 import { createGzip } from 'node:zlib'
 
+import type { IBootstrap } from '../IBootstrap.js'
+
 import { BaseError } from '../../exception/index.js'
 import {
   ApplicationProtocolVersion,
@@ -41,8 +43,11 @@ export class UIHttpServer extends AbstractUIServer {
 
   private readonly acceptsGzip: Map<UUIDv4, boolean>
 
-  public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) {
-    super(uiServerConfiguration)
+  public constructor (
+    protected override readonly uiServerConfiguration: UIServerConfiguration,
+    bootstrap: IBootstrap
+  ) {
+    super(uiServerConfiguration, bootstrap)
     this.acceptsGzip = new Map<UUIDv4, boolean>()
   }
 
index 2d4934f4b8512091a307252016066732a75f99bb..03f6381ef4764ce5364eee2de0f070f735247883 100644 (file)
@@ -7,6 +7,7 @@ import { readFileSync } from 'node:fs'
 import { dirname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
 
+import type { IBootstrap } from '../IBootstrap.js'
 import type { AbstractUIService } from './ui-services/AbstractUIService.js'
 
 import { BaseError } from '../../exception/index.js'
@@ -60,8 +61,11 @@ export class UIMCPServer extends AbstractUIServer {
 
   private service: AbstractUIService | undefined
 
-  public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) {
-    super(uiServerConfiguration)
+  public constructor (
+    protected override readonly uiServerConfiguration: UIServerConfiguration,
+    bootstrap: IBootstrap
+  ) {
+    super(uiServerConfiguration, bootstrap)
     this.pendingMcpRequests = new Map()
     this.ocppSchemaCache = new Map()
   }
index e7af6f4d1892a63fce541e0a0a8cf9dae52c0b65..d5f17a13b27857c18d5a225abbb535323a7b7c88 100644 (file)
@@ -1,3 +1,4 @@
+import type { IBootstrap } from '../IBootstrap.js'
 import type { AbstractUIServer } from './AbstractUIServer.js'
 
 import { BaseError } from '../../exception/index.js'
@@ -21,7 +22,8 @@ export class UIServerFactory {
   }
 
   public static getUIServerImplementation (
-    uiServerConfiguration: UIServerConfiguration
+    uiServerConfiguration: UIServerConfiguration,
+    bootstrap: IBootstrap
   ): AbstractUIServer {
     if (
       uiServerConfiguration.authentication?.enabled === true &&
@@ -70,10 +72,10 @@ export class UIServerFactory {
         const logMsg = `Application protocol type '${uiServerConfiguration.type}' is deprecated in '${ConfigurationSection.uiServer}' configuration section. Use '${ApplicationProtocol.MCP}' instead`
         logger.warn(`${UIServerFactory.logPrefix()} ${logMsg}`)
         // eslint-disable-next-line @typescript-eslint/no-deprecated
-        return new UIHttpServer(uiServerConfiguration)
+        return new UIHttpServer(uiServerConfiguration, bootstrap)
       }
       case ApplicationProtocol.MCP:
-        return new UIMCPServer(uiServerConfiguration)
+        return new UIMCPServer(uiServerConfiguration, bootstrap)
       case ApplicationProtocol.WS:
       default:
         if (
@@ -90,7 +92,7 @@ export class UIServerFactory {
           }'`
           logger.warn(`${UIServerFactory.logPrefix()} ${logMsg}`)
         }
-        return new UIWebSocketServer(uiServerConfiguration)
+        return new UIWebSocketServer(uiServerConfiguration, bootstrap)
     }
   }
 
index ee85a2e3484b823632727192463bf1981cdfbeca..51b39b70de44e9b96c39a8c594bd4895f91cd104 100644 (file)
@@ -4,6 +4,8 @@ import type { Duplex } from 'node:stream'
 import { StatusCodes } from 'http-status-codes'
 import { type RawData, WebSocket, WebSocketServer } from 'ws'
 
+import type { IBootstrap } from '../IBootstrap.js'
+
 import {
   MapStringifyFormat,
   type ProtocolNotification,
@@ -34,8 +36,11 @@ export class UIWebSocketServer extends AbstractUIServer {
 
   private readonly webSocketServer: WebSocketServer
 
-  public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) {
-    super(uiServerConfiguration)
+  public constructor (
+    protected override readonly uiServerConfiguration: UIServerConfiguration,
+    bootstrap: IBootstrap
+  ) {
+    super(uiServerConfiguration, bootstrap)
     this.webSocketServer = new WebSocketServer({
       handleProtocols,
       maxPayload: DEFAULT_MAX_PAYLOAD_SIZE,
index 03b5780538152dbf0b3a8c5afff39d57a1cbb46b..58c5a358e695aa0610fb54cb17b2831789ea837c 100644 (file)
@@ -28,7 +28,6 @@ import {
   isNotEmptyArray,
   logger,
 } from '../../../utils/index.js'
-import { Bootstrap } from '../../Bootstrap.js'
 import { UIServiceWorkerBroadcastChannel } from '../../broadcast-channel/UIServiceWorkerBroadcastChannel.js'
 import { DEFAULT_MAX_STATIONS, isValidNumberOfStations } from '../UIServerSecurity.js'
 
@@ -240,7 +239,7 @@ export abstract class AbstractUIService {
   ): Promise<ResponsePayload> {
     const { numberOfStations, options, template } =
       requestPayload as AddChargingStationsRequestPayload
-    if (!Bootstrap.getInstance().getState().started) {
+    if (!this.uiServer.getBootstrap().getState().started) {
       return {
         errorMessage:
           'Cannot add charging station(s) while the charging stations simulator is not started',
@@ -276,11 +275,13 @@ export abstract class AbstractUIService {
     for (let i = 0; i < numberOfStations; i++) {
       let stationInfo: ChargingStationInfo | undefined
       try {
-        stationInfo = await Bootstrap.getInstance().addChargingStation(
-          Bootstrap.getInstance().getLastContiguousIndex(template) + 1,
-          `${template}.json`,
-          options
-        )
+        stationInfo = await this.uiServer
+          .getBootstrap()
+          .addChargingStation(
+            this.uiServer.getBootstrap().getLastContiguousIndex(template) + 1,
+            `${template}.json`,
+            options
+          )
         if (stationInfo != null) {
           succeededStationInfos.push(stationInfo)
         }
@@ -331,7 +332,7 @@ export abstract class AbstractUIService {
     try {
       return {
         performanceStatistics: [
-          ...(Bootstrap.getInstance().getPerformanceStatistics() ?? []),
+          ...(this.uiServer.getBootstrap().getPerformanceStatistics() ?? []),
         ] as JsonType[],
         status: ResponseStatus.SUCCESS,
       } satisfies ResponsePayload
@@ -347,7 +348,7 @@ export abstract class AbstractUIService {
   private handleSimulatorState (): ResponsePayload {
     try {
       return {
-        state: Bootstrap.getInstance().getState() as unknown as JsonObject,
+        state: this.uiServer.getBootstrap().getState() as unknown as JsonObject,
         status: ResponseStatus.SUCCESS,
       } satisfies ResponsePayload
     } catch (error) {
@@ -361,7 +362,7 @@ export abstract class AbstractUIService {
 
   private async handleStartSimulator (): Promise<ResponsePayload> {
     try {
-      await Bootstrap.getInstance().start()
+      await this.uiServer.getBootstrap().start()
       return { status: ResponseStatus.SUCCESS }
     } catch (error) {
       return {
@@ -374,7 +375,7 @@ export abstract class AbstractUIService {
 
   private async handleStopSimulator (): Promise<ResponsePayload> {
     try {
-      await Bootstrap.getInstance().stop()
+      await this.uiServer.getBootstrap().stop()
       return { status: ResponseStatus.SUCCESS }
     } catch (error) {
       return {
index 0bea67391652365889392c7dccac994f929a5a02..dbcfb6d5530eee431380a77cc7a3d8d27c3bc58d 100644 (file)
@@ -12,9 +12,8 @@ import type {
   RequestCommand,
 } from '../types/index.js'
 
-import { getMessageTypeString } from '../charging-station/index.js'
 import { logger } from './Logger.js'
-import { isNotEmptyString } from './Utils.js'
+import { getMessageTypeString, isNotEmptyString } from './Utils.js'
 
 const moduleName = 'ErrorUtils'
 
index 6a33795ebd12f5c7b6a46081e7c2d64d24e278b0..a635a59ae7d665eb73733ae6f9b5aa049fc4ea80 100644 (file)
@@ -17,6 +17,7 @@ import { env } from 'node:process'
 import {
   type JsonType,
   MapStringifyFormat,
+  MessageType,
   type TimestampedData,
   type UUIDv4,
   WebSocketCloseEventStatusString,
@@ -514,3 +515,16 @@ export const truncateId = (identifier: string, maxLen = 8): string => {
   }
   return `${identifier.slice(0, maxLen)}...`
 }
+
+export const getMessageTypeString = (messageType: MessageType | undefined): string => {
+  switch (messageType) {
+    case MessageType.CALL_ERROR_MESSAGE:
+      return 'error'
+    case MessageType.CALL_MESSAGE:
+      return 'request'
+    case MessageType.CALL_RESULT_MESSAGE:
+      return 'response'
+    default:
+      return 'unknown'
+  }
+}
index 84bcf617e9468dc58bbe543446c90b4debb415e4..f1c900aa8153e104f252589b3c770ebce302f543 100644 (file)
@@ -43,6 +43,7 @@ export {
   formatDurationMilliSeconds,
   formatDurationSeconds,
   generateUUID,
+  getMessageTypeString,
   getRandomFloatFluctuatedRounded,
   getRandomFloatRounded,
   getWebSocketCloseEventStatusString,
index 8760f05c1995171cb897f9921b82cb69438cb14a..66818a0c6c962f905eb3d2abe09987ebcbb66842 100644 (file)
@@ -341,7 +341,7 @@ assert.strictEqual(mocks.webSocket.sentMessages.length, 1)
 
 | Utility                           | Purpose                     |
 | --------------------------------- | --------------------------- |
-| `createMockIdentifier()`          | UnifiedIdentifier factory   |
+| `createMockIdentifier()`          | Identifier factory          |
 | `createMockAuthRequest()`         | AuthRequest factory         |
 | `createMockAuthorizationResult()` | AuthorizationResult factory |
 
index 9ea307249d8192f6f1b96f9c4be37760b66dae1d..d407f5410cbcdd97e0ea1ff74768cc9816f37caf 100644 (file)
@@ -15,7 +15,6 @@ import {
   getChargingStationId,
   getHashId,
   getMaxNumberOfEvses,
-  getMessageTypeString,
   getPhaseRotationValue,
   hasPendingReservation,
   hasPendingReservations,
@@ -32,7 +31,6 @@ import {
   type ChargingStationTemplate,
   type ConnectorStatus,
   ConnectorStatusEnum,
-  MessageType,
   type MeterValue,
   OCPPVersion,
   type Reservation,
@@ -879,22 +877,4 @@ await describe('Helpers', async () => {
       assert.strictEqual(connectorStatus.MeterValues.length, 1)
     })
   })
-
-  await describe('getMessageTypeString', async () => {
-    await it('should return "request" for MessageType.CALL_MESSAGE', () => {
-      assert.strictEqual(getMessageTypeString(MessageType.CALL_MESSAGE), 'request')
-    })
-
-    await it('should return "response" for MessageType.CALL_RESULT_MESSAGE', () => {
-      assert.strictEqual(getMessageTypeString(MessageType.CALL_RESULT_MESSAGE), 'response')
-    })
-
-    await it('should return "error" for MessageType.CALL_ERROR_MESSAGE', () => {
-      assert.strictEqual(getMessageTypeString(MessageType.CALL_ERROR_MESSAGE), 'error')
-    })
-
-    await it('should return "unknown" for undefined', () => {
-      assert.strictEqual(getMessageTypeString(undefined), 'unknown')
-    })
-  })
 })
index 8883b7c2170527e899416796d8d38c00bb1d0918..5a30056d3d8a157b0349b391689e8df5eb0dea6e 100644 (file)
@@ -10,7 +10,7 @@ import type { ChargingStation } from '../../../../src/charging-station/index.js'
 
 import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
 import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
-import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js'
+import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js'
 import { GenericStatus, OCPPVersion } from '../../../../src/types/index.js'
 import { Constants } from '../../../../src/utils/index.js'
 import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
@@ -81,7 +81,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async ()
       // Mock the factory to return our mock auth service
       const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory)
       Object.assign(OCPPAuthServiceFactory, {
-        getInstance: (): Promise<typeof mockAuthService> => Promise.resolve(mockAuthService),
+        getInstance: (): typeof mockAuthService => mockAuthService,
       })
 
       try {
@@ -132,7 +132,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async ()
       // Mock the factory to return our mock auth service
       const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory)
       Object.assign(OCPPAuthServiceFactory, {
-        getInstance: (): Promise<typeof mockAuthService> => Promise.resolve(mockAuthService),
+        getInstance: (): typeof mockAuthService => mockAuthService,
       })
 
       try {
@@ -159,7 +159,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async ()
       // Mock the factory to return our mock auth service
       const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory)
       Object.assign(OCPPAuthServiceFactory, {
-        getInstance: (): Promise<typeof mockAuthService> => Promise.resolve(mockAuthService),
+        getInstance: (): typeof mockAuthService => mockAuthService,
       })
 
       try {
@@ -186,7 +186,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async ()
       // Mock the factory to return our mock auth service
       const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory)
       Object.assign(OCPPAuthServiceFactory, {
-        getInstance: (): Promise<typeof mockAuthService> => Promise.resolve(mockAuthService),
+        getInstance: (): typeof mockAuthService => mockAuthService,
       })
 
       try {
@@ -213,7 +213,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async ()
       // Mock the factory to return our mock auth service
       const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory)
       Object.assign(OCPPAuthServiceFactory, {
-        getInstance: (): Promise<typeof mockAuthService> => Promise.resolve(mockAuthService),
+        getInstance: (): typeof mockAuthService => mockAuthService,
       })
 
       try {
@@ -234,8 +234,9 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async ()
       // Mock factory to throw error (simulates no Authorization Cache support)
       const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory)
       Object.assign(OCPPAuthServiceFactory, {
-        getInstance: (): Promise<never> =>
-          Promise.reject(new Error('Authorization Cache not supported')),
+        getInstance: (): never => {
+          throw new Error('Authorization Cache not supported')
+        },
       })
 
       try {
index db4cd1837356af53a6a352daafbc61ccb615808f..621790b6b8ad602ed2f7170b1c68ba9bd00f892f 100644 (file)
@@ -11,7 +11,7 @@ import type { OCPP20RequestStartTransactionRequest } from '../../../../src/types
 
 import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
 import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
-import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js'
+import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js'
 import {
   OCPP20IdTokenEnumType,
   RequestStartStopStatusEnumType,
index be9c1d9b5983cbcaf29acb950817d0a1ee0b2aa4..170873d5c4aefccd5fbc48ad17df52533758ed3d 100644 (file)
@@ -14,7 +14,7 @@ import type {
 import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
 import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
 import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js'
-import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js'
+import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js'
 import {
   GetVariableStatusEnumType,
   OCPP20IdTokenEnumType,
index 058ecff9a161b082a491f43dd090cc2d9b7d427a..0b9326b6ab2b2b0588489a23d3bb1397a56cd6f6 100644 (file)
@@ -19,13 +19,21 @@ import type {
 
 import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
 import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
-import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js'
+import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js'
 import {
+  AuthenticationMethod,
+  AuthorizationStatus,
+  OCPPAuthServiceFactory,
+} from '../../../../src/charging-station/ocpp/auth/index.js'
+import {
+  AttributeEnumType,
   OCPP20ChargingProfileKindEnumType,
   OCPP20ChargingProfilePurposeEnumType,
+  OCPP20ComponentName,
   OCPP20IdTokenEnumType,
   OCPP20IncomingRequestCommand,
   OCPP20RequestCommand,
+  OCPP20RequiredVariableName,
   OCPP20TransactionEventEnumType,
   OCPP20TriggerReasonEnumType,
   OCPPVersion,
@@ -35,7 +43,10 @@ import { Constants } from '../../../../src/utils/index.js'
 import { flushMicrotasks, standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
 import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js'
 import { createMockChargingStation } from '../../ChargingStationTestUtils.js'
-import { createMockAuthService } from '../auth/helpers/MockFactories.js'
+import {
+  createMockAuthorizationResult,
+  createMockAuthService,
+} from '../auth/helpers/MockFactories.js'
 import {
   createOCPP20ListenerStation,
   resetConnectorTransactionState,
@@ -96,6 +107,87 @@ await describe('F01 & F02 - Remote Start Transaction', async () => {
     assert.strictEqual(typeof response.transactionId, 'string')
   })
 
+  // G03.FR.03 — Reject when auth returns non-ACCEPTED status
+  await it('should reject RequestStartTransaction when auth returns INVALID status', async () => {
+    // Arrange: Override mock auth service to return INVALID
+    const stationId = mockStation.stationInfo?.chargingStationId ?? 'unknown'
+    const rejectingAuthService = createMockAuthService({
+      authorize: () =>
+        Promise.resolve(
+          createMockAuthorizationResult({
+            method: AuthenticationMethod.REMOTE_AUTHORIZATION,
+            status: AuthorizationStatus.INVALID,
+          })
+        ),
+    })
+    OCPPAuthServiceFactory.setInstanceForTesting(stationId, rejectingAuthService)
+
+    const request: OCPP20RequestStartTransactionRequest = {
+      evseId: 1,
+      idToken: {
+        idToken: 'INVALID_TOKEN_001',
+        type: OCPP20IdTokenEnumType.ISO14443,
+      },
+      remoteStartId: 50,
+    }
+
+    // Act
+    const response = await testableService.handleRequestStartTransaction(mockStation, request)
+
+    // Assert
+    assert.notStrictEqual(response, undefined)
+    assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
+  })
+
+  // G03.FR.03 — Reject when auth returns BLOCKED for groupIdToken
+  await it('should reject RequestStartTransaction when groupIdToken auth returns BLOCKED', async () => {
+    // Arrange: Primary token accepted, group token blocked
+    let callCount = 0
+    const stationId = mockStation.stationInfo?.chargingStationId ?? 'unknown'
+    const mixedAuthService = createMockAuthService({
+      authorize: () => {
+        callCount++
+        if (callCount === 1) {
+          // First call: idToken → accepted
+          return Promise.resolve(
+            createMockAuthorizationResult({
+              method: AuthenticationMethod.REMOTE_AUTHORIZATION,
+              status: AuthorizationStatus.ACCEPTED,
+            })
+          )
+        }
+        // Second call: groupIdToken → blocked
+        return Promise.resolve(
+          createMockAuthorizationResult({
+            method: AuthenticationMethod.REMOTE_AUTHORIZATION,
+            status: AuthorizationStatus.BLOCKED,
+          })
+        )
+      },
+    })
+    OCPPAuthServiceFactory.setInstanceForTesting(stationId, mixedAuthService)
+
+    const request: OCPP20RequestStartTransactionRequest = {
+      evseId: 2,
+      groupIdToken: {
+        idToken: 'BLOCKED_GROUP_TOKEN',
+        type: OCPP20IdTokenEnumType.Central,
+      },
+      idToken: {
+        idToken: 'VALID_TOKEN_002',
+        type: OCPP20IdTokenEnumType.ISO14443,
+      },
+      remoteStartId: 51,
+    }
+
+    // Act
+    const response = await testableService.handleRequestStartTransaction(mockStation, request)
+
+    // Assert
+    assert.notStrictEqual(response, undefined)
+    assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
+  })
+
   // FR: F01.FR.17, F02.FR.05 - Verify remoteStartId and idToken are stored for later TransactionEvent
   await it('should store remoteStartId and idToken in connector status for TransactionEvent', async () => {
     const { station: spyChargingStation } = createMockChargingStation({
@@ -343,6 +435,61 @@ await describe('F01 & F02 - Remote Start Transaction', async () => {
     assert.notStrictEqual(response.transactionId, undefined)
   })
 
+  await it('should reject RequestStartTransaction when authorization throws an error', async () => {
+    // Arrange
+    const stationId = mockStation.stationInfo?.chargingStationId ?? 'unknown'
+    const throwingAuthService = createMockAuthService({
+      authorize: () => Promise.reject(new Error('Auth service unavailable')),
+    })
+    OCPPAuthServiceFactory.setInstanceForTesting(stationId, throwingAuthService)
+
+    const request: OCPP20RequestStartTransactionRequest = {
+      evseId: 1,
+      idToken: {
+        idToken: 'ERROR_TOKEN',
+        type: OCPP20IdTokenEnumType.ISO14443,
+      },
+      remoteStartId: 99,
+    }
+
+    // Act
+    const response = await testableService.handleRequestStartTransaction(mockStation, request)
+
+    // Assert
+    assert.notStrictEqual(response, undefined)
+    assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
+  })
+
+  await it('should accept RequestStartTransaction when AuthorizeRemoteStart is false', async () => {
+    // Arrange
+    const variableManager = OCPP20VariableManager.getInstance()
+    variableManager.setVariables(mockStation, [
+      {
+        attributeType: AttributeEnumType.Actual,
+        attributeValue: 'false',
+        component: { name: OCPP20ComponentName.AuthCtrlr },
+        variable: { name: OCPP20RequiredVariableName.AuthorizeRemoteStart },
+      },
+    ])
+
+    const request: OCPP20RequestStartTransactionRequest = {
+      evseId: 1,
+      idToken: {
+        idToken: 'SKIP_AUTH_TOKEN',
+        type: OCPP20IdTokenEnumType.ISO14443,
+      },
+      remoteStartId: 77,
+    }
+
+    // Act
+    const response = await testableService.handleRequestStartTransaction(mockStation, request)
+
+    // Assert
+    assert.notStrictEqual(response, undefined)
+    assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
+    assert.notStrictEqual(response.transactionId, undefined)
+  })
+
   // FR: F02.FR.01
   await it('should return proper response structure', async () => {
     const validRequest: OCPP20RequestStartTransactionRequest = {
index 507ab40814349aae38f459d2765308a4f113866a..d2029e2d7addd71da1df27c476f524fc444a82fe 100644 (file)
@@ -19,7 +19,7 @@ import type {
 
 import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
 import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
-import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js'
+import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js'
 import {
   OCPP20IdTokenEnumType,
   OCPP20IncomingRequestCommand,
index 20734005ec7081fe669ef325f052f6443d276679..97d9d318e1e2544b5fa54cc81d846ff9fb364adc 100644 (file)
@@ -15,7 +15,7 @@ import type {
 
 import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
 import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
-import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js'
+import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js'
 import {
   GetCertificateIdUseEnumType,
   HashAlgorithmEnumType,
index 470373ddc8d0087a1c455cd891d8f6350c6dde38..5c71ab6cbedf1100d12710cb05fcc114ad8bf7e0 100644 (file)
@@ -16,7 +16,7 @@ import type {
 
 import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
 import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
-import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js'
+import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js'
 import {
   AttributeEnumType,
   GetVariableStatusEnumType,
index 91ff90579d4271d1100fe338fac7d8ca70f7bd59..8df8cc9c80d64bd0bd6c0c3baa1a57707a7b16a5 100644 (file)
@@ -8,15 +8,15 @@ import assert from 'node:assert/strict'
 import { afterEach, beforeEach, describe, it } from 'node:test'
 
 import type { ChargingStation } from '../../../../src/charging-station/index.js'
+import type { LocalAuthStrategy } from '../../../../src/charging-station/ocpp/auth/index.js'
 import type { AuthCache } from '../../../../src/charging-station/ocpp/auth/interfaces/OCPPAuthService.js'
-import type { LocalAuthStrategy } from '../../../../src/charging-station/ocpp/auth/strategies/LocalAuthStrategy.js'
 
-import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js'
-import { OCPPAuthServiceImpl } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.js'
 import {
   AuthorizationStatus,
   IdentifierType,
-} from '../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
+  OCPPAuthServiceFactory,
+  OCPPAuthServiceImpl,
+} from '../../../../src/charging-station/ocpp/auth/index.js'
 import { OCPP20AuthorizationStatusEnumType, OCPPVersion } from '../../../../src/types/index.js'
 import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
 import { createMockChargingStation } from '../../ChargingStationTestUtils.js'
@@ -29,7 +29,7 @@ await describe('C10 - TransactionEventResponse Cache Update', async () => {
   let authService: OCPPAuthServiceImpl
   let authCache: AuthCache
 
-  beforeEach(async () => {
+  beforeEach(() => {
     const { station: mockStation } = createMockChargingStation({
       baseName: TEST_STATION_ID,
       connectorsCount: 1,
@@ -41,7 +41,7 @@ await describe('C10 - TransactionEventResponse Cache Update', async () => {
     station = mockStation
 
     authService = new OCPPAuthServiceImpl(station)
-    await authService.initialize()
+    authService.initialize()
 
     const localStrategy = authService.getStrategy('local') as LocalAuthStrategy | undefined
     const cache = localStrategy?.getAuthCache()
@@ -152,7 +152,7 @@ await describe('C10 - TransactionEventResponse Cache Update', async () => {
     assert.strictEqual(cachedExpired.status, AuthorizationStatus.EXPIRED)
   })
 
-  await it('should not update cache when authorizationCacheEnabled is false', async () => {
+  await it('should not update cache when authorizationCacheEnabled is false', () => {
     // Arrange — create service with cache disabled
     const { station: disabledStation } = createMockChargingStation({
       baseName: 'CS_CACHE_DISABLED',
@@ -163,7 +163,7 @@ await describe('C10 - TransactionEventResponse Cache Update', async () => {
       },
     })
     const disabledService = new OCPPAuthServiceImpl(disabledStation)
-    await disabledService.initialize()
+    disabledService.initialize()
     disabledService.updateConfiguration({ authorizationCacheEnabled: false })
 
     const idTokenInfo = {
index 5ca8924fb159d9c3fa0cc122f6550a4b01ffbe70..e5f95c39e08536b08154d110d189ba27518d434d 100644 (file)
@@ -2009,7 +2009,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
     })
 
     await describe('startUpdatedMeterValues', async () => {
-      await it('should not start OCPP 2.0 timer for OCPP 1.6 stations via unified dispatch', async t => {
+      await it('should not start OCPP 2.0 timer for OCPP 1.6 stations via dispatch', async t => {
         await withMockTimers(t, ['setInterval'], async () => {
           const { station: ocpp16Station } = createMockChargingStation({
             baseName: TEST_CHARGING_STATION_BASE_NAME,
diff --git a/tests/charging-station/ocpp/IdTagAuthorization.test.ts b/tests/charging-station/ocpp/IdTagAuthorization.test.ts
new file mode 100644 (file)
index 0000000..7e2c408
--- /dev/null
@@ -0,0 +1,298 @@
+/**
+ * @file Tests for IdTagAuthorization
+ * @description Verifies isIdTagAuthorized authorization function
+ *
+ * Covers:
+ * - isIdTagAuthorized — auth system for all OCPP versions
+ * - Connector state management based on authentication method
+ *
+ * Note: The auth subsystem (OCPPAuthService, strategies, adapters) has its own
+ * dedicated test suite in tests/charging-station/ocpp/auth/. These tests verify the
+ * wrapper/dispatch layer only — no overlap.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, describe, it } from 'node:test'
+
+import {
+  AuthContext,
+  AuthenticationMethod,
+  AuthorizationStatus,
+  OCPPAuthServiceFactory,
+} from '../../../src/charging-station/ocpp/auth/index.js'
+import { isIdTagAuthorized } from '../../../src/charging-station/ocpp/IdTagAuthorization.js'
+import { OCPPVersion } from '../../../src/types/index.js'
+import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
+import { createMockChargingStation } from '../ChargingStationTestUtils.js'
+import {
+  createMockAuthorizationResult,
+  createMockAuthService,
+} from './auth/helpers/MockFactories.js'
+
+/**
+ * Registers a mock auth service for the given station in OCPPAuthServiceFactory.
+ * @param station - Mock charging station instance
+ * @param overrides - Partial overrides for the mock auth service methods
+ * @returns The created mock auth service
+ */
+function injectMockAuthService (
+  station: ReturnType<typeof createMockChargingStation>['station'],
+  overrides?: Parameters<typeof createMockAuthService>[0]
+): ReturnType<typeof createMockAuthService> {
+  const stationId = station.stationInfo?.chargingStationId ?? 'unknown'
+  const mockService = createMockAuthService(overrides)
+  OCPPAuthServiceFactory.setInstanceForTesting(stationId, mockService)
+  return mockService
+}
+
+await describe('IdTagAuthorization', async () => {
+  afterEach(() => {
+    OCPPAuthServiceFactory.clearAllInstances()
+    standardCleanup()
+  })
+
+  await describe('isIdTagAuthorized', async () => {
+    await it('should return false when auth service rejects the tag', async () => {
+      // Arrange
+      const { station } = createMockChargingStation({
+        stationInfo: { remoteAuthorization: false },
+      })
+      injectMockAuthService(station, {
+        authorize: () =>
+          Promise.resolve(createMockAuthorizationResult({ status: AuthorizationStatus.INVALID })),
+      })
+
+      // Act
+      const result = await isIdTagAuthorized(station, 1, 'TAG-001')
+
+      // Assert
+      assert.strictEqual(result, false)
+    })
+
+    await it('should return true when auth service returns LOCAL_LIST accepted', async () => {
+      // Arrange
+      const { station } = createMockChargingStation()
+      injectMockAuthService(station, {
+        authorize: () =>
+          Promise.resolve(
+            createMockAuthorizationResult({
+              method: AuthenticationMethod.LOCAL_LIST,
+              status: AuthorizationStatus.ACCEPTED,
+            })
+          ),
+      })
+
+      // Act
+      const result = await isIdTagAuthorized(station, 1, 'TAG-001')
+
+      // Assert
+      assert.strictEqual(result, true)
+    })
+
+    await it('should set localAuthorizeIdTag when auth returns LOCAL_LIST method', async () => {
+      // Arrange
+      const { station } = createMockChargingStation()
+      injectMockAuthService(station, {
+        authorize: () =>
+          Promise.resolve(
+            createMockAuthorizationResult({
+              method: AuthenticationMethod.LOCAL_LIST,
+              status: AuthorizationStatus.ACCEPTED,
+            })
+          ),
+      })
+
+      // Act
+      await isIdTagAuthorized(station, 1, 'TAG-001')
+
+      // Assert
+      const connectorStatus = station.getConnectorStatus(1)
+      assert.ok(connectorStatus != null)
+      assert.strictEqual(connectorStatus.localAuthorizeIdTag, 'TAG-001')
+      assert.strictEqual(connectorStatus.idTagLocalAuthorized, true)
+    })
+
+    await it('should set idTagLocalAuthorized when auth returns CACHE method', async () => {
+      // Arrange
+      const { station } = createMockChargingStation()
+      injectMockAuthService(station, {
+        authorize: () =>
+          Promise.resolve(
+            createMockAuthorizationResult({
+              method: AuthenticationMethod.CACHE,
+              status: AuthorizationStatus.ACCEPTED,
+            })
+          ),
+      })
+
+      // Act
+      await isIdTagAuthorized(station, 1, 'TAG-CACHED')
+
+      // Assert
+      const connectorStatus = station.getConnectorStatus(1)
+      assert.ok(connectorStatus != null)
+      assert.strictEqual(connectorStatus.localAuthorizeIdTag, 'TAG-CACHED')
+      assert.strictEqual(connectorStatus.idTagLocalAuthorized, true)
+    })
+
+    await it('should authorize remotely when auth service returns REMOTE_AUTHORIZATION accepted', async () => {
+      // Arrange
+      const { station } = createMockChargingStation({
+        stationInfo: { remoteAuthorization: true },
+      })
+      injectMockAuthService(station, {
+        authorize: () =>
+          Promise.resolve(
+            createMockAuthorizationResult({
+              method: AuthenticationMethod.REMOTE_AUTHORIZATION,
+              status: AuthorizationStatus.ACCEPTED,
+            })
+          ),
+      })
+
+      // Act
+      const result = await isIdTagAuthorized(station, 1, 'TAG-001')
+
+      // Assert
+      assert.strictEqual(result, true)
+    })
+
+    await it('should not set localAuthorizeIdTag when REMOTE_AUTHORIZATION method', async () => {
+      // Arrange
+      const { station } = createMockChargingStation({
+        stationInfo: { remoteAuthorization: true },
+      })
+      injectMockAuthService(station, {
+        authorize: () =>
+          Promise.resolve(
+            createMockAuthorizationResult({
+              method: AuthenticationMethod.REMOTE_AUTHORIZATION,
+              status: AuthorizationStatus.ACCEPTED,
+            })
+          ),
+      })
+
+      // Act
+      await isIdTagAuthorized(station, 1, 'TAG-001')
+
+      // Assert
+      const connectorStatus = station.getConnectorStatus(1)
+      assert.ok(connectorStatus != null)
+      assert.strictEqual(connectorStatus.localAuthorizeIdTag, undefined)
+      assert.notStrictEqual(connectorStatus.idTagLocalAuthorized, true)
+    })
+
+    await it('should return false when remote authorization rejects the tag', async () => {
+      // Arrange
+      const { station } = createMockChargingStation({
+        stationInfo: { remoteAuthorization: true },
+      })
+      injectMockAuthService(station, {
+        authorize: () =>
+          Promise.resolve(
+            createMockAuthorizationResult({
+              method: AuthenticationMethod.REMOTE_AUTHORIZATION,
+              status: AuthorizationStatus.BLOCKED,
+            })
+          ),
+      })
+
+      // Act
+      const result = await isIdTagAuthorized(station, 1, 'TAG-999')
+
+      // Assert
+      assert.strictEqual(result, false)
+    })
+
+    await it('should return true but not set connector state for non-existent connector', async () => {
+      // Arrange
+      const { station } = createMockChargingStation()
+      injectMockAuthService(station, {
+        authorize: () =>
+          Promise.resolve(
+            createMockAuthorizationResult({
+              method: AuthenticationMethod.LOCAL_LIST,
+              status: AuthorizationStatus.ACCEPTED,
+            })
+          ),
+      })
+
+      // Act
+      const result = await isIdTagAuthorized(station, 99, 'TAG-001')
+
+      // Assert
+      assert.strictEqual(result, true)
+      const connectorStatus = station.getConnectorStatus(99)
+      assert.strictEqual(connectorStatus, undefined)
+    })
+
+    await it('should set localAuthorizeIdTag when auth returns OFFLINE_FALLBACK method', async () => {
+      // Arrange
+      const { station } = createMockChargingStation()
+      injectMockAuthService(station, {
+        authorize: () =>
+          Promise.resolve(
+            createMockAuthorizationResult({
+              method: AuthenticationMethod.OFFLINE_FALLBACK,
+              status: AuthorizationStatus.ACCEPTED,
+            })
+          ),
+      })
+
+      // Act
+      await isIdTagAuthorized(station, 1, 'TAG-OFFLINE')
+
+      // Assert
+      const connectorStatus = station.getConnectorStatus(1)
+      assert.ok(connectorStatus != null)
+      assert.strictEqual(connectorStatus.localAuthorizeIdTag, 'TAG-OFFLINE')
+      assert.strictEqual(connectorStatus.idTagLocalAuthorized, true)
+    })
+
+    await it('should return false when auth service throws an error', async () => {
+      // Arrange
+      const { station } = createMockChargingStation()
+      injectMockAuthService(station, {
+        authorize: () => Promise.reject(new Error('Test auth service error')),
+      })
+
+      // Act
+      const result = await isIdTagAuthorized(station, 1, 'TAG-ERROR')
+
+      // Assert
+      assert.strictEqual(result, false)
+    })
+
+    await it('should accept explicit auth context parameter', async () => {
+      // Arrange
+      const { station } = createMockChargingStation()
+      let capturedContext: string | undefined
+      injectMockAuthService(station, {
+        authorize: (request: { context?: string }) => {
+          capturedContext = request.context
+          return Promise.resolve(
+            createMockAuthorizationResult({
+              method: AuthenticationMethod.LOCAL_LIST,
+              status: AuthorizationStatus.ACCEPTED,
+            })
+          )
+        },
+      })
+
+      // Act
+      await isIdTagAuthorized(station, 1, 'TAG-001', AuthContext.REMOTE_START)
+
+      // Assert
+      assert.strictEqual(capturedContext, 'RemoteStart')
+    })
+
+    await it('should return false when no auth service is registered for station', async () => {
+      const { station } = createMockChargingStation({
+        ocppVersion: OCPPVersion.VERSION_20,
+      })
+
+      const result = await isIdTagAuthorized(station, 1, 'TAG-001')
+      assert.strictEqual(result, false)
+    })
+  })
+})
diff --git a/tests/charging-station/ocpp/OCPPServiceUtils-authorization.test.ts b/tests/charging-station/ocpp/OCPPServiceUtils-authorization.test.ts
deleted file mode 100644 (file)
index bf99cb9..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-/**
- * @file Tests for OCPPServiceUtils authorization wrapper functions
- * @description Verifies isIdTagAuthorized and isIdTagAuthorizedUnified functions
- *
- * Covers:
- * - isIdTagAuthorized — OCPP 1.6 legacy authorization (local auth list + remote authorization)
- * - isIdTagAuthorizedUnified — OCPP 2.0+ unified auth system with fallback
- *
- * Note: The unified auth subsystem (OCPPAuthService, strategies, adapters) has its own
- * dedicated test suite in tests/charging-station/ocpp/auth/. These tests verify the
- * wrapper/dispatch layer only — no overlap.
- */
-
-import assert from 'node:assert/strict'
-import { afterEach, describe, it } from 'node:test'
-
-import { getIdTagsFile } from '../../../src/charging-station/Helpers.js'
-import {
-  isIdTagAuthorized,
-  isIdTagAuthorizedUnified,
-} from '../../../src/charging-station/ocpp/OCPPServiceUtils.js'
-import { AuthorizationStatus, OCPPVersion } from '../../../src/types/index.js'
-import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
-import { createMockChargingStation } from '../ChargingStationTestUtils.js'
-
-interface StationLocalAuthOverrides {
-  getLocalAuthListEnabled: () => boolean
-  hasIdTags: () => boolean
-}
-
-/**
- * Configures local authorization on a mock station with the given id tags.
- * @param station - The mock station to configure
- * @param mocks - The mock infrastructure (for idTagsCache injection)
- * @param tags - Array of id tags to register in the local auth list
- */
-function setupLocalAuth (
-  station: ReturnType<typeof createMockChargingStation>['station'],
-  mocks: ReturnType<typeof createMockChargingStation>['mocks'],
-  tags: string[]
-): void {
-  const stationOverrides = station as unknown as StationLocalAuthOverrides
-  stationOverrides.getLocalAuthListEnabled = () => true
-  stationOverrides.hasIdTags = () => true
-  const stationInfo = station.stationInfo
-  if (stationInfo != null) {
-    const resolvedPath = getIdTagsFile(stationInfo)
-    if (resolvedPath != null) {
-      mocks.idTagsCache.setIdTags(resolvedPath, tags)
-    }
-  }
-}
-
-await describe('OCPPServiceUtils — authorization wrappers', async () => {
-  afterEach(() => {
-    standardCleanup()
-  })
-
-  await describe('isIdTagAuthorized (OCPP 1.6 legacy)', async () => {
-    await it('should return false when local and remote auth are both disabled', async () => {
-      const { station } = createMockChargingStation({
-        stationInfo: { remoteAuthorization: false },
-      })
-      const result = await isIdTagAuthorized(station, 1, 'TAG-001')
-      assert.strictEqual(result, false)
-    })
-
-    await it('should authorize locally when tag is in local auth list', async () => {
-      const { mocks, station } = createMockChargingStation({
-        stationInfo: { idTagsFile: 'test-idtags.json' },
-      })
-      setupLocalAuth(station, mocks, ['TAG-001', 'TAG-002'])
-
-      const result = await isIdTagAuthorized(station, 1, 'TAG-001')
-      assert.strictEqual(result, true)
-    })
-
-    await it('should set localAuthorizeIdTag and idTagLocalAuthorized on local auth success', async () => {
-      const { mocks, station } = createMockChargingStation({
-        stationInfo: { idTagsFile: 'test-idtags.json' },
-      })
-      setupLocalAuth(station, mocks, ['TAG-001'])
-
-      await isIdTagAuthorized(station, 1, 'TAG-001')
-
-      const connectorStatus = station.getConnectorStatus(1)
-      assert.strictEqual(connectorStatus?.localAuthorizeIdTag, 'TAG-001')
-      assert.strictEqual(connectorStatus.idTagLocalAuthorized, true)
-    })
-
-    await it('should authorize remotely when local auth is disabled and remote returns accepted', async () => {
-      const { station } = createMockChargingStation({
-        ocppRequestService: {
-          requestHandler: () =>
-            Promise.resolve({
-              idTagInfo: { status: AuthorizationStatus.ACCEPTED },
-            }),
-        },
-        stationInfo: { remoteAuthorization: true },
-      })
-
-      const result = await isIdTagAuthorized(station, 1, 'TAG-001')
-      assert.strictEqual(result, true)
-    })
-
-    await it('should return false when remote authorization rejects the tag', async () => {
-      const { station } = createMockChargingStation({
-        ocppRequestService: {
-          requestHandler: () =>
-            Promise.resolve({
-              idTagInfo: { status: AuthorizationStatus.BLOCKED },
-            }),
-        },
-        stationInfo: { remoteAuthorization: true },
-      })
-
-      const result = await isIdTagAuthorized(station, 1, 'TAG-999')
-      assert.strictEqual(result, false)
-    })
-
-    await it('should return false for non-existent connector even with local auth enabled', async () => {
-      const { mocks, station } = createMockChargingStation({
-        stationInfo: { idTagsFile: 'test-idtags.json', remoteAuthorization: false },
-      })
-      setupLocalAuth(station, mocks, ['TAG-001'])
-
-      const result = await isIdTagAuthorized(station, 99, 'TAG-001')
-      assert.strictEqual(result, false)
-    })
-  })
-
-  await describe('isIdTagAuthorizedUnified', async () => {
-    await it('should fall back to legacy auth for OCPP 1.6 station', async () => {
-      const { mocks, station } = createMockChargingStation({
-        stationInfo: { idTagsFile: 'test-idtags.json' },
-      })
-      setupLocalAuth(station, mocks, ['TAG-001'])
-
-      const result = await isIdTagAuthorizedUnified(station, 1, 'TAG-001')
-      assert.strictEqual(result, true)
-    })
-
-    await it('should return false on auth error for OCPP 2.0 station', async () => {
-      const { station } = createMockChargingStation({
-        ocppVersion: OCPPVersion.VERSION_20,
-      })
-
-      const result = await isIdTagAuthorizedUnified(station, 1, 'TAG-001')
-      assert.strictEqual(result, false)
-    })
-
-    await it('should attempt unified auth service for OCPP 2.0.1 station', async () => {
-      const { station } = createMockChargingStation({
-        ocppVersion: OCPPVersion.VERSION_201,
-      })
-
-      const result = await isIdTagAuthorizedUnified(station, 1, 'TAG-001')
-      assert.strictEqual(result, false)
-    })
-  })
-})
index 4e9f8d669104dcd819a5c641b765c6b187c4b437..39ea0e715cb0d404e8d1bcc0f5e956b9dc59a024 100644 (file)
@@ -220,7 +220,7 @@ await describe('OCPP Authentication', async () => {
 
   await describe('Cache Spec Compliance Integration', async () => {
     // C10.INT.01 - Cache wiring regression
-    await it('C10.INT.01: OCPPAuthServiceImpl wires auth cache into local strategy', async () => {
+    await it('C10.INT.01: OCPPAuthServiceImpl wires auth cache into local strategy', () => {
       const result16 = createMockChargingStation({
         baseName: 'TEST_CACHE_WIRING',
         connectorsCount: 1,
@@ -231,7 +231,7 @@ await describe('OCPP Authentication', async () => {
         },
       })
       const service = new OCPPAuthServiceImpl(result16.station)
-      await service.initialize()
+      service.initialize()
 
       const localStrategy = service.getStrategy('local') as LocalAuthStrategy | undefined
       assert.notStrictEqual(localStrategy, undefined)
index 52a3fcbb699e8fcbe2179e18e47022e702dd3685..5340bde99cc36fa43db04bbef5feeb8054dbb139 100644 (file)
@@ -64,20 +64,20 @@ await describe('OCPP16AuthAdapter', async () => {
     })
   })
 
-  await describe('convertToUnifiedIdentifier', async () => {
-    await it('should convert OCPP 1.6 idTag to unified identifier', () => {
+  await describe('convertToIdentifier', async () => {
+    await it('should convert OCPP 1.6 idTag to identifier', () => {
       const idTag = 'TEST_ID_TAG'
-      const result = adapter.convertToUnifiedIdentifier(idTag)
+      const result = adapter.convertToIdentifier(idTag)
 
       const expected = createMockIdentifier(idTag)
       assert.strictEqual(result.value, expected.value)
       assert.strictEqual(result.type, expected.type)
     })
 
-    await it('should include additional data in unified identifier', () => {
+    await it('should include additional data in identifier', () => {
       const idTag = 'TEST_ID_TAG'
       const additionalData = { customField: 'customValue', parentId: 'PARENT_TAG' }
-      const result = adapter.convertToUnifiedIdentifier(idTag, additionalData)
+      const result = adapter.convertToIdentifier(idTag, additionalData)
 
       assert.strictEqual(result.value, idTag)
       assert.strictEqual(result.parentId, 'PARENT_TAG')
@@ -85,11 +85,11 @@ await describe('OCPP16AuthAdapter', async () => {
     })
   })
 
-  await describe('convertFromUnifiedIdentifier', async () => {
-    await it('should convert unified identifier to OCPP 1.6 idTag', () => {
+  await describe('convertFromIdentifier', async () => {
+    await it('should convert identifier to OCPP 1.6 idTag', () => {
       const identifier = createMockIdentifier('TEST_ID_TAG')
 
-      const result = adapter.convertFromUnifiedIdentifier(identifier)
+      const result = adapter.convertFromIdentifier(identifier)
       assert.strictEqual(result, 'TEST_ID_TAG')
     })
   })
@@ -276,7 +276,7 @@ await describe('OCPP16AuthAdapter', async () => {
   })
 
   await describe('convertToOCPP16Response', async () => {
-    await it('should convert unified result to OCPP 1.6 response', () => {
+    await it('should convert result to OCPP 1.6 response', () => {
       const expiryDate = new Date()
       const result = createMockAuthorizationResult({
         expiryDate,
index a9e295107a121d49cc466f7bbe74396e13a2d950..88c6ab03ca802795dc191923107aab166ddade48 100644 (file)
@@ -50,27 +50,26 @@ await describe('OCPP20AuthAdapter', async () => {
     })
   })
 
-  await describe('convertToUnifiedIdentifier', async () => {
-    await it('should convert OCPP 2.0 IdToken object to unified identifier', () => {
+  await describe('convertToIdentifier', async () => {
+    await it('should convert OCPP 2.0 IdToken object to identifier', () => {
       const idToken = {
         idToken: 'TEST_TOKEN',
         type: OCPP20IdTokenEnumType.Central,
       }
 
-      const result = adapter.convertToUnifiedIdentifier(idToken)
+      const result = adapter.convertToIdentifier(idToken)
       const expected = createMockIdentifier('TEST_TOKEN')
 
       assert.strictEqual(result.value, expected.value)
-      assert.strictEqual(result.type, IdentifierType.ID_TAG)
+      assert.strictEqual(result.type, IdentifierType.CENTRAL)
       assert.strictEqual(result.additionalInfo?.ocpp20Type, OCPP20IdTokenEnumType.Central)
     })
 
-    await it('should convert string to unified identifier', () => {
-      const result = adapter.convertToUnifiedIdentifier('STRING_TOKEN')
-      const expected = createMockIdentifier('STRING_TOKEN')
+    await it('should convert string to identifier', () => {
+      const result = adapter.convertToIdentifier('STRING_TOKEN')
 
-      assert.strictEqual(result.value, expected.value)
-      assert.strictEqual(result.type, expected.type)
+      assert.strictEqual(result.value, 'STRING_TOKEN')
+      assert.strictEqual(result.type, IdentifierType.CENTRAL)
     })
 
     await it('should handle eMAID type correctly', () => {
@@ -79,7 +78,7 @@ await describe('OCPP20AuthAdapter', async () => {
         type: OCPP20IdTokenEnumType.eMAID,
       }
 
-      const result = adapter.convertToUnifiedIdentifier(idToken)
+      const result = adapter.convertToIdentifier(idToken)
 
       assert.strictEqual(result.value, 'EMAID123')
       assert.strictEqual(result.type, IdentifierType.E_MAID)
@@ -95,7 +94,7 @@ await describe('OCPP20AuthAdapter', async () => {
         type: OCPP20IdTokenEnumType.Local,
       }
 
-      const result = adapter.convertToUnifiedIdentifier(idToken)
+      const result = adapter.convertToIdentifier(idToken)
 
       assert.notStrictEqual(result.additionalInfo, undefined)
       assert.notStrictEqual(result.additionalInfo?.info_0, undefined)
@@ -103,11 +102,11 @@ await describe('OCPP20AuthAdapter', async () => {
     })
   })
 
-  await describe('convertFromUnifiedIdentifier', async () => {
-    await it('should convert unified identifier to OCPP 2.0 IdToken', () => {
+  await describe('convertFromIdentifier', async () => {
+    await it('should convert identifier to OCPP 2.0 IdToken', () => {
       const identifier = createMockIdentifier('CENTRAL_TOKEN', IdentifierType.CENTRAL)
 
-      const result = adapter.convertFromUnifiedIdentifier(identifier)
+      const result = adapter.convertFromIdentifier(identifier)
 
       assert.strictEqual(result.idToken, 'CENTRAL_TOKEN')
       assert.strictEqual(result.type, OCPP20IdTokenEnumType.Central)
@@ -116,7 +115,7 @@ await describe('OCPP20AuthAdapter', async () => {
     await it('should map E_MAID type correctly', () => {
       const identifier = createMockIdentifier('EMAID_TOKEN', IdentifierType.E_MAID)
 
-      const result = adapter.convertFromUnifiedIdentifier(identifier)
+      const result = adapter.convertFromIdentifier(identifier)
 
       assert.strictEqual(result.idToken, 'EMAID_TOKEN')
       assert.strictEqual(result.type, OCPP20IdTokenEnumType.eMAID)
@@ -125,7 +124,7 @@ await describe('OCPP20AuthAdapter', async () => {
     await it('should handle ID_TAG to Local mapping', () => {
       const identifier = createMockIdentifier('LOCAL_TAG')
 
-      const result = adapter.convertFromUnifiedIdentifier(identifier)
+      const result = adapter.convertFromIdentifier(identifier)
 
       assert.strictEqual(result.type, OCPP20IdTokenEnumType.Local)
     })
@@ -341,7 +340,7 @@ await describe('OCPP20AuthAdapter', async () => {
   })
 
   await describe('convertToOCPP20Response', async () => {
-    await it('should convert unified ACCEPTED status to OCPP 2.0 Accepted', () => {
+    await it('should convert ACCEPTED status to OCPP 2.0 Accepted', () => {
       const result = createMockAuthorizationResult({
         method: AuthenticationMethod.REMOTE_AUTHORIZATION,
       })
@@ -350,7 +349,7 @@ await describe('OCPP20AuthAdapter', async () => {
       assert.strictEqual(response, RequestStartStopStatusEnumType.Accepted)
     })
 
-    await it('should convert unified rejection statuses to OCPP 2.0 Rejected', () => {
+    await it('should convert rejection statuses to OCPP 2.0 Rejected', () => {
       const statuses = [
         AuthorizationStatus.BLOCKED,
         AuthorizationStatus.INVALID,
index 0fc91b860784c5a3af3f44c8959d26b9ef24b667..07b1a7d59e64f07687c53cd81e60823477ec7d1c 100644 (file)
@@ -20,51 +20,51 @@ await describe('AuthComponentFactory', async () => {
   })
 
   await describe('createAdapter', async () => {
-    await it('should create OCPP 1.6 adapter', async () => {
+    await it('should create OCPP 1.6 adapter', () => {
       const { station: chargingStation } = createMockChargingStation({
         stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
       })
-      const adapter = await AuthComponentFactory.createAdapter(chargingStation)
+      const adapter = AuthComponentFactory.createAdapter(chargingStation)
 
       assert.notStrictEqual(adapter, undefined)
       assert.strictEqual(adapter.ocppVersion, OCPPVersion.VERSION_16)
     })
 
-    await it('should create OCPP 2.0 adapter', async () => {
+    await it('should create OCPP 2.0 adapter', () => {
       const { station: chargingStation } = createMockChargingStation({
         stationInfo: { ocppVersion: OCPPVersion.VERSION_20 },
       })
-      const adapter = await AuthComponentFactory.createAdapter(chargingStation)
+      const adapter = AuthComponentFactory.createAdapter(chargingStation)
 
       assert.notStrictEqual(adapter, undefined)
       assert.strictEqual(adapter.ocppVersion, OCPPVersion.VERSION_20)
     })
 
-    await it('should create OCPP 2.0.1 adapter', async () => {
+    await it('should create OCPP 2.0.1 adapter', () => {
       const { station: chargingStation } = createMockChargingStation({
         stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
       })
-      const adapter = await AuthComponentFactory.createAdapter(chargingStation)
+      const adapter = AuthComponentFactory.createAdapter(chargingStation)
 
       assert.notStrictEqual(adapter, undefined)
       assert.strictEqual(adapter.ocppVersion, OCPPVersion.VERSION_20)
     })
 
-    await it('should throw error for unsupported version', async () => {
+    await it('should throw error for unsupported version', () => {
       const { station: chargingStation } = createMockChargingStation({
         stationInfo: { ocppVersion: 'VERSION_15' as OCPPVersion },
       })
 
-      await assert.rejects(AuthComponentFactory.createAdapter(chargingStation), {
+      assert.throws(() => AuthComponentFactory.createAdapter(chargingStation), {
         message: /Unsupported OCPP version/,
       })
     })
 
-    await it('should throw error when no OCPP version', async () => {
+    await it('should throw error when no OCPP version', () => {
       const { station: chargingStation } = createMockChargingStation()
       chargingStation.stationInfo = undefined
 
-      await assert.rejects(AuthComponentFactory.createAdapter(chargingStation), {
+      assert.throws(() => AuthComponentFactory.createAdapter(chargingStation), {
         message: /OCPP version not found/,
       })
     })
@@ -112,7 +112,7 @@ await describe('AuthComponentFactory', async () => {
   })
 
   await describe('createLocalStrategy', async () => {
-    await it('should return undefined when local auth list disabled', async () => {
+    await it('should return undefined when local auth list disabled', () => {
       const config: AuthConfiguration = {
         allowOfflineTxForUnknownId: false,
         authorizationCacheEnabled: false,
@@ -123,12 +123,12 @@ await describe('AuthComponentFactory', async () => {
         offlineAuthorizationEnabled: false,
       }
 
-      const result = await AuthComponentFactory.createLocalStrategy(undefined, undefined, config)
+      const result = AuthComponentFactory.createLocalStrategy(undefined, undefined, config)
 
       assert.strictEqual(result, undefined)
     })
 
-    await it('should create local strategy when enabled', async () => {
+    await it('should create local strategy when enabled', () => {
       const config: AuthConfiguration = {
         allowOfflineTxForUnknownId: false,
         authorizationCacheEnabled: false,
@@ -139,7 +139,7 @@ await describe('AuthComponentFactory', async () => {
         offlineAuthorizationEnabled: false,
       }
 
-      const result = await AuthComponentFactory.createLocalStrategy(undefined, undefined, config)
+      const result = AuthComponentFactory.createLocalStrategy(undefined, undefined, config)
 
       assert.notStrictEqual(result, undefined)
       if (result) {
@@ -149,11 +149,11 @@ await describe('AuthComponentFactory', async () => {
   })
 
   await describe('createRemoteStrategy', async () => {
-    await it('should return undefined when remote auth disabled', async () => {
+    await it('should return undefined when remote auth disabled', () => {
       const { station: chargingStation } = createMockChargingStation({
         stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
       })
-      const adapter = await AuthComponentFactory.createAdapter(chargingStation)
+      const adapter = AuthComponentFactory.createAdapter(chargingStation)
       const config: AuthConfiguration = {
         allowOfflineTxForUnknownId: false,
         authorizationCacheEnabled: false,
@@ -165,16 +165,16 @@ await describe('AuthComponentFactory', async () => {
         remoteAuthorization: false,
       }
 
-      const result = await AuthComponentFactory.createRemoteStrategy(adapter, undefined, config)
+      const result = AuthComponentFactory.createRemoteStrategy(adapter, undefined, config)
 
       assert.strictEqual(result, undefined)
     })
 
-    await it('should create remote strategy when enabled', async () => {
+    await it('should create remote strategy when enabled', () => {
       const { station: chargingStation } = createMockChargingStation({
         stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
       })
-      const adapter = await AuthComponentFactory.createAdapter(chargingStation)
+      const adapter = AuthComponentFactory.createAdapter(chargingStation)
       const config: AuthConfiguration = {
         allowOfflineTxForUnknownId: false,
         authorizationCacheEnabled: false,
@@ -186,7 +186,7 @@ await describe('AuthComponentFactory', async () => {
         remoteAuthorization: true,
       }
 
-      const result = await AuthComponentFactory.createRemoteStrategy(adapter, undefined, config)
+      const result = AuthComponentFactory.createRemoteStrategy(adapter, undefined, config)
 
       assert.notStrictEqual(result, undefined)
       if (result) {
@@ -196,11 +196,11 @@ await describe('AuthComponentFactory', async () => {
   })
 
   await describe('createCertificateStrategy', async () => {
-    await it('should create certificate strategy', async () => {
+    await it('should create certificate strategy', () => {
       const { station: chargingStation } = createMockChargingStation({
         stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
       })
-      const adapter = await AuthComponentFactory.createAdapter(chargingStation)
+      const adapter = AuthComponentFactory.createAdapter(chargingStation)
       const config: AuthConfiguration = {
         allowOfflineTxForUnknownId: false,
         authorizationCacheEnabled: false,
@@ -211,7 +211,7 @@ await describe('AuthComponentFactory', async () => {
         offlineAuthorizationEnabled: false,
       }
 
-      const result = await AuthComponentFactory.createCertificateStrategy(
+      const result = AuthComponentFactory.createCertificateStrategy(
         chargingStation,
         adapter,
         config
@@ -223,11 +223,11 @@ await describe('AuthComponentFactory', async () => {
   })
 
   await describe('createStrategies', async () => {
-    await it('should create only certificate strategy by default', async () => {
+    await it('should create only certificate strategy by default', () => {
       const { station: chargingStation } = createMockChargingStation({
         stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
       })
-      const adapter = await AuthComponentFactory.createAdapter(chargingStation)
+      const adapter = AuthComponentFactory.createAdapter(chargingStation)
       const config: AuthConfiguration = {
         allowOfflineTxForUnknownId: false,
         authorizationCacheEnabled: false,
@@ -238,7 +238,7 @@ await describe('AuthComponentFactory', async () => {
         offlineAuthorizationEnabled: false,
       }
 
-      const result = await AuthComponentFactory.createStrategies(
+      const result = AuthComponentFactory.createStrategies(
         chargingStation,
         adapter,
         undefined,
@@ -250,11 +250,11 @@ await describe('AuthComponentFactory', async () => {
       assert.strictEqual(result[0].priority, 3)
     })
 
-    await it('should create and sort all strategies when enabled', async () => {
+    await it('should create and sort all strategies when enabled', () => {
       const { station: chargingStation } = createMockChargingStation({
         stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
       })
-      const adapter = await AuthComponentFactory.createAdapter(chargingStation)
+      const adapter = AuthComponentFactory.createAdapter(chargingStation)
       const config: AuthConfiguration = {
         allowOfflineTxForUnknownId: false,
         authorizationCacheEnabled: false,
@@ -266,7 +266,7 @@ await describe('AuthComponentFactory', async () => {
         remoteAuthorization: true,
       }
 
-      const result = await AuthComponentFactory.createStrategies(
+      const result = AuthComponentFactory.createStrategies(
         chargingStation,
         adapter,
         undefined,
index edd7447cd70bfdbf5edc783ef11fadd941d21733..cfc863a2c772a51ddd7b5121149d08b49db3969a 100644 (file)
@@ -20,8 +20,8 @@ import {
   type AuthorizationResult,
   AuthorizationStatus,
   type AuthRequest,
+  type Identifier,
   IdentifierType,
-  type UnifiedIdentifier,
 } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
 import { OCPPVersion } from '../../../../../src/types/index.js'
 import { OCPP20IdTokenEnumType, type OCPP20IdTokenType } from '../../../../../src/types/index.js'
@@ -32,15 +32,15 @@ import { OCPP20IdTokenEnumType, type OCPP20IdTokenType } from '../../../../../sr
  */
 
 /**
- * Create a mock UnifiedIdentifier for any OCPP version.
+ * Create a mock Identifier for any OCPP version.
  * @param value - Identifier token value (defaults to 'TEST-TAG-001')
  * @param type - Identifier type enum value (defaults to ID_TAG)
- * @returns Mock UnifiedIdentifier configured for testing
+ * @returns Mock Identifier configured for testing
  */
 export const createMockIdentifier = (
   value = 'TEST-TAG-001',
   type: IdentifierType = IdentifierType.ID_TAG
-): UnifiedIdentifier => ({
+): Identifier => ({
   type,
   value,
 })
@@ -137,7 +137,7 @@ export const createMockAuthService = (overrides?: Partial<OCPPAuthService>): OCP
     invalidateCache: () => {
       /* empty */
     },
-    isLocallyAuthorized: (_identifier: UnifiedIdentifier, _connectorId?: number) =>
+    isLocallyAuthorized: (_identifier: Identifier, _connectorId?: number) =>
       new Promise<AuthorizationResult | undefined>(resolve => {
         resolve(undefined)
       }),
@@ -194,7 +194,7 @@ export const createMockOCPPAdapter = (
   ocppVersion: OCPPVersion,
   overrides?: Partial<OCPPAuthAdapter>
 ): OCPPAuthAdapter => ({
-  authorizeRemote: (_identifier: UnifiedIdentifier) =>
+  authorizeRemote: (_identifier: Identifier) =>
     new Promise<AuthorizationResult>(resolve => {
       resolve(
         createMockAuthorizationResult({
@@ -202,11 +202,11 @@ export const createMockOCPPAdapter = (
         })
       )
     }),
-  convertFromUnifiedIdentifier: (identifier: UnifiedIdentifier) =>
+  convertFromIdentifier: (identifier: Identifier) =>
     ocppVersion === OCPPVersion.VERSION_16
       ? identifier.value
       : { idToken: identifier.value, type: OCPP20IdTokenEnumType.Central },
-  convertToUnifiedIdentifier: (identifier: OCPP20IdTokenType | string) => ({
+  convertToIdentifier: (identifier: OCPP20IdTokenType | string) => ({
     type: IdentifierType.ID_TAG,
     value: typeof identifier === 'string' ? identifier : identifier.idToken,
   }),
index 93ca23e72461d91ac9149a5fbadbd19e77e7cb2e..83c589261f5393bdbaee3089b844989dc048a8b9 100644 (file)
@@ -28,41 +28,44 @@ await describe('OCPPAuthServiceFactory', async () => {
       mockStation20 = createMockAuthServiceTestStation('getInstance-20', OCPPVersion.VERSION_20)
     })
 
-    await it('should create a new instance for a charging station', async () => {
-      const authService = await OCPPAuthServiceFactory.getInstance(mockStation16)
+    await it('should create a new instance for a charging station', () => {
+      const authService = OCPPAuthServiceFactory.getInstance(mockStation16)
 
       assert.notStrictEqual(authService, undefined)
       assert.strictEqual(typeof authService.authorize, 'function')
       assert.strictEqual(typeof authService.getConfiguration, 'function')
     })
 
-    await it('should return cached instance for same charging station', async () => {
-      const authService1 = await OCPPAuthServiceFactory.getInstance(mockStation20)
-      const authService2 = await OCPPAuthServiceFactory.getInstance(mockStation20)
+    await it('should return cached instance for same charging station', () => {
+      const authService1 = OCPPAuthServiceFactory.getInstance(mockStation20)
+      const authService2 = OCPPAuthServiceFactory.getInstance(mockStation20)
 
       assert.strictEqual(authService1, authService2)
     })
 
-    await it('should create different instances for different charging stations', async () => {
-      const authService1 = await OCPPAuthServiceFactory.getInstance(mockStation16)
-      const authService2 = await OCPPAuthServiceFactory.getInstance(mockStation20)
+    await it('should create different instances for different charging stations', () => {
+      const authService1 = OCPPAuthServiceFactory.getInstance(mockStation16)
+      const authService2 = OCPPAuthServiceFactory.getInstance(mockStation20)
 
       assert.notStrictEqual(authService1, authService2)
     })
 
-    await it('should throw error for charging station without stationInfo', async () => {
+    await it('should throw error for charging station without stationInfo', () => {
       const mockStation = {
         logPrefix: () => '[TEST-CS-UNKNOWN]',
         stationInfo: undefined,
       } as unknown as ChargingStation
 
-      try {
-        await OCPPAuthServiceFactory.getInstance(mockStation)
-        assert.fail('Expected getInstance to throw for station without stationInfo')
-      } catch (error) {
-        assert.ok(error instanceof Error)
-        assert.ok(error.message.includes('OCPP version not found in charging station'))
-      }
+      assert.throws(
+        () => {
+          OCPPAuthServiceFactory.getInstance(mockStation)
+        },
+        (error: unknown) => {
+          assert.ok(error instanceof Error)
+          assert.ok(error.message.includes('OCPP version not found in charging station'))
+          return true
+        }
+      )
     })
   })
 
@@ -75,18 +78,18 @@ await describe('OCPPAuthServiceFactory', async () => {
       mockStation20 = createMockAuthServiceTestStation('createInstance-20', OCPPVersion.VERSION_20)
     })
 
-    await it('should create a new uncached instance', async () => {
-      const authService1 = await OCPPAuthServiceFactory.createInstance(mockStation16)
-      const authService2 = await OCPPAuthServiceFactory.createInstance(mockStation16)
+    await it('should create a new uncached instance', () => {
+      const authService1 = OCPPAuthServiceFactory.createInstance(mockStation16)
+      const authService2 = OCPPAuthServiceFactory.createInstance(mockStation16)
 
       assert.notStrictEqual(authService1, undefined)
       assert.notStrictEqual(authService2, undefined)
       assert.notStrictEqual(authService1, authService2)
     })
 
-    await it('should not cache created instances', async () => {
+    await it('should not cache created instances', () => {
       const initialCount = OCPPAuthServiceFactory.getCachedInstanceCount()
-      await OCPPAuthServiceFactory.createInstance(mockStation20)
+      OCPPAuthServiceFactory.createInstance(mockStation20)
       const finalCount = OCPPAuthServiceFactory.getCachedInstanceCount()
 
       assert.strictEqual(finalCount, initialCount)
@@ -102,15 +105,15 @@ await describe('OCPPAuthServiceFactory', async () => {
       mockStation20 = createMockAuthServiceTestStation('clearInstance-20', OCPPVersion.VERSION_20)
     })
 
-    await it('should clear cached instance for a charging station', async () => {
+    await it('should clear cached instance for a charging station', () => {
       // Create and cache instance
-      const authService1 = await OCPPAuthServiceFactory.getInstance(mockStation16)
+      const authService1 = OCPPAuthServiceFactory.getInstance(mockStation16)
 
       // Clear the cache
       OCPPAuthServiceFactory.clearInstance(mockStation16)
 
       // Get instance again - should be a new instance
-      const authService2 = await OCPPAuthServiceFactory.getInstance(mockStation16)
+      const authService2 = OCPPAuthServiceFactory.getInstance(mockStation16)
 
       assert.notStrictEqual(authService1, authService2)
     })
@@ -131,10 +134,10 @@ await describe('OCPPAuthServiceFactory', async () => {
       mockStation20 = createMockAuthServiceTestStation('clearAll-20', OCPPVersion.VERSION_20)
     })
 
-    await it('should clear all cached instances', async () => {
+    await it('should clear all cached instances', () => {
       // Create multiple instances
-      await OCPPAuthServiceFactory.getInstance(mockStation16)
-      await OCPPAuthServiceFactory.getInstance(mockStation20)
+      OCPPAuthServiceFactory.getInstance(mockStation16)
+      OCPPAuthServiceFactory.getInstance(mockStation20)
 
       // Clear all
       OCPPAuthServiceFactory.clearAllInstances()
@@ -155,17 +158,17 @@ await describe('OCPPAuthServiceFactory', async () => {
       mockStation20 = createMockAuthServiceTestStation('count-20', OCPPVersion.VERSION_20)
     })
 
-    await it('should return the number of cached instances', async () => {
+    await it('should return the number of cached instances', () => {
       assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 0)
 
-      await OCPPAuthServiceFactory.getInstance(mockStation16)
+      OCPPAuthServiceFactory.getInstance(mockStation16)
       assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 1)
 
-      await OCPPAuthServiceFactory.getInstance(mockStation20)
+      OCPPAuthServiceFactory.getInstance(mockStation20)
       assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 2)
 
       // Getting same instance should not increase count
-      await OCPPAuthServiceFactory.getInstance(mockStation16)
+      OCPPAuthServiceFactory.getInstance(mockStation16)
       assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 2)
     })
   })
@@ -180,9 +183,9 @@ await describe('OCPPAuthServiceFactory', async () => {
       mockStation20 = createMockAuthServiceTestStation('stats-20', OCPPVersion.VERSION_20)
     })
 
-    await it('should return factory statistics', async () => {
-      await OCPPAuthServiceFactory.getInstance(mockStation16)
-      await OCPPAuthServiceFactory.getInstance(mockStation20)
+    await it('should return factory statistics', () => {
+      OCPPAuthServiceFactory.getInstance(mockStation16)
+      OCPPAuthServiceFactory.getInstance(mockStation20)
 
       const stats = OCPPAuthServiceFactory.getStatistics()
 
@@ -212,16 +215,16 @@ await describe('OCPPAuthServiceFactory', async () => {
       mockStation20 = createMockAuthServiceTestStation('version-20', OCPPVersion.VERSION_20)
     })
 
-    await it('should create service for OCPP 1.6 station', async () => {
-      const authService = await OCPPAuthServiceFactory.getInstance(mockStation16)
+    await it('should create service for OCPP 1.6 station', () => {
+      const authService = OCPPAuthServiceFactory.getInstance(mockStation16)
 
       assert.notStrictEqual(authService, undefined)
       assert.strictEqual(typeof authService.authorize, 'function')
       assert.strictEqual(typeof authService.getConfiguration, 'function')
     })
 
-    await it('should create service for OCPP 2.0 station', async () => {
-      const authService = await OCPPAuthServiceFactory.getInstance(mockStation20)
+    await it('should create service for OCPP 2.0 station', () => {
+      const authService = OCPPAuthServiceFactory.getInstance(mockStation20)
 
       assert.notStrictEqual(authService, undefined)
       assert.strictEqual(typeof authService.authorize, 'function')
@@ -242,10 +245,10 @@ await describe('OCPPAuthServiceFactory', async () => {
       )
     })
 
-    await it('should properly manage instance lifecycle', async () => {
+    await it('should properly manage instance lifecycle', () => {
       // Create instances
       for (const station of mockStations) {
-        await OCPPAuthServiceFactory.getInstance(station)
+        OCPPAuthServiceFactory.getInstance(station)
       }
 
       assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 5)
index 21a44f42e9f9b2795f97524000c799893d24c500..919ce6cd1b087581255ca003cb2ad9b132f1f1af 100644 (file)
@@ -14,8 +14,8 @@ import {
   AuthContext,
   AuthenticationMethod,
   AuthorizationStatus,
+  type Identifier,
   IdentifierType,
-  type UnifiedIdentifier,
 } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
 import { OCPPVersion } from '../../../../../src/types/index.js'
 import { standardCleanup } from '../../../../helpers/TestLifecycleHelpers.js'
@@ -98,11 +98,11 @@ await describe('OCPPAuthServiceImpl', async () => {
       mockStation20 = createMockAuthServiceTestStation('isSupported-20', OCPPVersion.VERSION_20)
     })
 
-    await it('should check if identifier type is supported for OCPP 1.6', async () => {
+    await it('should check if identifier type is supported for OCPP 1.6', () => {
       const authService = new OCPPAuthServiceImpl(mockStation16)
-      await authService.initialize()
+      authService.initialize()
 
-      const idTagIdentifier: UnifiedIdentifier = {
+      const idTagIdentifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'VALID_ID_TAG',
       }
@@ -110,11 +110,11 @@ await describe('OCPPAuthServiceImpl', async () => {
       assert.strictEqual(authService.isSupported(idTagIdentifier), true)
     })
 
-    await it('should check if identifier type is supported for OCPP 2.0', async () => {
+    await it('should check if identifier type is supported for OCPP 2.0', () => {
       const authService = new OCPPAuthServiceImpl(mockStation20)
-      await authService.initialize()
+      authService.initialize()
 
-      const centralIdentifier: UnifiedIdentifier = {
+      const centralIdentifier: Identifier = {
         type: IdentifierType.CENTRAL,
         value: 'CENTRAL_ID',
       }
@@ -164,7 +164,7 @@ await describe('OCPPAuthServiceImpl', async () => {
     await it('should invalidate cache for specific identifier', () => {
       const authService = new OCPPAuthServiceImpl(mockStation)
 
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'TAG_TO_INVALIDATE',
       }
@@ -204,7 +204,7 @@ await describe('OCPPAuthServiceImpl', async () => {
     await it('should authorize identifier using strategy chain', async () => {
       const authService = new OCPPAuthServiceImpl(mockStation)
 
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'VALID_TAG',
       }
@@ -225,7 +225,7 @@ await describe('OCPPAuthServiceImpl', async () => {
     await it('should return INVALID status when all strategies fail', async () => {
       const authService = new OCPPAuthServiceImpl(mockStation)
 
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'UNKNOWN_TAG',
       }
@@ -253,7 +253,7 @@ await describe('OCPPAuthServiceImpl', async () => {
     await it('should check local authorization', async () => {
       const authService = new OCPPAuthServiceImpl(mockStation)
 
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'LOCAL_TAG',
       }
@@ -277,7 +277,7 @@ await describe('OCPPAuthServiceImpl', async () => {
     await it('should handle OCPP 1.6 specific identifiers', async () => {
       const authService = new OCPPAuthServiceImpl(mockStation16)
 
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'OCPP16_TAG',
       }
@@ -296,7 +296,7 @@ await describe('OCPPAuthServiceImpl', async () => {
     await it('should handle OCPP 2.0 specific identifiers', async () => {
       const authService = new OCPPAuthServiceImpl(mockStation20)
 
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.E_MAID,
         value: 'EMAID123456',
       }
@@ -323,7 +323,7 @@ await describe('OCPPAuthServiceImpl', async () => {
     await it('should handle invalid identifier gracefully', async () => {
       const authService = new OCPPAuthServiceImpl(mockStation)
 
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: '',
       }
@@ -352,7 +352,7 @@ await describe('OCPPAuthServiceImpl', async () => {
     await it('should handle TRANSACTION_START context', async () => {
       const authService = new OCPPAuthServiceImpl(mockStation16)
 
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'START_TAG',
       }
@@ -372,7 +372,7 @@ await describe('OCPPAuthServiceImpl', async () => {
     await it('should handle TRANSACTION_STOP context', async () => {
       const authService = new OCPPAuthServiceImpl(mockStation16)
 
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'STOP_TAG',
       }
@@ -392,7 +392,7 @@ await describe('OCPPAuthServiceImpl', async () => {
     await it('should handle REMOTE_START context', async () => {
       const authService = new OCPPAuthServiceImpl(mockStation20)
 
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.CENTRAL,
         value: 'REMOTE_ID',
       }
@@ -416,9 +416,9 @@ await describe('OCPPAuthServiceImpl', async () => {
       mockStation = createMockAuthServiceTestStation('cache-wiring')
     })
 
-    await it('should have a defined authCache after initialization', async () => {
+    await it('should have a defined authCache after initialization', () => {
       const authService = new OCPPAuthServiceImpl(mockStation)
-      await authService.initialize()
+      authService.initialize()
 
       const localStrategy = authService.getStrategy('local')
       assert.notStrictEqual(localStrategy, undefined)
index 321a5320f0e3a3e5a79b3bc81387c4b0f35c6a96..19d9a79a72fa561bd4c74957c00c2a5c5667c1b5 100644 (file)
@@ -47,7 +47,7 @@ await describe('CertificateAuthStrategy', async () => {
             })
           )
         }),
-      convertToUnifiedIdentifier: identifier => ({
+      convertToIdentifier: identifier => ({
         type: IdentifierType.CERTIFICATE,
         value: typeof identifier === 'string' ? identifier : JSON.stringify(identifier),
       }),
index 361c93847033fecc8ffbf8fc3521a441c79dd057..5564dc15dad107fed42e4d60a765cc4f97eea41a 100644 (file)
@@ -10,6 +10,7 @@ import {
   AuthenticationError,
   AuthErrorCode,
   AuthorizationStatus,
+  type Identifier,
   IdentifierType,
   isCertificateBased,
   isOCPP16Type,
@@ -20,7 +21,6 @@ import {
   mapToOCPP20Status,
   mapToOCPP20TokenType,
   requiresAdditionalInfo,
-  type UnifiedIdentifier,
 } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
 import {
   OCPP16AuthorizationStatus,
@@ -66,71 +66,71 @@ await describe('AuthTypes', async () => {
 
   await describe('TypeMappers', async () => {
     await describe('OCPP 1.6 Status Mapping', async () => {
-      await it('should map OCPP 1.6 ACCEPTED to unified ACCEPTED', () => {
+      await it('should map OCPP 1.6 ACCEPTED to ACCEPTED', () => {
         const result = mapOCPP16Status(OCPP16AuthorizationStatus.ACCEPTED)
         assert.strictEqual(result, AuthorizationStatus.ACCEPTED)
       })
 
-      await it('should map OCPP 1.6 BLOCKED to unified BLOCKED', () => {
+      await it('should map OCPP 1.6 BLOCKED to BLOCKED', () => {
         const result = mapOCPP16Status(OCPP16AuthorizationStatus.BLOCKED)
         assert.strictEqual(result, AuthorizationStatus.BLOCKED)
       })
 
-      await it('should map OCPP 1.6 EXPIRED to unified EXPIRED', () => {
+      await it('should map OCPP 1.6 EXPIRED to EXPIRED', () => {
         const result = mapOCPP16Status(OCPP16AuthorizationStatus.EXPIRED)
         assert.strictEqual(result, AuthorizationStatus.EXPIRED)
       })
 
-      await it('should map OCPP 1.6 INVALID to unified INVALID', () => {
+      await it('should map OCPP 1.6 INVALID to INVALID', () => {
         const result = mapOCPP16Status(OCPP16AuthorizationStatus.INVALID)
         assert.strictEqual(result, AuthorizationStatus.INVALID)
       })
 
-      await it('should map OCPP 1.6 CONCURRENT_TX to unified CONCURRENT_TX', () => {
+      await it('should map OCPP 1.6 CONCURRENT_TX to CONCURRENT_TX', () => {
         const result = mapOCPP16Status(OCPP16AuthorizationStatus.CONCURRENT_TX)
         assert.strictEqual(result, AuthorizationStatus.CONCURRENT_TX)
       })
     })
 
     await describe('OCPP 2.0 Token Type Mapping', async () => {
-      await it('should map OCPP 2.0 Central to unified CENTRAL', () => {
+      await it('should map OCPP 2.0 Central to CENTRAL', () => {
         const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.Central)
         assert.strictEqual(result, IdentifierType.CENTRAL)
       })
 
-      await it('should map OCPP 2.0 Local to unified LOCAL', () => {
+      await it('should map OCPP 2.0 Local to LOCAL', () => {
         const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.Local)
         assert.strictEqual(result, IdentifierType.LOCAL)
       })
 
-      await it('should map OCPP 2.0 eMAID to unified E_MAID', () => {
+      await it('should map OCPP 2.0 eMAID to E_MAID', () => {
         const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.eMAID)
         assert.strictEqual(result, IdentifierType.E_MAID)
       })
 
-      await it('should map OCPP 2.0 ISO14443 to unified ISO14443', () => {
+      await it('should map OCPP 2.0 ISO14443 to ISO14443', () => {
         const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.ISO14443)
         assert.strictEqual(result, IdentifierType.ISO14443)
       })
 
-      await it('should map OCPP 2.0 KeyCode to unified KEY_CODE', () => {
+      await it('should map OCPP 2.0 KeyCode to KEY_CODE', () => {
         const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.KeyCode)
         assert.strictEqual(result, IdentifierType.KEY_CODE)
       })
     })
 
-    await describe('Unified to OCPP 1.6 Status Mapping', async () => {
-      await it('should map unified ACCEPTED to OCPP 1.6 ACCEPTED', () => {
+    await describe('Auth to OCPP 1.6 Status Mapping', async () => {
+      await it('should map ACCEPTED to OCPP 1.6 ACCEPTED', () => {
         const result = mapToOCPP16Status(AuthorizationStatus.ACCEPTED)
         assert.strictEqual(result, OCPP16AuthorizationStatus.ACCEPTED)
       })
 
-      await it('should map unified BLOCKED to OCPP 1.6 BLOCKED', () => {
+      await it('should map BLOCKED to OCPP 1.6 BLOCKED', () => {
         const result = mapToOCPP16Status(AuthorizationStatus.BLOCKED)
         assert.strictEqual(result, OCPP16AuthorizationStatus.BLOCKED)
       })
 
-      await it('should map unified EXPIRED to OCPP 1.6 EXPIRED', () => {
+      await it('should map EXPIRED to OCPP 1.6 EXPIRED', () => {
         const result = mapToOCPP16Status(AuthorizationStatus.EXPIRED)
         assert.strictEqual(result, OCPP16AuthorizationStatus.EXPIRED)
       })
@@ -151,8 +151,8 @@ await describe('AuthTypes', async () => {
       })
     })
 
-    await describe('Unified to OCPP 2.0 Status Mapping', async () => {
-      await it('should map unified ACCEPTED to OCPP 2.0 Accepted', () => {
+    await describe('Auth to OCPP 2.0 Status Mapping', async () => {
+      await it('should map ACCEPTED to OCPP 2.0 Accepted', () => {
         const result = mapToOCPP20Status(AuthorizationStatus.ACCEPTED)
         assert.strictEqual(result, RequestStartStopStatusEnumType.Accepted)
       })
@@ -173,23 +173,23 @@ await describe('AuthTypes', async () => {
       })
     })
 
-    await describe('Unified to OCPP 2.0 Token Type Mapping', async () => {
-      await it('should map unified CENTRAL to OCPP 2.0 Central', () => {
+    await describe('Auth to OCPP 2.0 Token Type Mapping', async () => {
+      await it('should map CENTRAL to OCPP 2.0 Central', () => {
         const result = mapToOCPP20TokenType(IdentifierType.CENTRAL)
         assert.strictEqual(result, OCPP20IdTokenEnumType.Central)
       })
 
-      await it('should map unified E_MAID to OCPP 2.0 eMAID', () => {
+      await it('should map E_MAID to OCPP 2.0 eMAID', () => {
         const result = mapToOCPP20TokenType(IdentifierType.E_MAID)
         assert.strictEqual(result, OCPP20IdTokenEnumType.eMAID)
       })
 
-      await it('should map unified ID_TAG to OCPP 2.0 Local', () => {
+      await it('should map ID_TAG to OCPP 2.0 Local', () => {
         const result = mapToOCPP20TokenType(IdentifierType.ID_TAG)
         assert.strictEqual(result, OCPP20IdTokenEnumType.Local)
       })
 
-      await it('should map unified LOCAL to OCPP 2.0 Local', () => {
+      await it('should map LOCAL to OCPP 2.0 Local', () => {
         const result = mapToOCPP20TokenType(IdentifierType.LOCAL)
         assert.strictEqual(result, OCPP20IdTokenEnumType.Local)
       })
@@ -249,9 +249,9 @@ await describe('AuthTypes', async () => {
     })
   })
 
-  await describe('UnifiedIdentifier', async () => {
+  await describe('Identifier', async () => {
     await it('should create valid OCPP 1.6 identifier', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'VALID_ID_TAG',
       }
@@ -261,7 +261,7 @@ await describe('AuthTypes', async () => {
     })
 
     await it('should create valid OCPP 2.0 identifier with additional info', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         additionalInfo: {
           contractId: 'CONTRACT123',
           issuer: 'EMSProvider',
@@ -277,7 +277,7 @@ await describe('AuthTypes', async () => {
     })
 
     await it('should support certificate-based identifier', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         certificateHashData: {
           hashAlgorithm: 'SHA256',
           issuerKeyHash: 'KEY_HASH',
index ba032bfe9940d50261e582aea02d368c4183a0d2..0499605e04b476a158f6c15aa8f03b1b0ffb90bc 100644 (file)
@@ -10,8 +10,8 @@ import {
   AuthenticationMethod,
   type AuthorizationResult,
   AuthorizationStatus,
+  type Identifier,
   IdentifierType,
-  type UnifiedIdentifier,
 } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
 import { AuthHelpers } from '../../../../../src/charging-station/ocpp/auth/utils/AuthHelpers.js'
 import { OCPP20MessageFormatEnumType } from '../../../../../src/types/index.js'
@@ -52,7 +52,7 @@ await describe('AuthHelpers', async () => {
 
   await describe('createAuthRequest', async () => {
     await it('should create basic auth request with minimal parameters', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'TEST123',
       }
@@ -69,7 +69,7 @@ await describe('AuthHelpers', async () => {
     })
 
     await it('should create auth request with connector ID', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.LOCAL,
         value: 'LOCAL001',
       }
@@ -82,7 +82,7 @@ await describe('AuthHelpers', async () => {
     })
 
     await it('should create auth request with metadata', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.CENTRAL,
         value: 'CENTRAL001',
       }
@@ -125,7 +125,7 @@ await describe('AuthHelpers', async () => {
   await describe('formatAuthError', async () => {
     await it('should format error message with truncated identifier', () => {
       const error = new Error('Connection timeout')
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'VERY_LONG_IDENTIFIER_VALUE_12345',
       }
@@ -139,7 +139,7 @@ await describe('AuthHelpers', async () => {
 
     await it('should handle short identifiers correctly', () => {
       const error = new Error('Invalid format')
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.LOCAL,
         value: 'SHORT',
       }
index 5396361e080ba2a684f4c176961caf83a11fd9da..80745b20d78ddee9c76e889ad2bf67882d1bf8a3 100644 (file)
@@ -7,8 +7,8 @@ import { afterEach, describe, it } from 'node:test'
 
 import {
   type AuthConfiguration,
+  type Identifier,
   IdentifierType,
-  type UnifiedIdentifier,
 } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
 import { AuthValidators } from '../../../../../src/charging-station/ocpp/auth/utils/AuthValidators.js'
 import { standardCleanup } from '../../../../helpers/TestLifecycleHelpers.js'
@@ -273,7 +273,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return false for empty value', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: '',
       }
@@ -282,7 +282,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return false for ID_TAG exceeding 20 characters', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'VERY_LONG_IDENTIFIER_VALUE_123456789',
       }
@@ -291,7 +291,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return true for valid ID_TAG within 20 characters', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ID_TAG,
         value: 'VALID_ID_TAG',
       }
@@ -300,7 +300,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return true for OCPP 2.0 LOCAL type within 36 characters', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.LOCAL,
         value: 'LOCAL_TOKEN_123',
       }
@@ -309,7 +309,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return false for OCPP 2.0 type exceeding 36 characters', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.CENTRAL,
         value: 'VERY_LONG_CENTRAL_IDENTIFIER_VALUE_1234567890123456789',
       }
@@ -318,7 +318,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return true for CENTRAL type within 36 characters', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.CENTRAL,
         value: 'CENTRAL_TOKEN',
       }
@@ -327,7 +327,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return true for E_MAID type', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.E_MAID,
         value: 'DE-ABC-123456',
       }
@@ -336,7 +336,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return true for ISO14443 type', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.ISO14443,
         value: '04A2B3C4D5E6F7',
       }
@@ -345,7 +345,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return true for KEY_CODE type', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.KEY_CODE,
         value: '1234',
       }
@@ -354,7 +354,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return true for MAC_ADDRESS type', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.MAC_ADDRESS,
         value: '00:11:22:33:44:55',
       }
@@ -363,7 +363,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return true for NO_AUTHORIZATION type', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         type: IdentifierType.NO_AUTHORIZATION,
         value: 'NO_AUTH',
       }
@@ -372,7 +372,7 @@ await describe('AuthValidators', async () => {
     })
 
     await it('should return false for unsupported type', () => {
-      const identifier: UnifiedIdentifier = {
+      const identifier: Identifier = {
         // @ts-expect-error: Testing invalid type
         type: 'UNSUPPORTED_TYPE',
         value: 'VALUE',
index 89d2f54c012cb1294cb3ab6714a89a4a034a93aa..906681c74d091dc96b25f6e00067e8edb2a81291 100644 (file)
@@ -7,7 +7,7 @@ import assert from 'node:assert/strict'
 import { afterEach, beforeEach, describe, it } from 'node:test'
 import { gunzipSync } from 'node:zlib'
 
-import type { UUIDv4 } from '../../../src/types/index.js'
+import type { UIServerConfiguration, UUIDv4 } from '../../../src/types/index.js'
 
 import { UIHttpServer } from '../../../src/charging-station/ui-server/UIHttpServer.js'
 import { DEFAULT_COMPRESSION_THRESHOLD } from '../../../src/charging-station/ui-server/UIServerSecurity.js'
@@ -15,6 +15,7 @@ import { ApplicationProtocol, ResponseStatus } from '../../../src/types/index.js
 import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
 import { GZIP_STREAM_FLUSH_DELAY_MS, TEST_UUID } from './UIServerTestConstants.js'
 import {
+  createMockBootstrap,
   createMockUIServerConfiguration,
   MockServerResponse,
   waitForStreamFlush,
@@ -22,6 +23,11 @@ import {
 
 // eslint-disable-next-line @typescript-eslint/no-deprecated
 class TestableUIHttpServer extends UIHttpServer {
+  public constructor (config: UIServerConfiguration) {
+    // eslint-disable-next-line @typescript-eslint/no-deprecated
+    super(config, createMockBootstrap())
+  }
+
   public addResponseHandler (uuid: UUIDv4, res: MockServerResponse): void {
     this.responseHandlers.set(uuid, res as never)
   }
@@ -182,7 +188,8 @@ await describe('UIHttpServer', async () => {
           port: 9090,
         },
         type: ApplicationProtocol.HTTP,
-      })
+      }),
+      createMockBootstrap()
     )
 
     assert.notStrictEqual(serverCustom, undefined)
index a32e0dc2c5866daf76d7f6dc3c6a513e58bac121..6349347203bb2c4984e477466df8e1994c8316da 100644 (file)
@@ -17,7 +17,7 @@ import { HttpMethod } from '../../../src/charging-station/ui-server/UIServerUtil
 import { ApplicationProtocol, ConfigurationSection } from '../../../src/types/index.js'
 import { Configuration } from '../../../src/utils/Configuration.js'
 import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
-import { createMockUIServerConfiguration } from './UIServerTestUtils.js'
+import { createMockBootstrap, createMockUIServerConfiguration } from './UIServerTestUtils.js'
 
 /**
  * Parse SSE events from raw response body.
@@ -118,7 +118,8 @@ await describe('UIMCPServer HTTP Integration', async () => {
       createMockUIServerConfiguration({
         options: { host: 'localhost', port: 0 },
         type: ApplicationProtocol.MCP,
-      })
+      }),
+      createMockBootstrap()
     )
     server.start()
     const httpServer = Reflect.get(server, 'httpServer') as Server
index 1145c86fc82521e846422a0c6e4ac7ca90cff7f4..8bae9e8a1008b7dd766ebe793d5aa6591448de3d 100644 (file)
@@ -12,7 +12,12 @@ import { Readable } from 'node:stream'
 import { afterEach, beforeEach, describe, it } from 'node:test'
 import { fileURLToPath } from 'node:url'
 
-import type { ProtocolResponse, RequestPayload, ResponsePayload } from '../../../src/types/index.js'
+import type {
+  ProtocolResponse,
+  RequestPayload,
+  ResponsePayload,
+  UIServerConfiguration,
+} from '../../../src/types/index.js'
 
 import {
   mcpToolSchemas,
@@ -36,11 +41,16 @@ import {
 } from '../../helpers/TestLifecycleHelpers.js'
 import { TEST_HASH_ID, TEST_HASH_ID_2, TEST_UUID, TEST_UUID_2 } from './UIServerTestConstants.js'
 import {
+  createMockBootstrap,
   createMockChargingStationDataWithVersion,
   createMockUIServerConfiguration,
 } from './UIServerTestUtils.js'
 
 class TestableUIMCPServer extends UIMCPServer {
+  public constructor (config: UIServerConfiguration) {
+    super(config, createMockBootstrap())
+  }
+
   public callCheckVersionCompatibility (
     hashIds: string[] | undefined,
     ocpp16Payload: Record<string, unknown> | undefined,
index 1916a1a613e8f198ad37d2ff945cb6433fc13eaa..4f729439dd0d564dfccb6a720b88b51b60f684bf 100644 (file)
@@ -4,7 +4,7 @@
  */
 
 import assert from 'node:assert/strict'
-import { afterEach, describe, it } from 'node:test'
+import { afterEach, beforeEach, describe, it } from 'node:test'
 
 import { UIHttpServer } from '../../../src/charging-station/ui-server/UIHttpServer.js'
 import { UIMCPServer } from '../../../src/charging-station/ui-server/UIMCPServer.js'
@@ -12,16 +12,22 @@ import { UIServerFactory } from '../../../src/charging-station/ui-server/UIServe
 import { UIWebSocketServer } from '../../../src/charging-station/ui-server/UIWebSocketServer.js'
 import { ApplicationProtocol, ApplicationProtocolVersion } from '../../../src/types/index.js'
 import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
-import { createMockUIServerConfiguration } from './UIServerTestUtils.js'
+import { createMockBootstrap, createMockUIServerConfiguration } from './UIServerTestUtils.js'
 
 await describe('UIServerFactory', async () => {
+  let mockBootstrap: ReturnType<typeof createMockBootstrap>
+
+  beforeEach(() => {
+    mockBootstrap = createMockBootstrap()
+  })
+
   afterEach(() => {
     standardCleanup()
   })
 
   await it('should create UIHttpServer for HTTP protocol', () => {
     const config = createMockUIServerConfiguration({ type: ApplicationProtocol.HTTP })
-    const server = UIServerFactory.getUIServerImplementation(config)
+    const server = UIServerFactory.getUIServerImplementation(config, mockBootstrap)
     // eslint-disable-next-line @typescript-eslint/no-deprecated
     assert.ok(server instanceof UIHttpServer)
     server.stop()
@@ -29,14 +35,14 @@ await describe('UIServerFactory', async () => {
 
   await it('should create UIWebSocketServer for WS protocol', () => {
     const config = createMockUIServerConfiguration({ type: ApplicationProtocol.WS })
-    const server = UIServerFactory.getUIServerImplementation(config)
+    const server = UIServerFactory.getUIServerImplementation(config, mockBootstrap)
     assert.ok(server instanceof UIWebSocketServer)
     server.stop()
   })
 
   await it('should create UIMCPServer for MCP protocol', () => {
     const config = createMockUIServerConfiguration({ type: ApplicationProtocol.MCP })
-    const server = UIServerFactory.getUIServerImplementation(config)
+    const server = UIServerFactory.getUIServerImplementation(config, mockBootstrap)
     assert.ok(server instanceof UIMCPServer)
     server.stop()
   })
@@ -46,7 +52,7 @@ await describe('UIServerFactory', async () => {
       type: ApplicationProtocol.MCP,
       version: ApplicationProtocolVersion.VERSION_20,
     })
-    const server = UIServerFactory.getUIServerImplementation(config)
+    const server = UIServerFactory.getUIServerImplementation(config, mockBootstrap)
     assert.strictEqual(config.version, ApplicationProtocolVersion.VERSION_11)
     server.stop()
   })
@@ -56,7 +62,7 @@ await describe('UIServerFactory', async () => {
       type: ApplicationProtocol.WS,
       version: ApplicationProtocolVersion.VERSION_20,
     })
-    const server = UIServerFactory.getUIServerImplementation(config)
+    const server = UIServerFactory.getUIServerImplementation(config, mockBootstrap)
     assert.strictEqual(config.version, ApplicationProtocolVersion.VERSION_11)
     server.stop()
   })
index c162f0b8f6e0636026c6f9925c40d7b1cd4e0b85..48f99a30c9b5711a91f1e8f25d61bf178af41b2f 100644 (file)
@@ -6,6 +6,7 @@ import type { IncomingMessage } from 'node:http'
 
 import { EventEmitter } from 'node:events'
 
+import type { IBootstrap } from '../../../src/charging-station/IBootstrap.js'
 import type {
   ChargingStationData,
   ProcedureName,
@@ -28,10 +29,28 @@ import {
 } from '../../../src/types/index.js'
 import { MockWebSocket } from '../mocks/MockWebSocket.js'
 
+export const createMockBootstrap = (): IBootstrap => ({
+  addChargingStation: () => Promise.resolve(undefined),
+  getLastContiguousIndex: () => 0,
+  getPerformanceStatistics: () => undefined,
+  getState: () => ({
+    configuration: undefined,
+    started: false,
+    templateStatistics: new Map(),
+    version: '0.0.0',
+  }),
+  start: () => Promise.resolve(),
+  stop: () => Promise.resolve(),
+})
+
 /**
  * Testable UIWebSocketServer that exposes protected members for testing.
  */
 export class TestableUIWebSocketServer extends UIWebSocketServer {
+  public constructor (config: UIServerConfiguration, bootstrap: IBootstrap = createMockBootstrap()) {
+    super(config, bootstrap)
+  }
+
   /**
    * Add a response handler for testing.
    * @param uuid - Unique identifier for the response handler
index d873e234cd23e2dcb8f52a8677f219537d01907a..499d7337ab35296d3a384a601fe570dc89f1e74e 100644 (file)
@@ -3,16 +3,16 @@ import type {
   AuthConfiguration,
   AuthorizationResult,
   AuthRequest,
-  UnifiedIdentifier,
-} from '../../src/charging-station/ocpp/auth/types/AuthTypes.js'
+  Identifier,
+} from '../../src/charging-station/ocpp/auth/index.js'
 
-import { OCPPAuthServiceImpl } from '../../src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.js'
 import {
   AuthContext,
   AuthenticationMethod,
   AuthorizationStatus,
   IdentifierType,
-} from '../../src/charging-station/ocpp/auth/types/AuthTypes.js'
+  OCPPAuthServiceImpl,
+} from '../../src/charging-station/ocpp/auth/index.js'
 import { logger } from '../../src/utils/index.js'
 
 export class OCPPAuthIntegrationTest {
@@ -113,7 +113,7 @@ export class OCPPAuthIntegrationTest {
   }
 
   private async testCacheOperations (): Promise<void> {
-    const testIdentifier: UnifiedIdentifier = {
+    const testIdentifier: Identifier = {
       type: IdentifierType.LOCAL,
       value: 'CACHE_TEST_ID',
     }
@@ -156,7 +156,7 @@ export class OCPPAuthIntegrationTest {
   }
 
   private async testErrorHandling (): Promise<void> {
-    const invalidIdentifier: UnifiedIdentifier = {
+    const invalidIdentifier: Identifier = {
       type: IdentifierType.ISO14443,
       value: '',
     }
@@ -188,7 +188,7 @@ export class OCPPAuthIntegrationTest {
   }
 
   private async testOCPP16AuthFlow (): Promise<void> {
-    const identifier: UnifiedIdentifier = {
+    const identifier: Identifier = {
       type: IdentifierType.ISO14443,
       value: 'VALID_ID_123',
     }
@@ -216,7 +216,7 @@ export class OCPPAuthIntegrationTest {
   }
 
   private async testOCPP20AuthFlow (): Promise<void> {
-    const identifier: UnifiedIdentifier = {
+    const identifier: Identifier = {
       type: IdentifierType.ISO15693,
       value: 'VALID_ID_456',
     }
@@ -261,7 +261,7 @@ export class OCPPAuthIntegrationTest {
       throw new Error('Invalid authentication statistics')
     }
 
-    const identifier: UnifiedIdentifier = {
+    const identifier: Identifier = {
       type: IdentifierType.ISO14443,
       value: 'PERF_TEST_ID',
     }
@@ -327,7 +327,7 @@ export class OCPPAuthIntegrationTest {
       }
     }
 
-    const testIdentifier: UnifiedIdentifier = {
+    const testIdentifier: Identifier = {
       type: IdentifierType.ISO14443,
       value: 'TEST123',
     }
index cf87126fd841d8666304306ad69da146142ccf5b..0930d08dec21b7703e098c6e3c79f38501d5c767 100644 (file)
@@ -14,7 +14,7 @@ import { satisfies } from 'semver'
 import type { TimestampedData } from '../../src/types/index.js'
 
 import { JSRuntime, runtime } from '../../scripts/runtime.js'
-import { MapStringifyFormat } from '../../src/types/index.js'
+import { MapStringifyFormat, MessageType } from '../../src/types/index.js'
 import { Constants } from '../../src/utils/index.js'
 import {
   clampToSafeTimerValue,
@@ -29,6 +29,7 @@ import {
   formatDurationMilliSeconds,
   formatDurationSeconds,
   generateUUID,
+  getMessageTypeString,
   getRandomFloat,
   getRandomFloatFluctuatedRounded,
   getRandomFloatRounded,
@@ -886,4 +887,22 @@ await describe('Utils', async () => {
       )
     })
   })
+
+  await describe('getMessageTypeString', async () => {
+    await it('should return "request" for MessageType.CALL_MESSAGE', () => {
+      assert.strictEqual(getMessageTypeString(MessageType.CALL_MESSAGE), 'request')
+    })
+
+    await it('should return "response" for MessageType.CALL_RESULT_MESSAGE', () => {
+      assert.strictEqual(getMessageTypeString(MessageType.CALL_RESULT_MESSAGE), 'response')
+    })
+
+    await it('should return "error" for MessageType.CALL_ERROR_MESSAGE', () => {
+      assert.strictEqual(getMessageTypeString(MessageType.CALL_ERROR_MESSAGE), 'error')
+    })
+
+    await it('should return "unknown" for undefined', () => {
+      assert.strictEqual(getMessageTypeString(undefined), 'unknown')
+    })
+  })
 })