homebridge-kia 1.0.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 +108 -0
- package/config.schema.json +51 -0
- package/dist/homebridge-ui/config.d.ts +5 -0
- package/dist/homebridge-ui/config.js +23 -0
- package/dist/homebridge-ui/config.js.map +1 -0
- package/dist/homebridge-ui/public/index.html +196 -0
- package/dist/homebridge-ui/server.d.ts +1 -0
- package/dist/homebridge-ui/server.js +126 -0
- package/dist/homebridge-ui/server.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/kia/auth.d.ts +21 -0
- package/dist/src/kia/auth.js +106 -0
- package/dist/src/kia/auth.js.map +1 -0
- package/dist/src/kia/client.d.ts +27 -0
- package/dist/src/kia/client.js +456 -0
- package/dist/src/kia/client.js.map +1 -0
- package/dist/src/kia/crypto.d.ts +9 -0
- package/dist/src/kia/crypto.js +32 -0
- package/dist/src/kia/crypto.js.map +1 -0
- package/dist/src/kia/types.d.ts +86 -0
- package/dist/src/kia/types.js +17 -0
- package/dist/src/kia/types.js.map +1 -0
- package/dist/src/platform.d.ts +27 -0
- package/dist/src/platform.js +192 -0
- package/dist/src/platform.js.map +1 -0
- package/dist/src/settings.d.ts +8 -0
- package/dist/src/settings.js +9 -0
- package/dist/src/settings.js.map +1 -0
- package/dist/src/vehicle-accessory.d.ts +23 -0
- package/dist/src/vehicle-accessory.js +259 -0
- package/dist/src/vehicle-accessory.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# homebridge-kia-connect
|
|
2
|
+
|
|
3
|
+
Homebridge plugin for Kia Connect vehicles.
|
|
4
|
+
|
|
5
|
+
This plugin exposes a Kia vehicle to Apple Home through Homebridge, including vehicle status sensors and a small set of remote controls.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Vehicle status in HomeKit
|
|
10
|
+
- Door lock / unlock
|
|
11
|
+
- Remote climate start / stop
|
|
12
|
+
- Fuel level
|
|
13
|
+
- 12V battery level
|
|
14
|
+
- Outside temperature
|
|
15
|
+
- Engine running state
|
|
16
|
+
- Door, window, hood, and trunk sensors
|
|
17
|
+
- Tire pressure warning
|
|
18
|
+
- OTP-assisted Kia Connect authentication through the Homebridge UI
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Node.js `20.18.0` or newer
|
|
23
|
+
- Homebridge `1.8.0` or newer
|
|
24
|
+
- A Kia Connect account for a supported US Kia vehicle
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Install through the Homebridge UI, or with npm:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g homebridge-kia
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then restart Homebridge.
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
The plugin is a dynamic platform and uses the following settings:
|
|
39
|
+
|
|
40
|
+
- `username`: Kia Connect email
|
|
41
|
+
- `password`: Kia Connect password
|
|
42
|
+
- `vehicleIndex`: Which vehicle to use if your account has multiple vehicles
|
|
43
|
+
- `pollIntervalMinutes`: Refresh interval, minimum `5`
|
|
44
|
+
- `enableDoorLock`: Show the HomeKit lock service
|
|
45
|
+
- `enableClimateControl`: Show the HomeKit climate switch
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"platform": "KiaConnect",
|
|
52
|
+
"name": "Kia Connect",
|
|
53
|
+
"username": "you@example.com",
|
|
54
|
+
"password": "your-password",
|
|
55
|
+
"vehicleIndex": 0,
|
|
56
|
+
"pollIntervalMinutes": 30,
|
|
57
|
+
"enableDoorLock": true,
|
|
58
|
+
"enableClimateControl": true
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Authentication / OTP
|
|
63
|
+
|
|
64
|
+
If Kia Connect requires a one-time password:
|
|
65
|
+
|
|
66
|
+
1. Open the plugin settings in Homebridge.
|
|
67
|
+
2. Enter your Kia Connect email and password if needed.
|
|
68
|
+
3. Click `Login`.
|
|
69
|
+
4. Choose `Email` or `SMS` for the OTP.
|
|
70
|
+
5. Enter the code and verify it.
|
|
71
|
+
6. Restart Homebridge after authentication succeeds.
|
|
72
|
+
|
|
73
|
+
## HomeKit Services
|
|
74
|
+
|
|
75
|
+
This plugin creates one accessory for the selected vehicle and can expose:
|
|
76
|
+
|
|
77
|
+
- `LockMechanism` for door lock control
|
|
78
|
+
- `Switch` for climate control
|
|
79
|
+
- `Battery` for 12V battery level
|
|
80
|
+
- `HumiditySensor` for fuel level
|
|
81
|
+
- `TemperatureSensor` for outside temperature
|
|
82
|
+
- `OccupancySensor` for engine running
|
|
83
|
+
- `ContactSensor` services for doors, windows, hood, and trunk
|
|
84
|
+
- `LeakSensor` for tire pressure warning
|
|
85
|
+
|
|
86
|
+
## Notes
|
|
87
|
+
|
|
88
|
+
- This plugin currently targets the US Kia Connect API.
|
|
89
|
+
- Climate control uses a fixed start temperature of `72F`.
|
|
90
|
+
- Polling is periodic and should not be set aggressively.
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm install
|
|
96
|
+
npm run build
|
|
97
|
+
npm run lint
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Project layout:
|
|
101
|
+
|
|
102
|
+
- [src](/Users/jordanfreund/Desktop/homebridge-kia-connect/src)
|
|
103
|
+
- [homebridge-ui](/Users/jordanfreund/Desktop/homebridge-kia-connect/homebridge-ui)
|
|
104
|
+
- [config.schema.json](/Users/jordanfreund/Desktop/homebridge-kia-connect/config.schema.json)
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
ISC
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pluginAlias": "KiaConnect",
|
|
3
|
+
"pluginType": "platform",
|
|
4
|
+
"singular": true,
|
|
5
|
+
"customUi": true,
|
|
6
|
+
"customUiPath": "./dist/homebridge-ui",
|
|
7
|
+
"schema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"name": {
|
|
11
|
+
"title": "Name",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"default": "Kia Connect",
|
|
14
|
+
"required": true
|
|
15
|
+
},
|
|
16
|
+
"username": {
|
|
17
|
+
"title": "Kia Connect Email",
|
|
18
|
+
"type": "string",
|
|
19
|
+
"required": true
|
|
20
|
+
},
|
|
21
|
+
"password": {
|
|
22
|
+
"title": "Kia Connect Password",
|
|
23
|
+
"type": "string",
|
|
24
|
+
"required": true
|
|
25
|
+
},
|
|
26
|
+
"pollIntervalMinutes": {
|
|
27
|
+
"title": "Poll Interval (minutes)",
|
|
28
|
+
"type": "integer",
|
|
29
|
+
"default": 30,
|
|
30
|
+
"minimum": 5
|
|
31
|
+
},
|
|
32
|
+
"enableClimateControl": {
|
|
33
|
+
"title": "Enable Climate Control",
|
|
34
|
+
"type": "boolean",
|
|
35
|
+
"default": true
|
|
36
|
+
},
|
|
37
|
+
"enableDoorLock": {
|
|
38
|
+
"title": "Enable Door Lock",
|
|
39
|
+
"type": "boolean",
|
|
40
|
+
"default": true
|
|
41
|
+
},
|
|
42
|
+
"vehicleIndex": {
|
|
43
|
+
"title": "Vehicle Index",
|
|
44
|
+
"type": "integer",
|
|
45
|
+
"default": 0,
|
|
46
|
+
"minimum": 0,
|
|
47
|
+
"description": "If you have multiple vehicles, specify the index (0 = first vehicle)"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
export function readSavedCredentials(homebridgeConfigPath) {
|
|
3
|
+
if (!homebridgeConfigPath) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
const config = JSON.parse(readFileSync(homebridgeConfigPath, 'utf-8'));
|
|
8
|
+
const pluginConfig = config.platforms?.find(platform => (platform.platform === 'KiaConnect'
|
|
9
|
+
&& typeof platform.username === 'string'
|
|
10
|
+
&& typeof platform.password === 'string'));
|
|
11
|
+
if (!pluginConfig) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
username: pluginConfig.username,
|
|
16
|
+
password: pluginConfig.password,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../homebridge-ui/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIvC,MAAM,UAAU,oBAAoB,CAAC,oBAA6B;IAChE,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAEpE,CAAC;QAEF,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CACtD,QAAQ,CAAC,QAAQ,KAAK,YAAY;eAC/B,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ;eACrC,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CACzC,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO;YACL,QAAQ,EAAE,YAAY,CAAC,QAAkB;YACzC,QAAQ,EAAE,YAAY,CAAC,QAAkB;SAC1C,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.status-card { margin-bottom: 1rem; }
|
|
3
|
+
.otp-section { display: none; }
|
|
4
|
+
.success-section { display: none; }
|
|
5
|
+
.btn-group { margin-bottom: 0.5rem; }
|
|
6
|
+
#status-message { font-weight: bold; }
|
|
7
|
+
</style>
|
|
8
|
+
|
|
9
|
+
<!-- Status -->
|
|
10
|
+
<div class="card status-card">
|
|
11
|
+
<div class="card-body">
|
|
12
|
+
<h5 class="card-title">Kia Connect Authentication</h5>
|
|
13
|
+
<div class="form-group">
|
|
14
|
+
<label for="username">Kia Connect Email</label>
|
|
15
|
+
<input type="email" id="username" class="form-control" autocomplete="username">
|
|
16
|
+
</div>
|
|
17
|
+
<div class="form-group">
|
|
18
|
+
<label for="password">Kia Connect Password</label>
|
|
19
|
+
<input type="password" id="password" class="form-control" autocomplete="current-password">
|
|
20
|
+
</div>
|
|
21
|
+
<p id="status-message">Checking authentication status...</p>
|
|
22
|
+
<button id="login-btn" class="btn btn-primary" style="display:none" onclick="doLogin()">Login</button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<!-- OTP Section -->
|
|
27
|
+
<div id="otp-section" class="card otp-section">
|
|
28
|
+
<div class="card-body">
|
|
29
|
+
<h5 class="card-title">One-Time Password Required</h5>
|
|
30
|
+
<p id="otp-info"></p>
|
|
31
|
+
<div class="btn-group">
|
|
32
|
+
<button class="btn btn-outline-primary btn-sm" onclick="sendOtp('EMAIL')">Send via Email</button>
|
|
33
|
+
<button class="btn btn-outline-secondary btn-sm" onclick="sendOtp('SMS')">Send via SMS</button>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="input-group mt-3">
|
|
36
|
+
<input type="text" id="otp-code" class="form-control" placeholder="Enter OTP code" maxlength="6">
|
|
37
|
+
<button class="btn btn-success" onclick="verifyOtp()">Verify</button>
|
|
38
|
+
</div>
|
|
39
|
+
<p id="otp-status" class="mt-2 text-muted"></p>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- Success -->
|
|
44
|
+
<div id="success-section" class="card success-section">
|
|
45
|
+
<div class="card-body">
|
|
46
|
+
<h5 class="card-title text-success">Authenticated</h5>
|
|
47
|
+
<p>Kia Connect is authenticated. Your vehicle will be loaded automatically within a few seconds.</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<script>
|
|
52
|
+
let currentConfig = {};
|
|
53
|
+
let pluginAlias = 'KiaConnect';
|
|
54
|
+
let pendingOtpCredentials = null;
|
|
55
|
+
|
|
56
|
+
async function loadConfig() {
|
|
57
|
+
const pluginConfig = await homebridge.getPluginConfig();
|
|
58
|
+
const schema = await homebridge.getPluginConfigSchema();
|
|
59
|
+
pluginAlias = schema.pluginAlias || pluginAlias;
|
|
60
|
+
currentConfig = (pluginConfig && pluginConfig[0]) || {};
|
|
61
|
+
document.getElementById('username').value = currentConfig.username || '';
|
|
62
|
+
document.getElementById('password').value = currentConfig.password || '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function persistCredentials(username, password) {
|
|
66
|
+
const nextConfig = {
|
|
67
|
+
platform: currentConfig.platform || pluginAlias,
|
|
68
|
+
name: currentConfig.name || 'Kia Connect',
|
|
69
|
+
...currentConfig,
|
|
70
|
+
username,
|
|
71
|
+
password,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
await homebridge.updatePluginConfig([nextConfig]);
|
|
75
|
+
await homebridge.savePluginConfig();
|
|
76
|
+
currentConfig = nextConfig;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function setCredentialInputsDisabled(disabled) {
|
|
80
|
+
document.getElementById('username').disabled = disabled;
|
|
81
|
+
document.getElementById('password').disabled = disabled;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
(async function checkStatus() {
|
|
85
|
+
try {
|
|
86
|
+
await loadConfig();
|
|
87
|
+
const res = await homebridge.request('/auth/status');
|
|
88
|
+
if (res.authenticated) {
|
|
89
|
+
document.getElementById('status-message').textContent = 'Authenticated';
|
|
90
|
+
document.getElementById('status-message').className = 'text-success';
|
|
91
|
+
document.getElementById('success-section').style.display = 'block';
|
|
92
|
+
} else {
|
|
93
|
+
document.getElementById('status-message').textContent = 'Not authenticated';
|
|
94
|
+
document.getElementById('login-btn').style.display = 'inline-block';
|
|
95
|
+
}
|
|
96
|
+
} catch (e) {
|
|
97
|
+
document.getElementById('status-message').textContent = 'Not authenticated';
|
|
98
|
+
document.getElementById('login-btn').style.display = 'inline-block';
|
|
99
|
+
}
|
|
100
|
+
})();
|
|
101
|
+
|
|
102
|
+
async function doLogin() {
|
|
103
|
+
const btn = document.getElementById('login-btn');
|
|
104
|
+
const username = document.getElementById('username').value.trim();
|
|
105
|
+
const password = document.getElementById('password').value;
|
|
106
|
+
if (!username || !password) {
|
|
107
|
+
homebridge.toast.error('Enter your Kia Connect email and password first.');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
btn.disabled = true;
|
|
112
|
+
btn.textContent = 'Logging in...';
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const res = await homebridge.request('/auth/login', {
|
|
116
|
+
username,
|
|
117
|
+
password,
|
|
118
|
+
});
|
|
119
|
+
if (res.success) {
|
|
120
|
+
pendingOtpCredentials = null;
|
|
121
|
+
await persistCredentials(username, password);
|
|
122
|
+
document.getElementById('status-message').textContent = 'Authenticated!';
|
|
123
|
+
document.getElementById('status-message').className = 'text-success';
|
|
124
|
+
btn.style.display = 'none';
|
|
125
|
+
document.getElementById('success-section').style.display = 'block';
|
|
126
|
+
homebridge.toast.success('Login successful!');
|
|
127
|
+
} else if (res.otpRequired) {
|
|
128
|
+
pendingOtpCredentials = { username, password };
|
|
129
|
+
setCredentialInputsDisabled(true);
|
|
130
|
+
btn.style.display = 'none';
|
|
131
|
+
document.getElementById('status-message').textContent = 'OTP verification required';
|
|
132
|
+
document.getElementById('otp-section').style.display = 'block';
|
|
133
|
+
let info = 'A one-time password is required.';
|
|
134
|
+
if (res.email) info += ' Email: ' + res.email;
|
|
135
|
+
if (res.sms) info += ' SMS: ' + res.sms;
|
|
136
|
+
document.getElementById('otp-info').textContent = info;
|
|
137
|
+
} else {
|
|
138
|
+
pendingOtpCredentials = null;
|
|
139
|
+
setCredentialInputsDisabled(false);
|
|
140
|
+
homebridge.toast.error('Login failed. Check your credentials.');
|
|
141
|
+
btn.disabled = false;
|
|
142
|
+
btn.textContent = 'Login';
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
pendingOtpCredentials = null;
|
|
146
|
+
setCredentialInputsDisabled(false);
|
|
147
|
+
homebridge.toast.error('Login failed: ' + (e.message || e));
|
|
148
|
+
btn.disabled = false;
|
|
149
|
+
btn.textContent = 'Login';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function sendOtp(method) {
|
|
154
|
+
const statusEl = document.getElementById('otp-status');
|
|
155
|
+
statusEl.textContent = 'Sending OTP via ' + method + '...';
|
|
156
|
+
try {
|
|
157
|
+
await homebridge.request('/auth/send-otp', { method });
|
|
158
|
+
statusEl.textContent = 'OTP sent via ' + method + '. Check your ' + (method === 'EMAIL' ? 'email' : 'phone') + '.';
|
|
159
|
+
statusEl.className = 'mt-2 text-success';
|
|
160
|
+
homebridge.toast.success('OTP sent!');
|
|
161
|
+
} catch (e) {
|
|
162
|
+
statusEl.textContent = 'Failed to send OTP: ' + (e.message || e);
|
|
163
|
+
statusEl.className = 'mt-2 text-danger';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function verifyOtp() {
|
|
168
|
+
const code = document.getElementById('otp-code').value.trim();
|
|
169
|
+
if (!code) {
|
|
170
|
+
homebridge.toast.error('Please enter the OTP code');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const statusEl = document.getElementById('otp-status');
|
|
175
|
+
statusEl.textContent = 'Verifying...';
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const res = await homebridge.request('/auth/verify-otp', { code });
|
|
179
|
+
if (res.success) {
|
|
180
|
+
if (pendingOtpCredentials) {
|
|
181
|
+
await persistCredentials(pendingOtpCredentials.username, pendingOtpCredentials.password);
|
|
182
|
+
}
|
|
183
|
+
pendingOtpCredentials = null;
|
|
184
|
+
document.getElementById('otp-section').style.display = 'none';
|
|
185
|
+
document.getElementById('success-section').style.display = 'block';
|
|
186
|
+
document.getElementById('status-message').textContent = 'Authenticated!';
|
|
187
|
+
document.getElementById('status-message').className = 'text-success';
|
|
188
|
+
homebridge.toast.success('Authentication successful! Restart Homebridge to load your vehicle.');
|
|
189
|
+
}
|
|
190
|
+
} catch (e) {
|
|
191
|
+
statusEl.textContent = 'Verification failed: ' + (e.message || e);
|
|
192
|
+
statusEl.className = 'mt-2 text-danger';
|
|
193
|
+
homebridge.toast.error('OTP verification failed');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
</script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { HomebridgePluginUiServer, RequestError } from '@homebridge/plugin-ui-utils/dist/server.js';
|
|
2
|
+
import { KiaAuthManager } from '../src/kia/auth.js';
|
|
3
|
+
import { KiaApiClient } from '../src/kia/client.js';
|
|
4
|
+
import { AuthenticationError } from '../src/kia/types.js';
|
|
5
|
+
import { readSavedCredentials } from './config.js';
|
|
6
|
+
function createConsoleLogger(prefix) {
|
|
7
|
+
return {
|
|
8
|
+
info: (...args) => console.log(`[${prefix}]`, ...args),
|
|
9
|
+
warn: (...args) => console.warn(`[${prefix}]`, ...args),
|
|
10
|
+
error: (...args) => console.error(`[${prefix}]`, ...args),
|
|
11
|
+
debug: (...args) => console.debug(`[${prefix}]`, ...args),
|
|
12
|
+
log: (...args) => console.log(`[${prefix}]`, ...args),
|
|
13
|
+
success: (...args) => console.log(`[${prefix}]`, ...args),
|
|
14
|
+
prefix,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
class KiaConnectUiServer extends HomebridgePluginUiServer {
|
|
18
|
+
authManager;
|
|
19
|
+
apiClient;
|
|
20
|
+
otpState;
|
|
21
|
+
pendingCredentials;
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.onRequest('/auth/status', this.handleAuthStatus.bind(this));
|
|
25
|
+
this.onRequest('/auth/login', this.handleLogin.bind(this));
|
|
26
|
+
this.onRequest('/auth/send-otp', this.handleSendOtp.bind(this));
|
|
27
|
+
this.onRequest('/auth/verify-otp', this.handleVerifyOtp.bind(this));
|
|
28
|
+
this.ready();
|
|
29
|
+
}
|
|
30
|
+
getClients() {
|
|
31
|
+
if (!this.authManager) {
|
|
32
|
+
const storagePath = this.homebridgeStoragePath ?? '/tmp';
|
|
33
|
+
const log = createConsoleLogger('KiaConnect');
|
|
34
|
+
const savedCredentials = readSavedCredentials(this.homebridgeConfigPath);
|
|
35
|
+
this.authManager = new KiaAuthManager(storagePath, log);
|
|
36
|
+
const username = this.pendingCredentials?.username ?? savedCredentials?.username ?? '';
|
|
37
|
+
const password = this.pendingCredentials?.password ?? savedCredentials?.password ?? '';
|
|
38
|
+
this.apiClient = new KiaApiClient(this.authManager, log, username, password);
|
|
39
|
+
}
|
|
40
|
+
return { authManager: this.authManager, apiClient: this.apiClient };
|
|
41
|
+
}
|
|
42
|
+
async handleAuthStatus() {
|
|
43
|
+
const { authManager, apiClient } = this.getClients();
|
|
44
|
+
authManager.reloadToken();
|
|
45
|
+
const isValid = authManager.isTokenValid();
|
|
46
|
+
if (!isValid) {
|
|
47
|
+
return { authenticated: false };
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
await apiClient.getVehicles();
|
|
51
|
+
return { authenticated: true };
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e instanceof AuthenticationError) {
|
|
55
|
+
authManager.clearToken();
|
|
56
|
+
}
|
|
57
|
+
return { authenticated: false };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async handleLogin(payload) {
|
|
61
|
+
// Re-create clients with credentials if provided
|
|
62
|
+
if (payload?.username && payload?.password) {
|
|
63
|
+
this.authManager = undefined;
|
|
64
|
+
this.apiClient = undefined;
|
|
65
|
+
this.pendingCredentials = { username: payload.username, password: payload.password };
|
|
66
|
+
}
|
|
67
|
+
const { apiClient } = this.getClients();
|
|
68
|
+
try {
|
|
69
|
+
const result = await apiClient.login();
|
|
70
|
+
if (result.success) {
|
|
71
|
+
return { success: true };
|
|
72
|
+
}
|
|
73
|
+
if (result.otpRequired && result.otpState) {
|
|
74
|
+
this.otpState = result.otpState;
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
otpRequired: true,
|
|
78
|
+
email: result.otpState.email,
|
|
79
|
+
sms: result.otpState.sms,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return { success: false };
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
const message = e instanceof Error ? e.message : 'Login failed';
|
|
86
|
+
throw new RequestError(message, { status: 500 });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async handleSendOtp(payload) {
|
|
90
|
+
if (!this.otpState) {
|
|
91
|
+
throw new RequestError('No OTP session in progress. Login first.', { status: 400 });
|
|
92
|
+
}
|
|
93
|
+
const { apiClient } = this.getClients();
|
|
94
|
+
try {
|
|
95
|
+
await apiClient.sendOtp(this.otpState, payload.method ?? 'EMAIL');
|
|
96
|
+
return { success: true };
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
const message = e instanceof Error ? e.message : 'Failed to send OTP';
|
|
100
|
+
throw new RequestError(message, { status: 500 });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async handleVerifyOtp(payload) {
|
|
104
|
+
if (!this.otpState) {
|
|
105
|
+
throw new RequestError('No OTP session in progress. Login first.', { status: 400 });
|
|
106
|
+
}
|
|
107
|
+
const { apiClient } = this.getClients();
|
|
108
|
+
try {
|
|
109
|
+
const success = await apiClient.verifyOtp(this.otpState, payload.code);
|
|
110
|
+
if (success) {
|
|
111
|
+
this.otpState = undefined;
|
|
112
|
+
return { success: true };
|
|
113
|
+
}
|
|
114
|
+
throw new RequestError('OTP verification failed', { status: 400 });
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
if (e instanceof RequestError) {
|
|
118
|
+
throw e;
|
|
119
|
+
}
|
|
120
|
+
const message = e instanceof Error ? e.message : 'OTP verification failed';
|
|
121
|
+
throw new RequestError(message, { status: 500 });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
(() => new KiaConnectUiServer())();
|
|
126
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../homebridge-ui/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,eAAe,CAAC,GAAQ,EAAE,EAAE;IAC1B,GAAG,CAAC,gBAAgB,CAAC,aAAa,EAAE,kBAAkB,CAAC,CAAC;AAC1D,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Logger } from 'homebridge';
|
|
2
|
+
import type { PersistedToken } from './types.js';
|
|
3
|
+
export declare class KiaAuthManager {
|
|
4
|
+
private readonly storagePath;
|
|
5
|
+
private readonly log;
|
|
6
|
+
private readonly tokenPath;
|
|
7
|
+
private token;
|
|
8
|
+
private cachedDeviceId;
|
|
9
|
+
constructor(storagePath: string, log: Logger);
|
|
10
|
+
loadToken(): PersistedToken | null;
|
|
11
|
+
saveToken(token: PersistedToken): void;
|
|
12
|
+
isTokenValid(): boolean;
|
|
13
|
+
getAccessToken(): string | null;
|
|
14
|
+
getRefreshToken(): string | null;
|
|
15
|
+
getDeviceId(): string;
|
|
16
|
+
getVehicleKey(): string | null;
|
|
17
|
+
reloadToken(): void;
|
|
18
|
+
updateToken(accessToken: string, refreshToken: string, deviceId?: string): void;
|
|
19
|
+
setVehicleKey(vehicleKey: string): void;
|
|
20
|
+
clearToken(): void;
|
|
21
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { generateDeviceId } from './crypto.js';
|
|
4
|
+
import { SESSION_LIFETIME_MS } from '../settings.js';
|
|
5
|
+
export class KiaAuthManager {
|
|
6
|
+
storagePath;
|
|
7
|
+
log;
|
|
8
|
+
tokenPath;
|
|
9
|
+
token = null;
|
|
10
|
+
cachedDeviceId = null;
|
|
11
|
+
constructor(storagePath, log) {
|
|
12
|
+
this.storagePath = storagePath;
|
|
13
|
+
this.log = log;
|
|
14
|
+
this.tokenPath = join(storagePath, 'kia-connect-token.json');
|
|
15
|
+
this.token = this.loadToken();
|
|
16
|
+
}
|
|
17
|
+
loadToken() {
|
|
18
|
+
try {
|
|
19
|
+
if (!existsSync(this.tokenPath)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const data = readFileSync(this.tokenPath, 'utf-8');
|
|
23
|
+
const parsed = JSON.parse(data);
|
|
24
|
+
if (parsed.accessToken && parsed.refreshToken && parsed.deviceId) {
|
|
25
|
+
this.log.debug('Loaded persisted token');
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
this.log.warn('Could not load persisted token:', e);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
saveToken(token) {
|
|
36
|
+
try {
|
|
37
|
+
if (!existsSync(this.storagePath)) {
|
|
38
|
+
mkdirSync(this.storagePath, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
const tmpPath = this.tokenPath + '.tmp';
|
|
41
|
+
writeFileSync(tmpPath, JSON.stringify(token, null, 2), 'utf-8');
|
|
42
|
+
renameSync(tmpPath, this.tokenPath);
|
|
43
|
+
this.token = token;
|
|
44
|
+
this.log.debug('Token persisted to disk');
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
this.log.error('Failed to save token:', e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
isTokenValid() {
|
|
51
|
+
return this.token !== null && Date.now() < this.token.validUntil;
|
|
52
|
+
}
|
|
53
|
+
getAccessToken() {
|
|
54
|
+
return this.token?.accessToken ?? null;
|
|
55
|
+
}
|
|
56
|
+
getRefreshToken() {
|
|
57
|
+
return this.token?.refreshToken ?? null;
|
|
58
|
+
}
|
|
59
|
+
getDeviceId() {
|
|
60
|
+
if (this.token?.deviceId) {
|
|
61
|
+
return this.token.deviceId;
|
|
62
|
+
}
|
|
63
|
+
// Cache in memory so all requests in a session use the same device ID
|
|
64
|
+
// (critical for OTP flow where multiple requests must share identity)
|
|
65
|
+
if (this.cachedDeviceId) {
|
|
66
|
+
return this.cachedDeviceId;
|
|
67
|
+
}
|
|
68
|
+
this.cachedDeviceId = generateDeviceId();
|
|
69
|
+
this.log.debug('Generated new device ID:', this.cachedDeviceId);
|
|
70
|
+
return this.cachedDeviceId;
|
|
71
|
+
}
|
|
72
|
+
getVehicleKey() {
|
|
73
|
+
return this.token?.vehicleKey ?? null;
|
|
74
|
+
}
|
|
75
|
+
reloadToken() {
|
|
76
|
+
this.token = this.loadToken();
|
|
77
|
+
}
|
|
78
|
+
updateToken(accessToken, refreshToken, deviceId) {
|
|
79
|
+
const token = {
|
|
80
|
+
accessToken,
|
|
81
|
+
refreshToken,
|
|
82
|
+
deviceId: deviceId ?? this.getDeviceId(),
|
|
83
|
+
vehicleKey: this.token?.vehicleKey,
|
|
84
|
+
validUntil: Date.now() + SESSION_LIFETIME_MS,
|
|
85
|
+
};
|
|
86
|
+
this.saveToken(token);
|
|
87
|
+
}
|
|
88
|
+
setVehicleKey(vehicleKey) {
|
|
89
|
+
if (this.token) {
|
|
90
|
+
this.token.vehicleKey = vehicleKey;
|
|
91
|
+
this.saveToken(this.token);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
clearToken() {
|
|
95
|
+
this.token = null;
|
|
96
|
+
try {
|
|
97
|
+
if (existsSync(this.tokenPath)) {
|
|
98
|
+
unlinkSync(this.tokenPath);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
this.log.warn('Failed to clear token file:', e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=auth.js.map
|