* 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>
"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",
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'
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)
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}
'@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'}
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}
'@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==}
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}
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}
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}
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}
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==}
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==}
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==}
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}
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}
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'}
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'}
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'}
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}
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'}
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'}
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
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'}
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'}
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==}
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'}
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==}
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==}
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'}
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==}
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'}
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'}
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'}
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==}
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'}
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'}
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'}
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==}
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
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'}
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==}
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==}
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'}
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'}
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==}
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==}
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==}
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==}
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'}
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'}
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'}
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==}
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==}
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==}
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'}
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'}
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'}
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'}
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'}
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'}
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'}
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'}
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'}
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'}
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'}
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'}
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'}
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'}
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)
'@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
'@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': {}
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)
'@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
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)
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
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:
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: {}
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
b4a@1.8.0: {}
+ balanced-match@1.0.2: {}
+
balanced-match@4.0.4: {}
base64-js@1.5.1: {}
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
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:
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
commander@10.0.1: {}
+ commander@11.1.0: {}
+
commander@14.0.3: {}
commander@2.20.3: {}
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
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
debounce@1.2.1: {}
+ debug@2.6.9:
+ dependencies:
+ ms: 2.0.0
+
debug@4.3.4:
dependencies:
ms: 2.1.2
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
inherits: 2.0.4
minimalistic-assert: 1.0.1
+ detect-file@1.0.0: {}
+
detect-libc@2.1.2: {}
detective@5.2.1:
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
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:
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):
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: {}
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
forwarded@0.2.0: {}
+ fp-ts@2.5.0: {}
+
fresh@2.0.0: {}
from2-string@1.1.0:
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:
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: {}
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: {}
ieee754@1.2.1: {}
+ ignore-walk@6.0.5:
+ dependencies:
+ minimatch: 10.2.4
+
ignore@5.3.2: {}
ignore@7.0.5: {}
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: {}
is-docker@2.2.1: {}
+ is-docker@3.0.0: {}
+
is-extglob@2.1.1: {}
is-finalizationregistry@1.1.1:
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
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: {}
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
dependencies:
'@keyv/serialize': 1.1.1
+ kleur@4.1.5: {}
+
knex@3.1.0(better-sqlite3@11.10.0):
dependencies:
colorette: 2.0.19
dependencies:
p-locate: 6.0.0
+ lodash-es@4.17.23: {}
+
lodash.camelcase@4.3.0: {}
lodash.chunk@4.2.0: {}
lodash.startcase@4.4.0: {}
+ lodash.uniqwith@4.5.0: {}
+
lodash.upperfirst@4.3.1: {}
lodash@4.17.23: {}
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:
merge2@1.4.1: {}
+ meriyah@4.5.0: {}
+
micromatch@4.0.8:
dependencies:
braces: 3.0.3
dependencies:
brace-expansion: 5.0.5
+ minimatch@7.4.9:
+ dependencies:
+ brace-expansion: 2.0.3
+
minimist@1.2.8: {}
minipass@7.1.3: {}
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
nanoid@3.3.11: {}
+ nanospinner@1.2.2:
+ dependencies:
+ picocolors: 1.1.1
+
napi-build-utils@2.0.0: {}
natural-compare@1.4.0: {}
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):
dependencies:
semver: 7.7.4
+ node-addon-api@7.1.1: {}
+
node-exports-info@1.6.0:
dependencies:
array.prototype.flatmap: 1.3.3
dependencies:
ee-first: 1.1.1
+ on-headers@1.1.0: {}
+
on-net-listen@1.1.2: {}
once@1.4.0:
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
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:
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: {}
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: {}
dependencies:
escape-goat: 2.1.1
+ pure-rand@6.1.0: {}
+
qs@6.15.0:
dependencies:
side-channel: 1.1.0
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: {}
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
seedrandom@3.0.5: {}
+ semver-compare@1.0.0: {}
+
semver-diff@3.1.1:
dependencies:
semver: 6.3.1
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:
split2@4.2.0: {}
+ sprintf-js@1.0.3: {}
+
sqlstring-sqlite@0.1.1: {}
sqlstring@2.3.3: {}
toidentifier@1.0.1: {}
+ totalist@3.0.1: {}
+
tough-cookie@6.0.1:
dependencies:
tldts: 7.0.27
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
transitivePeerDependencies:
- supports-color
+ typescript@5.9.3: {}
+
typescript@6.0.2: {}
ufo@1.6.3: {}
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
yallist@5.0.0: {}
+ yaml@1.10.3: {}
+
yaml@2.8.3: {}
yargs-parser@15.0.3:
camelcase: 5.3.1
decamelize: 1.2.0
+ yargs-parser@20.2.9: {}
+
yargs-parser@21.1.1: {}
yargs@14.2.3:
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
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'
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' }
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
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(
formatDurationMilliSeconds,
formatDurationSeconds,
getErrorMessage,
+ getMessageTypeString,
getWebSocketCloseEventStatusString,
handleFileException,
isEmpty,
getHashId,
getIdTagsFile,
getMaxNumberOfEvses,
- getMessageTypeString,
getPhaseRotationValue,
hasFeatureProfile,
hasReservationExpired,
ConnectorStatusEnum,
CurrentType,
type EvseTemplate,
- MessageType,
OCPPVersion,
RecurrencyKindType,
type Reservation,
}
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'
- }
-}
--- /dev/null
+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>
+}
checkChargingStationState,
getConnectorChargingProfiles,
getIdTagsFile,
- getMessageTypeString,
hasFeatureProfile,
hasPendingReservation,
hasPendingReservations,
resetAuthorizeConnectorStatus,
resetConnectorStatus,
} from './Helpers.js'
+export type { IBootstrap } from './IBootstrap.js'
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'
// idTag authorization check required
if (
chargingStation.getAuthorizeRemoteTxRequests() &&
- !(await OCPPServiceUtils.isIdTagAuthorizedUnified(
+ !(await isIdTagAuthorized(
chargingStation,
transactionConnectorId,
- idTag
+ idTag,
+ AuthContext.REMOTE_START
))
) {
return this.notifyRemoteStartTransactionRejected(
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)
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 {
} 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,
* @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) {
)
}
+ 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
}
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)}:`,
}
if (!isAuthorized) {
- logger.warn(
- `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: IdToken ${truncateId(idToken.idToken)} is not authorized`
- )
return {
status: RequestStartStopStatusEnumType.Rejected,
statusInfo: {
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)}:`,
transactionId: generateUUID(),
}
}
-
if (!isGroupAuthorized) {
- logger.warn(
- `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: GroupIdToken ${truncateId(groupIdToken.idToken)} is not authorized`
- )
return {
status: RequestStartStopStatusEnumType.Rejected,
statusInfo: {
)
}
- 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)
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) {
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,
--- /dev/null
+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
+ }
+}
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'
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,
MeterValueMeasurand,
MeterValuePhase,
MeterValueUnit,
- type OCPP16AuthorizeResponse,
type OCPP16MeterValue,
type OCPP16SampledValue,
type OCPP16StatusNotificationRequest,
OCPP16StopTransactionReason,
OCPP20AuthorizationStatusEnumType,
- type OCPP20AuthorizeResponse,
type OCPP20ConnectorStatusEnumType,
OCPP20IdTokenEnumType,
type OCPP20MeterValue,
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'
}
}
-/**
- * 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,
return
}
if (options.send) {
- await checkConnectorStatusTransition(chargingStation, connectorId, status)
+ checkConnectorStatusTransition(chargingStation, connectorId, status)
await chargingStation.ocppRequestService.requestHandler<
StatusNotificationRequest,
StatusNotificationResponse
}
}
-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(
}
case OCPPVersion.VERSION_20:
case OCPPVersion.VERSION_201: {
- const { OCPP20Constants } = await import('./2.0/OCPP20Constants.js')
if (
(connectorId === 0 &&
OCPP20Constants.ChargingStationStatusTransitions.findIndex(
// 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
AuthConfiguration,
AuthorizationResult,
AuthRequest,
- UnifiedIdentifier,
+ Identifier,
} from '../types/AuthTypes.js'
import { getConfigurationKey } from '../../../../charging-station/ConfigurationKeyUtils.js'
* 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
/**
* 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> {
idTag: identifier.value,
})
- // Convert response to unified format
+ // Convert response to auth format
const result: AuthorizationResult = {
additionalInfo: {
connectorId,
}
/**
- * 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)]))
}
}
+ /**
+ * 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,
transactionId?: number,
context?: string
): AuthRequest {
- const identifier = this.convertToUnifiedIdentifier(idTag)
+ const identifier = this.convertToIdentifier(idTag)
// Map context string to AuthContext enum
let authContext: AuthContext
/**
* 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
AuthConfiguration,
AuthorizationResult,
AuthRequest,
- UnifiedIdentifier,
+ Identifier,
} from '../types/AuthTypes.js'
import { OCPP20VariableManager } from '../../2.0/OCPP20VariableManager.js'
AuthorizationStatus,
IdentifierType,
mapOCPP20AuthorizationStatus,
+ mapOCPP20TokenType,
mapToOCPP20Status,
+ mapToOCPP20TokenType,
} from '../types/AuthTypes.js'
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
/**
* 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> {
}
try {
- const idToken = this.convertFromUnifiedIdentifier(identifier)
+ const idToken = this.convertFromIdentifier(identifier)
// Validate token format
const isValidToken = this.isValidIdentifier(identifier)
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 {
},
isOffline: false,
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: unifiedStatus,
+ status: mappedStatus,
timestamp: new Date(),
}
} catch (error) {
}
/**
- * 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_'))
}
/**
- * 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
idToken = identifier
}
- // Map OCPP 2.0 IdToken type to unified type
- const unifiedType = this.mapToUnifiedIdentifierType(idToken.type)
+ const identifierType = mapOCPP20TokenType(idToken.type)
return {
additionalInfo: {
: {}),
},
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,
transactionId?: string,
context?: string
): AuthRequest {
- const identifier = this.convertToUnifiedIdentifier(idTokenOrString)
+ const identifier = this.convertToIdentifier(idTokenOrString)
// Map context string to AuthContext enum
let authContext: AuthContext
/**
* 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
}
}
- /**
- * 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')
)
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
- }
}
-import type { ChargingStation } from '../../../ChargingStation.js'
+import type { ChargingStation } from '../../../index.js'
import type {
AuthCache,
AuthStrategy,
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'
/**
* @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) {
}
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,
* @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
* @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 &&
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
* @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
* @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
/**
* 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.
export { OCPP20AuthAdapter } from './adapters/OCPP20AuthAdapter.js'
// ============================================================================
-// Type Guards & Mappers (Pure Functions)
+// Adapters
// ============================================================================
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,
AuthorizationStatus,
type AuthRequest,
type CertificateHashData,
+ type Identifier,
IdentifierType,
isCertificateBased,
isOCPP16Type,
mapToOCPP20Status,
mapToOCPP20TokenType,
requiresAdditionalInfo,
- type UnifiedIdentifier,
} from './types/AuthTypes.js'
// ============================================================================
AuthConfiguration,
AuthorizationResult,
AuthRequest,
- UnifiedIdentifier,
+ Identifier,
} from '../types/AuthTypes.js'
import type { IdentifierType } from '../types/AuthTypes.js'
*/
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
*/
* Initialize the strategy with configuration
* @param config - Authentication configuration
*/
- initialize(config: AuthConfiguration): Promise<void> | void
+ initialize(config: AuthConfiguration): void
/**
* Strategy name for identification
/**
* 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
/**
* 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 {
* 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)
* @returns Promise resolving to local authorization result, undefined if not found
*/
isLocallyAuthorized(
- identifier: UnifiedIdentifier,
+ identifier: Identifier,
connectorId?: number
): Promise<AuthorizationResult | undefined>
-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'
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
*
*/
// 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
* @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
}
* @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
)
const authService = new OCPPAuthServiceImpl(chargingStation)
- await authService.initialize()
+ authService.initialize()
// Cache the instance
this.instances.set(stationId, authService)
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'
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,
type AuthorizationResult,
AuthorizationStatus,
type AuthRequest,
+ type Identifier,
IdentifierType,
mapOCPP20AuthorizationStatus,
- type UnifiedIdentifier,
} from '../types/AuthTypes.js'
import { AuthConfigValidator } from '../utils/ConfigValidator.js'
)
// 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(
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' },
* 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)}`
)
/**
* 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
/**
* 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,
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`
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(),
}
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`
)
}
/**
* 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) {
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
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'
/**
* 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.
* 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 = {
/**
* 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
/**
* 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
} {
AuthenticationMethod,
AuthErrorCode,
AuthorizationStatus,
+ enhanceAuthResult,
} from '../types/AuthTypes.js'
const moduleName = 'LocalAuthStrategy'
)
return undefined
}
- return this.enhanceResult(localResult, AuthenticationMethod.LOCAL_LIST, startTime)
+ return enhanceAuthResult(
+ localResult,
+ AuthenticationMethod.LOCAL_LIST,
+ this.name,
+ startTime
+ )
}
}
)
return undefined
}
- return this.enhanceResult(cacheResult, AuthenticationMethod.CACHE, startTime)
+ return enhanceAuthResult(cacheResult, AuthenticationMethod.CACHE, this.name, startTime)
}
}
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
+ )
}
}
}
}
- /**
- * 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
}
/**
- * 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()) {
AuthenticationError,
AuthenticationMethod,
AuthErrorCode,
+ enhanceAuthResult,
IdentifierType,
} from '../types/AuthTypes.js'
)
}
- return this.enhanceResult(result, startTime)
+ return enhanceAuthResult(
+ result,
+ AuthenticationMethod.REMOTE_AUTHORIZATION,
+ this.name,
+ startTime
+ )
}
logger.debug(
}
}
- /**
- * 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
}
/**
- * 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
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',
readonly evseId?: number
/** Identifier to authenticate */
- readonly identifier: UnifiedIdentifier
+ readonly identifier: Identifier
/** Additional context data */
readonly metadata?: Record<string, unknown>
}
/**
- * 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>
/**
* 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:**
* 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
* ```
*/
}
/**
- * 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
* ```
*/
}
/**
- * 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
* ```
*/
}
/**
- * 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
}
/**
- * 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
}
/**
- * 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
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(),
+})
AuthenticationMethod,
AuthorizationResult,
AuthRequest,
- UnifiedIdentifier,
+ Identifier,
} from '../types/AuthTypes.js'
import { truncateId } from '../../../../utils/index.js'
/**
* 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>
* @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}`
}
-import type { AuthConfiguration, UnifiedIdentifier } from '../types/AuthTypes.js'
+import type { AuthConfiguration, Identifier } from '../types/AuthTypes.js'
import { IdentifierType } from '../types/AuthTypes.js'
}
/**
- * 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 {
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:
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
* Provides validation and helper functions for authentication operations
*/
-export { AuthHelpers } from './AuthHelpers.js'
export { AuthValidators } from './AuthValidators.js'
export { AuthConfigValidator } from './ConfigValidator.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'
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'
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) {
return this.chargingStations.delete(hashId)
}
+ public getBootstrap (): IBootstrap {
+ return this.bootstrap
+ }
+
public getChargingStationData (hashId: string): ChargingStationData | undefined {
return this.chargingStations.get(hashId)
}
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,
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>()
}
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'
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()
}
+import type { IBootstrap } from '../IBootstrap.js'
import type { AbstractUIServer } from './AbstractUIServer.js'
import { BaseError } from '../../exception/index.js'
}
public static getUIServerImplementation (
- uiServerConfiguration: UIServerConfiguration
+ uiServerConfiguration: UIServerConfiguration,
+ bootstrap: IBootstrap
): AbstractUIServer {
if (
uiServerConfiguration.authentication?.enabled === true &&
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 (
}'`
logger.warn(`${UIServerFactory.logPrefix()} ${logMsg}`)
}
- return new UIWebSocketServer(uiServerConfiguration)
+ return new UIWebSocketServer(uiServerConfiguration, bootstrap)
}
}
import { StatusCodes } from 'http-status-codes'
import { type RawData, WebSocket, WebSocketServer } from 'ws'
+import type { IBootstrap } from '../IBootstrap.js'
+
import {
MapStringifyFormat,
type ProtocolNotification,
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,
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'
): 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',
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)
}
try {
return {
performanceStatistics: [
- ...(Bootstrap.getInstance().getPerformanceStatistics() ?? []),
+ ...(this.uiServer.getBootstrap().getPerformanceStatistics() ?? []),
] as JsonType[],
status: ResponseStatus.SUCCESS,
} satisfies ResponsePayload
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) {
private async handleStartSimulator (): Promise<ResponsePayload> {
try {
- await Bootstrap.getInstance().start()
+ await this.uiServer.getBootstrap().start()
return { status: ResponseStatus.SUCCESS }
} catch (error) {
return {
private async handleStopSimulator (): Promise<ResponsePayload> {
try {
- await Bootstrap.getInstance().stop()
+ await this.uiServer.getBootstrap().stop()
return { status: ResponseStatus.SUCCESS }
} catch (error) {
return {
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'
import {
type JsonType,
MapStringifyFormat,
+ MessageType,
type TimestampedData,
type UUIDv4,
WebSocketCloseEventStatusString,
}
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'
+ }
+}
formatDurationMilliSeconds,
formatDurationSeconds,
generateUUID,
+ getMessageTypeString,
getRandomFloatFluctuatedRounded,
getRandomFloatRounded,
getWebSocketCloseEventStatusString,
| Utility | Purpose |
| --------------------------------- | --------------------------- |
-| `createMockIdentifier()` | UnifiedIdentifier factory |
+| `createMockIdentifier()` | Identifier factory |
| `createMockAuthRequest()` | AuthRequest factory |
| `createMockAuthorizationResult()` | AuthorizationResult factory |
getChargingStationId,
getHashId,
getMaxNumberOfEvses,
- getMessageTypeString,
getPhaseRotationValue,
hasPendingReservation,
hasPendingReservations,
type ChargingStationTemplate,
type ConnectorStatus,
ConnectorStatusEnum,
- MessageType,
type MeterValue,
OCPPVersion,
type Reservation,
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')
- })
- })
})
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'
// 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 {
// 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 {
// 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 {
// 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 {
// 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 {
// 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 {
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,
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,
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,
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,
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({
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 = {
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,
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,
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,
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'
let authService: OCPPAuthServiceImpl
let authCache: AuthCache
- beforeEach(async () => {
+ beforeEach(() => {
const { station: mockStation } = createMockChargingStation({
baseName: TEST_STATION_ID,
connectorsCount: 1,
station = mockStation
authService = new OCPPAuthServiceImpl(station)
- await authService.initialize()
+ authService.initialize()
const localStrategy = authService.getStrategy('local') as LocalAuthStrategy | undefined
const cache = localStrategy?.getAuthCache()
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',
},
})
const disabledService = new OCPPAuthServiceImpl(disabledStation)
- await disabledService.initialize()
+ disabledService.initialize()
disabledService.updateConfiguration({ authorizationCacheEnabled: false })
const idTokenInfo = {
})
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,
--- /dev/null
+/**
+ * @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)
+ })
+ })
+})
+++ /dev/null
-/**
- * @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)
- })
- })
-})
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,
},
})
const service = new OCPPAuthServiceImpl(result16.station)
- await service.initialize()
+ service.initialize()
const localStrategy = service.getStrategy('local') as LocalAuthStrategy | undefined
assert.notStrictEqual(localStrategy, undefined)
})
})
- 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')
})
})
- 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')
})
})
})
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,
})
})
- 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', () => {
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)
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)
})
})
- 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)
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)
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)
})
})
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,
})
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,
})
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/,
})
})
})
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,
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,
offlineAuthorizationEnabled: false,
}
- const result = await AuthComponentFactory.createLocalStrategy(undefined, undefined, config)
+ const result = AuthComponentFactory.createLocalStrategy(undefined, undefined, config)
assert.notStrictEqual(result, undefined)
if (result) {
})
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,
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,
remoteAuthorization: true,
}
- const result = await AuthComponentFactory.createRemoteStrategy(adapter, undefined, config)
+ const result = AuthComponentFactory.createRemoteStrategy(adapter, undefined, config)
assert.notStrictEqual(result, undefined)
if (result) {
})
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,
offlineAuthorizationEnabled: false,
}
- const result = await AuthComponentFactory.createCertificateStrategy(
+ const result = AuthComponentFactory.createCertificateStrategy(
chargingStation,
adapter,
config
})
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,
offlineAuthorizationEnabled: false,
}
- const result = await AuthComponentFactory.createStrategies(
+ const result = AuthComponentFactory.createStrategies(
chargingStation,
adapter,
undefined,
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,
remoteAuthorization: true,
}
- const result = await AuthComponentFactory.createStrategies(
+ const result = AuthComponentFactory.createStrategies(
chargingStation,
adapter,
undefined,
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'
*/
/**
- * 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,
})
invalidateCache: () => {
/* empty */
},
- isLocallyAuthorized: (_identifier: UnifiedIdentifier, _connectorId?: number) =>
+ isLocallyAuthorized: (_identifier: Identifier, _connectorId?: number) =>
new Promise<AuthorizationResult | undefined>(resolve => {
resolve(undefined)
}),
ocppVersion: OCPPVersion,
overrides?: Partial<OCPPAuthAdapter>
): OCPPAuthAdapter => ({
- authorizeRemote: (_identifier: UnifiedIdentifier) =>
+ authorizeRemote: (_identifier: Identifier) =>
new Promise<AuthorizationResult>(resolve => {
resolve(
createMockAuthorizationResult({
})
)
}),
- 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,
}),
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
+ }
+ )
})
})
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)
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)
})
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()
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)
})
})
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()
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')
)
})
- 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)
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'
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',
}
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',
}
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',
}
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',
}
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',
}
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',
}
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',
}
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',
}
await it('should handle invalid identifier gracefully', async () => {
const authService = new OCPPAuthServiceImpl(mockStation)
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.ID_TAG,
value: '',
}
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',
}
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',
}
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',
}
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)
})
)
}),
- convertToUnifiedIdentifier: identifier => ({
+ convertToIdentifier: identifier => ({
type: IdentifierType.CERTIFICATE,
value: typeof identifier === 'string' ? identifier : JSON.stringify(identifier),
}),
AuthenticationError,
AuthErrorCode,
AuthorizationStatus,
+ type Identifier,
IdentifierType,
isCertificateBased,
isOCPP16Type,
mapToOCPP20Status,
mapToOCPP20TokenType,
requiresAdditionalInfo,
- type UnifiedIdentifier,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
import {
OCPP16AuthorizationStatus,
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)
})
})
})
- 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)
})
})
})
- 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)
})
})
})
- 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',
}
})
await it('should create valid OCPP 2.0 identifier with additional info', () => {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
additionalInfo: {
contractId: 'CONTRACT123',
issuer: 'EMSProvider',
})
await it('should support certificate-based identifier', () => {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
certificateHashData: {
hashAlgorithm: 'SHA256',
issuerKeyHash: 'KEY_HASH',
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'
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',
}
})
await it('should create auth request with connector ID', () => {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.LOCAL,
value: 'LOCAL001',
}
})
await it('should create auth request with metadata', () => {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.CENTRAL,
value: 'CENTRAL001',
}
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',
}
await it('should handle short identifiers correctly', () => {
const error = new Error('Invalid format')
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.LOCAL,
value: 'SHORT',
}
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'
})
await it('should return false for empty value', () => {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.ID_TAG,
value: '',
}
})
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',
}
})
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',
}
})
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',
}
})
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',
}
})
await it('should return true for CENTRAL type within 36 characters', () => {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.CENTRAL,
value: 'CENTRAL_TOKEN',
}
})
await it('should return true for E_MAID type', () => {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.E_MAID,
value: 'DE-ABC-123456',
}
})
await it('should return true for ISO14443 type', () => {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.ISO14443,
value: '04A2B3C4D5E6F7',
}
})
await it('should return true for KEY_CODE type', () => {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.KEY_CODE,
value: '1234',
}
})
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',
}
})
await it('should return true for NO_AUTHORIZATION type', () => {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.NO_AUTHORIZATION,
value: 'NO_AUTH',
}
})
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',
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'
import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
import { GZIP_STREAM_FLUSH_DELAY_MS, TEST_UUID } from './UIServerTestConstants.js'
import {
+ createMockBootstrap,
createMockUIServerConfiguration,
MockServerResponse,
waitForStreamFlush,
// 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)
}
port: 9090,
},
type: ApplicationProtocol.HTTP,
- })
+ }),
+ createMockBootstrap()
)
assert.notStrictEqual(serverCustom, undefined)
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.
createMockUIServerConfiguration({
options: { host: 'localhost', port: 0 },
type: ApplicationProtocol.MCP,
- })
+ }),
+ createMockBootstrap()
)
server.start()
const httpServer = Reflect.get(server, 'httpServer') as Server
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,
} 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,
*/
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'
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()
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()
})
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()
})
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()
})
import { EventEmitter } from 'node:events'
+import type { IBootstrap } from '../../../src/charging-station/IBootstrap.js'
import type {
ChargingStationData,
ProcedureName,
} 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
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 {
}
private async testCacheOperations (): Promise<void> {
- const testIdentifier: UnifiedIdentifier = {
+ const testIdentifier: Identifier = {
type: IdentifierType.LOCAL,
value: 'CACHE_TEST_ID',
}
}
private async testErrorHandling (): Promise<void> {
- const invalidIdentifier: UnifiedIdentifier = {
+ const invalidIdentifier: Identifier = {
type: IdentifierType.ISO14443,
value: '',
}
}
private async testOCPP16AuthFlow (): Promise<void> {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.ISO14443,
value: 'VALID_ID_123',
}
}
private async testOCPP20AuthFlow (): Promise<void> {
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.ISO15693,
value: 'VALID_ID_456',
}
throw new Error('Invalid authentication statistics')
}
- const identifier: UnifiedIdentifier = {
+ const identifier: Identifier = {
type: IdentifierType.ISO14443,
value: 'PERF_TEST_ID',
}
}
}
- const testIdentifier: UnifiedIdentifier = {
+ const testIdentifier: Identifier = {
type: IdentifierType.ISO14443,
value: 'TEST123',
}
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,
formatDurationMilliSeconds,
formatDurationSeconds,
generateUUID,
+ getMessageTypeString,
getRandomFloat,
getRandomFloatFluctuatedRounded,
getRandomFloatRounded,
)
})
})
+
+ 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')
+ })
+ })
})