node-chargepoint 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -157
- package/dist/cli.cjs +26 -10
- package/dist/index.cjs +26 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -5
- package/dist/index.d.ts +18 -5
- package/dist/index.js +26 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -289,172 +289,18 @@ for (const s of filtered) {
|
|
|
289
289
|
|
|
290
290
|
## CLI
|
|
291
291
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
### Authentication
|
|
295
|
-
|
|
296
|
-
Credentials are read from environment variables:
|
|
297
|
-
|
|
298
|
-
```bash
|
|
299
|
-
export CP_USERNAME="user@example.com"
|
|
300
|
-
export CP_TOKEN="<coulomb_sess cookie value>"
|
|
301
|
-
# or use --password / CP_PASSWORD for password-based login
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
### Commands
|
|
305
|
-
|
|
306
|
-
```bash
|
|
307
|
-
# Show account info
|
|
308
|
-
chargepoint -u user@example.com -t $CP_TOKEN account
|
|
309
|
-
|
|
310
|
-
# List registered vehicles
|
|
311
|
-
chargepoint -u user@example.com -t $CP_TOKEN vehicles
|
|
312
|
-
|
|
313
|
-
# Show current charging session status
|
|
314
|
-
chargepoint -u user@example.com -t $CP_TOKEN status
|
|
315
|
-
|
|
316
|
-
# List home charger IDs
|
|
317
|
-
chargepoint -u user@example.com -t $CP_TOKEN chargers
|
|
318
|
-
|
|
319
|
-
# Start a charging session on a device
|
|
320
|
-
chargepoint -u user@example.com -t $CP_TOKEN start <deviceId>
|
|
321
|
-
|
|
322
|
-
# Show details for a charging station
|
|
323
|
-
chargepoint -u user@example.com -t $CP_TOKEN station <deviceId>
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
Global options:
|
|
327
|
-
|
|
328
|
-
```
|
|
329
|
-
-u, --username <username> ChargePoint username (or set CP_USERNAME)
|
|
330
|
-
-t, --token <token> Coulomb session token (or set CP_TOKEN)
|
|
331
|
-
-p, --password <password> Password login fallback (or set CP_PASSWORD)
|
|
332
|
-
-V, --version Print version
|
|
333
|
-
-h, --help Display help
|
|
334
|
-
```
|
|
292
|
+
See [docs/cli.md](docs/cli.md) for usage, commands, and global options.
|
|
335
293
|
|
|
336
294
|
---
|
|
337
295
|
|
|
338
296
|
## Error Handling
|
|
339
297
|
|
|
340
|
-
|
|
341
|
-
import {
|
|
342
|
-
ChargePoint,
|
|
343
|
-
LoginError,
|
|
344
|
-
InvalidSession,
|
|
345
|
-
DatadomeCaptcha,
|
|
346
|
-
CommunicationError,
|
|
347
|
-
APIError,
|
|
348
|
-
} from 'node-chargepoint';
|
|
349
|
-
|
|
350
|
-
try {
|
|
351
|
-
await client.loginWithPassword('bad-password');
|
|
352
|
-
} catch (err) {
|
|
353
|
-
if (err instanceof LoginError) {
|
|
354
|
-
console.error('Wrong credentials');
|
|
355
|
-
} else if (err instanceof InvalidSession) {
|
|
356
|
-
console.error('Session expired — re-authenticate');
|
|
357
|
-
} else if (err instanceof DatadomeCaptcha) {
|
|
358
|
-
console.error('Bot protection triggered:', err.captchaUrl);
|
|
359
|
-
} else if (err instanceof CommunicationError) {
|
|
360
|
-
console.error(`API error ${err.statusCode}:`, err.message);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
**Error hierarchy:**
|
|
366
|
-
|
|
367
|
-
```
|
|
368
|
-
APIError
|
|
369
|
-
├── CommunicationError (non-2xx response)
|
|
370
|
-
│ ├── LoginError (authentication failed)
|
|
371
|
-
│ └── InvalidSession (session expired — HTTP 401)
|
|
372
|
-
└── DatadomeCaptcha (Datadome bot protection — HTTP 403)
|
|
373
|
-
```
|
|
298
|
+
See [docs/error-handling.md](docs/error-handling.md) for the error class hierarchy and handling examples.
|
|
374
299
|
|
|
375
300
|
---
|
|
376
301
|
|
|
377
302
|
## Development
|
|
378
303
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
```bash
|
|
382
|
-
git clone https://github.com/musicbender/node-chargepoint.git
|
|
383
|
-
cd node-chargepoint
|
|
384
|
-
pnpm install
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
### Type checking
|
|
388
|
-
|
|
389
|
-
```bash
|
|
390
|
-
pnpm typecheck
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
### Tests
|
|
394
|
-
|
|
395
|
-
```bash
|
|
396
|
-
pnpm test
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
Tests use [Vitest](https://vitest.dev/) and [MSW](https://mswjs.io/) to intercept `fetch` calls. No real network traffic is made during tests.
|
|
400
|
-
|
|
401
|
-
### E2E Tests
|
|
402
|
-
|
|
403
|
-
An optional E2E suite runs against the live ChargePoint API using a real home charger. It requires valid credentials and is not run as part of `pnpm test`.
|
|
404
|
-
|
|
405
|
-
**Setup:**
|
|
406
|
-
|
|
407
|
-
Create a `.env.e2e` file in the project root (it is gitignored):
|
|
408
|
-
|
|
409
|
-
```
|
|
410
|
-
CP_USERNAME=your-chargepoint-email@example.com
|
|
411
|
-
CP_PASSWORD=your-chargepoint-password
|
|
412
|
-
|
|
413
|
-
# After first run, paste the printed token here to avoid re-logging in:
|
|
414
|
-
# CP_TOKEN=
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
> **Tip:** `coulomb_sess` tokens contain special characters (`#`, `?`). Always wrap the value in
|
|
418
|
-
> double quotes when exporting to your shell:
|
|
419
|
-
> ```bash
|
|
420
|
-
> export CP_TOKEN="abc...#D???"
|
|
421
|
-
> ```
|
|
422
|
-
|
|
423
|
-
**Run (read-only — safe with charger in any state):**
|
|
424
|
-
|
|
425
|
-
```bash
|
|
426
|
-
pnpm test:e2e
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
On first run the session token is printed. Paste it into `CP_TOKEN` in `.env.e2e` to skip
|
|
430
|
-
password re-authentication on subsequent runs.
|
|
431
|
-
|
|
432
|
-
**Run with mutation tests** (schedule, amperage limit, LED brightness — each test restores
|
|
433
|
-
original values):
|
|
434
|
-
|
|
435
|
-
```bash
|
|
436
|
-
E2E_MUTATIONS=true pnpm test:e2e
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
The following operations are intentionally excluded from the automated suite due to their
|
|
440
|
-
disruptive nature: `restartHomeCharger()`, `startChargingSession()`, `stopChargingSession()`.
|
|
441
|
-
Use the CLI to invoke these manually.
|
|
442
|
-
|
|
443
|
-
### Build
|
|
444
|
-
|
|
445
|
-
```bash
|
|
446
|
-
pnpm build
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
Produces `dist/index.js` (ESM), `dist/index.cjs` (CommonJS), and `dist/index.d.ts` (type declarations) via [tsup](https://tsup.egoist.dev/).
|
|
450
|
-
|
|
451
|
-
### Checks
|
|
452
|
-
|
|
453
|
-
| Command | Purpose |
|
|
454
|
-
|---|---|
|
|
455
|
-
| `pnpm typecheck` | TypeScript strict type checking |
|
|
456
|
-
| `pnpm test` | Run all tests |
|
|
457
|
-
| `pnpm build` | Build CJS + ESM + `.d.ts` |
|
|
458
|
-
| `pnpm prepublishOnly` | Build + typecheck (runs automatically before `pnpm publish`) |
|
|
304
|
+
See [docs/development.md](docs/development.md) for setup, tests, E2E tests, and build instructions.
|
|
459
305
|
|
|
460
306
|
---
|
package/dist/cli.cjs
CHANGED
|
@@ -29,7 +29,7 @@ var import_commander = require("commander");
|
|
|
29
29
|
// package.json
|
|
30
30
|
var package_default = {
|
|
31
31
|
name: "node-chargepoint",
|
|
32
|
-
version: "0.
|
|
32
|
+
version: "0.4.0",
|
|
33
33
|
description: "A Node.js/TypeScript wrapper for the ChargePoint EV charging network API. Based on python-chargepoint by Marc Billow.",
|
|
34
34
|
keywords: [
|
|
35
35
|
"chargepoint",
|
|
@@ -80,7 +80,7 @@ var package_default = {
|
|
|
80
80
|
prepublishOnly: "pnpm build && pnpm typecheck"
|
|
81
81
|
},
|
|
82
82
|
engines: {
|
|
83
|
-
node: ">=
|
|
83
|
+
node: ">=22"
|
|
84
84
|
},
|
|
85
85
|
packageManager: "pnpm@11.0.9+sha512.34ce82e6780233cf9cad8685029a8f81d2e06196c5a9bad98879f7424940c6817c4e4524fb7d38b8553ceed48b9758b8ebaf1abd3600c232c4c8cf7366086f38",
|
|
86
86
|
dependencies: {
|
|
@@ -100,6 +100,7 @@ var package_default = {
|
|
|
100
100
|
// src/constants.ts
|
|
101
101
|
var DISCOVERY_API = "https://discovery.chargepoint.com/discovery/v3/globalconfig";
|
|
102
102
|
var VERSION = package_default.version;
|
|
103
|
+
var NODE_CHARGEPOINT_VERSION = package_default.version;
|
|
103
104
|
var USER_AGENT = `node-chargepoint/${VERSION}`;
|
|
104
105
|
|
|
105
106
|
// src/exceptions.ts
|
|
@@ -404,12 +405,12 @@ var ChargingSession = class _ChargingSession {
|
|
|
404
405
|
this.sessionId
|
|
405
406
|
);
|
|
406
407
|
}
|
|
407
|
-
static async start(deviceId, client) {
|
|
408
|
+
static async start(deviceId, client, options) {
|
|
408
409
|
await sendCommand(client, "start", deviceId);
|
|
409
|
-
const deadline = Date.now() + 15e3;
|
|
410
|
+
const deadline = Date.now() + (options?.pollTimeoutMs ?? 15e3);
|
|
410
411
|
let status = await client.getUserChargingStatus();
|
|
411
412
|
while (!status && Date.now() < deadline) {
|
|
412
|
-
await sleep(2e3);
|
|
413
|
+
await sleep(options?.pollIntervalMs ?? 2e3);
|
|
413
414
|
status = await client.getUserChargingStatus();
|
|
414
415
|
}
|
|
415
416
|
if (!status) {
|
|
@@ -434,6 +435,8 @@ var ChargePoint = class _ChargePoint {
|
|
|
434
435
|
_coulombToken = null;
|
|
435
436
|
_region;
|
|
436
437
|
_userId = null;
|
|
438
|
+
_timeout;
|
|
439
|
+
_debug;
|
|
437
440
|
/** The current session token. Save this after login to avoid re-authenticating. */
|
|
438
441
|
get coulombToken() {
|
|
439
442
|
return this._coulombToken;
|
|
@@ -450,10 +453,16 @@ var ChargePoint = class _ChargePoint {
|
|
|
450
453
|
if (options.coulombToken) {
|
|
451
454
|
client._setToken(options.coulombToken, region);
|
|
452
455
|
}
|
|
456
|
+
client._timeout = options.timeout;
|
|
457
|
+
client._debug = options.debug;
|
|
453
458
|
return client;
|
|
454
459
|
}
|
|
455
460
|
_setToken(token, region) {
|
|
456
|
-
|
|
461
|
+
try {
|
|
462
|
+
this._coulombToken = decodeURIComponent(token);
|
|
463
|
+
} catch {
|
|
464
|
+
throw new APIError("Malformed coulomb_sess token: invalid percent-encoding");
|
|
465
|
+
}
|
|
457
466
|
this._region = region;
|
|
458
467
|
}
|
|
459
468
|
/** @internal Used by session.ts and tests. */
|
|
@@ -469,12 +478,19 @@ var ChargePoint = class _ChargePoint {
|
|
|
469
478
|
headers.set("cp-session-token", this._coulombToken);
|
|
470
479
|
headers.set("cp-region", this._region);
|
|
471
480
|
}
|
|
472
|
-
|
|
481
|
+
this._debug?.(`${method} ${url}`);
|
|
482
|
+
const signal = this._timeout !== void 0 ? AbortSignal.timeout(this._timeout) : void 0;
|
|
483
|
+
const response = await fetch(url, { ...init, method, headers, signal });
|
|
484
|
+
this._debug?.(`${response.status} ${url}`);
|
|
473
485
|
const setCookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [response.headers.get("set-cookie") ?? ""].filter(Boolean);
|
|
474
486
|
for (const cookie of setCookies) {
|
|
475
487
|
const match = /^coulomb_sess=([^;]+)/.exec(cookie);
|
|
476
488
|
if (match) {
|
|
477
|
-
|
|
489
|
+
try {
|
|
490
|
+
this._coulombToken = decodeURIComponent(match[1] ?? "");
|
|
491
|
+
} catch {
|
|
492
|
+
throw new CommunicationError(response.status, "Malformed coulomb_sess cookie: invalid percent-encoding");
|
|
493
|
+
}
|
|
478
494
|
break;
|
|
479
495
|
}
|
|
480
496
|
}
|
|
@@ -773,8 +789,8 @@ var ChargePoint = class _ChargePoint {
|
|
|
773
789
|
await session.refresh();
|
|
774
790
|
return session;
|
|
775
791
|
}
|
|
776
|
-
async startChargingSession(deviceId) {
|
|
777
|
-
return ChargingSession.start(deviceId, this);
|
|
792
|
+
async startChargingSession(deviceId, options) {
|
|
793
|
+
return ChargingSession.start(deviceId, this, options);
|
|
778
794
|
}
|
|
779
795
|
// ---------------------------------------------------------------------------
|
|
780
796
|
// Stations
|
package/dist/index.cjs
CHANGED
|
@@ -8,11 +8,12 @@ var https__default = /*#__PURE__*/_interopDefault(https);
|
|
|
8
8
|
|
|
9
9
|
// package.json
|
|
10
10
|
var package_default = {
|
|
11
|
-
version: "0.
|
|
11
|
+
version: "0.4.0"};
|
|
12
12
|
|
|
13
13
|
// src/constants.ts
|
|
14
14
|
var DISCOVERY_API = "https://discovery.chargepoint.com/discovery/v3/globalconfig";
|
|
15
15
|
var VERSION = package_default.version;
|
|
16
|
+
var NODE_CHARGEPOINT_VERSION = package_default.version;
|
|
16
17
|
var USER_AGENT = `node-chargepoint/${VERSION}`;
|
|
17
18
|
|
|
18
19
|
// src/exceptions.ts
|
|
@@ -314,12 +315,12 @@ var ChargingSession = class _ChargingSession {
|
|
|
314
315
|
this.sessionId
|
|
315
316
|
);
|
|
316
317
|
}
|
|
317
|
-
static async start(deviceId, client) {
|
|
318
|
+
static async start(deviceId, client, options) {
|
|
318
319
|
await sendCommand(client, "start", deviceId);
|
|
319
|
-
const deadline = Date.now() + 15e3;
|
|
320
|
+
const deadline = Date.now() + (options?.pollTimeoutMs ?? 15e3);
|
|
320
321
|
let status = await client.getUserChargingStatus();
|
|
321
322
|
while (!status && Date.now() < deadline) {
|
|
322
|
-
await sleep(2e3);
|
|
323
|
+
await sleep(options?.pollIntervalMs ?? 2e3);
|
|
323
324
|
status = await client.getUserChargingStatus();
|
|
324
325
|
}
|
|
325
326
|
if (!status) {
|
|
@@ -344,6 +345,8 @@ var ChargePoint = class _ChargePoint {
|
|
|
344
345
|
_coulombToken = null;
|
|
345
346
|
_region;
|
|
346
347
|
_userId = null;
|
|
348
|
+
_timeout;
|
|
349
|
+
_debug;
|
|
347
350
|
/** The current session token. Save this after login to avoid re-authenticating. */
|
|
348
351
|
get coulombToken() {
|
|
349
352
|
return this._coulombToken;
|
|
@@ -360,10 +363,16 @@ var ChargePoint = class _ChargePoint {
|
|
|
360
363
|
if (options.coulombToken) {
|
|
361
364
|
client._setToken(options.coulombToken, region);
|
|
362
365
|
}
|
|
366
|
+
client._timeout = options.timeout;
|
|
367
|
+
client._debug = options.debug;
|
|
363
368
|
return client;
|
|
364
369
|
}
|
|
365
370
|
_setToken(token, region) {
|
|
366
|
-
|
|
371
|
+
try {
|
|
372
|
+
this._coulombToken = decodeURIComponent(token);
|
|
373
|
+
} catch {
|
|
374
|
+
throw new APIError("Malformed coulomb_sess token: invalid percent-encoding");
|
|
375
|
+
}
|
|
367
376
|
this._region = region;
|
|
368
377
|
}
|
|
369
378
|
/** @internal Used by session.ts and tests. */
|
|
@@ -379,12 +388,19 @@ var ChargePoint = class _ChargePoint {
|
|
|
379
388
|
headers.set("cp-session-token", this._coulombToken);
|
|
380
389
|
headers.set("cp-region", this._region);
|
|
381
390
|
}
|
|
382
|
-
|
|
391
|
+
this._debug?.(`${method} ${url}`);
|
|
392
|
+
const signal = this._timeout !== void 0 ? AbortSignal.timeout(this._timeout) : void 0;
|
|
393
|
+
const response = await fetch(url, { ...init, method, headers, signal });
|
|
394
|
+
this._debug?.(`${response.status} ${url}`);
|
|
383
395
|
const setCookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [response.headers.get("set-cookie") ?? ""].filter(Boolean);
|
|
384
396
|
for (const cookie of setCookies) {
|
|
385
397
|
const match = /^coulomb_sess=([^;]+)/.exec(cookie);
|
|
386
398
|
if (match) {
|
|
387
|
-
|
|
399
|
+
try {
|
|
400
|
+
this._coulombToken = decodeURIComponent(match[1] ?? "");
|
|
401
|
+
} catch {
|
|
402
|
+
throw new CommunicationError(response.status, "Malformed coulomb_sess cookie: invalid percent-encoding");
|
|
403
|
+
}
|
|
388
404
|
break;
|
|
389
405
|
}
|
|
390
406
|
}
|
|
@@ -683,8 +699,8 @@ var ChargePoint = class _ChargePoint {
|
|
|
683
699
|
await session.refresh();
|
|
684
700
|
return session;
|
|
685
701
|
}
|
|
686
|
-
async startChargingSession(deviceId) {
|
|
687
|
-
return ChargingSession.start(deviceId, this);
|
|
702
|
+
async startChargingSession(deviceId, options) {
|
|
703
|
+
return ChargingSession.start(deviceId, this, options);
|
|
688
704
|
}
|
|
689
705
|
// ---------------------------------------------------------------------------
|
|
690
706
|
// Stations
|
|
@@ -726,5 +742,6 @@ exports.CommunicationError = CommunicationError;
|
|
|
726
742
|
exports.DatadomeCaptcha = DatadomeCaptcha;
|
|
727
743
|
exports.InvalidSession = InvalidSession;
|
|
728
744
|
exports.LoginError = LoginError;
|
|
745
|
+
exports.NODE_CHARGEPOINT_VERSION = NODE_CHARGEPOINT_VERSION;
|
|
729
746
|
//# sourceMappingURL=index.cjs.map
|
|
730
747
|
//# sourceMappingURL=index.cjs.map
|