hsync 0.30.1 → 0.31.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/todo.md ADDED
@@ -0,0 +1,324 @@
1
+ # hsync Client - Auth Feature TODO
2
+
3
+ ## Overview
4
+
5
+ Add HTTP proxy authentication so services without built-in auth (like LM Studio) can be protected when exposed via hsync.
6
+
7
+ ## Design
8
+
9
+ **Simple case:** One `--token` flag enables auth
10
+ ```bash
11
+ hsync -p 1234 --token "my-secret"
12
+ ```
13
+
14
+ **Accepts both:**
15
+ - `Authorization: Bearer my-secret`
16
+ - `Authorization: Basic base64(hsync:my-secret)`
17
+ - URL embedded: `https://hsync:my-secret@xyz.hsync.tech/...`
18
+
19
+ **Optional username override:**
20
+ ```bash
21
+ hsync -p 1234 --token "my-secret" --username "admin"
22
+ # Basic auth expects admin:my-secret
23
+ ```
24
+
25
+ **Programmatic:**
26
+ ```javascript
27
+ const con = await hsync.createConnection({
28
+ port: 1234,
29
+ token: 'my-secret',
30
+ username: 'admin' // optional, defaults to 'hsync'
31
+ });
32
+ ```
33
+
34
+ ## Implementation Tasks
35
+
36
+ ### 1. CLI Changes (cli.js)
37
+
38
+ - [ ] Add `--token <string>` option
39
+ - [ ] Add `--username <string>` option (optional, default: 'hsync')
40
+ - [ ] Pass token/username to connection config
41
+
42
+ ```javascript
43
+ .addOption(new Option('-t, --token <string>', 'auth token for HTTP proxy').env('HSYNC_TOKEN'))
44
+ .addOption(new Option('-u, --username <string>', 'username for Basic auth').env('HSYNC_USERNAME').default('hsync'))
45
+ ```
46
+
47
+ ### 2. Connection Changes (connection.js)
48
+
49
+ - [ ] Accept `token` and `username` in config
50
+ - [ ] After MQTT connect, call `serverPeer.methods.setHttpAuth()` to register auth with server
51
+ - [ ] Store auth config on hsyncClient object
52
+
53
+ ```javascript
54
+ if (config.token) {
55
+ await hsyncClient.serverPeer.methods.setHttpAuth({
56
+ token: config.token,
57
+ username: config.username || 'hsync'
58
+ });
59
+ }
60
+ ```
61
+
62
+ ### 3. Config Changes (config.js)
63
+
64
+ - [ ] Add `token` and `username` to baseConfig from env vars
65
+
66
+ ```javascript
67
+ token: env.HSYNC_TOKEN,
68
+ username: env.HSYNC_USERNAME || 'hsync',
69
+ ```
70
+
71
+ ## Server Dependency
72
+
73
+ This feature requires hsync-server to implement `setHttpAuth` RPC method and auth checking. See hsync-server/todo.md.
74
+
75
+ ## Testing
76
+
77
+ - [ ] Test Bearer auth: `curl -H "Authorization: Bearer my-secret" ...`
78
+ - [ ] Test Basic auth: `curl -u hsync:my-secret ...`
79
+ - [ ] Test URL embedded: `curl https://hsync:my-secret@... `
80
+ - [ ] Test wrong token returns 401
81
+ - [ ] Test no token set = no auth checking (open proxy)
82
+ - [ ] Test custom username with Basic auth
83
+
84
+ ## Documentation
85
+
86
+ - [ ] Update README with auth examples
87
+ - [ ] Add examples for LM Studio use case
88
+
89
+ ---
90
+
91
+ # Sub-subdomain Routing Feature TODO
92
+
93
+ ## Overview
94
+
95
+ Route multiple local services through a single hsync connection. One client, multiple services - no need for separate connections per service.
96
+
97
+ Less overhead: one WebSocket connection instead of many, and one Node process (CLI) instead of multiple instances.
98
+
99
+ Uses a configurable separator (e.g., `_`) to stay within one subdomain level for wildcard cert compatibility.
100
+
101
+ ## Why Underscore?
102
+
103
+ Wildcard certs (`*.myhsyncserver.com`) only cover ONE subdomain level:
104
+ ```
105
+ ✅ myapp.myhsyncserver.com - covered
106
+ ✅ api.myhsyncserver.com - covered
107
+ ❌ api.myapp.myhsyncserver.com - NOT covered (two levels)
108
+ ```
109
+
110
+ Using underscore keeps it to one level:
111
+ ```
112
+ ✅ api_myapp.myhsyncserver.com - covered by *.myhsyncserver.com
113
+ ✅ frontend_myapp.myhsyncserver.com - covered
114
+ ```
115
+
116
+ One wildcard cert, unlimited sub-services per client.
117
+
118
+ ## Design
119
+
120
+ **Example:**
121
+ ```
122
+ api_myapp.myhsyncserver.com → hsync client "myapp" → localhost:3001
123
+ frontend_myapp.myhsyncserver.com → hsync client "myapp" → localhost:3000
124
+ myapp.myhsyncserver.com → hsync client "myapp" → localhost:3000 (default)
125
+ ```
126
+
127
+ **CLI:**
128
+ ```bash
129
+ hsync -p 3000 --subsub api:3001 --subsub frontend:3002
130
+ ```
131
+
132
+ **Programmatic:**
133
+ ```javascript
134
+ const con = await hsync.createConnection({
135
+ port: 3000, // default
136
+ subsubs: {
137
+ 'api': 3001,
138
+ 'frontend': 3002
139
+ }
140
+ });
141
+ ```
142
+
143
+ ## Topic Structure
144
+
145
+ New `websub/` namespace to avoid clashing with existing `web/` close action:
146
+
147
+ ```
148
+ web/{hostname}/{socketId} ← no prefix (existing)
149
+ web/{hostname}/{socketId}/close ← close (existing)
150
+
151
+ websub/{hostname}/{prefix}/{socketId} ← with prefix (new)
152
+ websub/{hostname}/{prefix}/{socketId}/close ← close with prefix (new)
153
+ ```
154
+
155
+ Raw binary payloads throughout - no encoding overhead.
156
+
157
+ ## Implementation Tasks
158
+
159
+ ### 1. CLI Changes (cli.js)
160
+
161
+ - [ ] Add `--subsub <prefix:port>` option (repeatable)
162
+
163
+ ```javascript
164
+ .addOption(new Option('--subsub <mapping>', 'sub-subdomain prefix to port mapping (e.g., api:3001)').argParser(collectSubsubs))
165
+
166
+ function collectSubsubs(value, previous) {
167
+ const [prefix, port] = value.split(':');
168
+ return { ...previous, [prefix]: parseInt(port, 10) };
169
+ }
170
+ ```
171
+
172
+ ### 2. Connection Changes (connection.js)
173
+
174
+ - [ ] Accept `subsubs` in config
175
+ - [ ] Subscribe to `websub/{hostname}/#` if subsubs configured
176
+ - [ ] Handle `websub/` messages in message handler
177
+
178
+ ```javascript
179
+ if (config.subsubs) {
180
+ mqConn.subscribe(`websub/${myHostName}/#`);
181
+ }
182
+
183
+ mqConn.on('message', (topic, message) => {
184
+ const [name, hostName, ...rest] = topic.split('/');
185
+
186
+ if (name === 'websub') {
187
+ const [prefix, socketId, action] = rest;
188
+ const port = config.subsubs[prefix] || config.port;
189
+ webHandler.handleWebRequest(hostName, socketId, action, message, port);
190
+ }
191
+ // ... existing web/ handling
192
+ });
193
+ ```
194
+
195
+ ### 3. Web Handler Changes (lib/web-handler.js)
196
+
197
+ - [ ] Accept optional port parameter
198
+ - [ ] Use passed port instead of default config.port
199
+
200
+ ```javascript
201
+ function handleWebRequest(hostName, socketId, action, message, port = defaultPort) {
202
+ // ... use port for socket.connect()
203
+ }
204
+ ```
205
+
206
+ ### 4. Config Changes (config.js)
207
+
208
+ - [ ] Add subsubs to config structure
209
+
210
+ ## Server Dependency
211
+
212
+ Requires hsync-server to:
213
+ - Have `HSYNC_SUBSUB_SEPARATOR` configured (e.g., `_`) - feature disabled if not set
214
+ - Parse sub-subdomain prefix (e.g., `api_myapp` → prefix: `api`, base: `myapp`)
215
+ - Route to client based on base hostname
216
+ - Publish to `websub/{base}/{prefix}/{socketId}` when prefix present
217
+
218
+ See hsync-server/todo.md.
219
+
220
+ ## Testing
221
+
222
+ - [ ] Test `api_myapp.server.com` forwards to port 3001
223
+ - [ ] Test `frontend_myapp.server.com` forwards to port 3000
224
+ - [ ] Test `myapp.server.com` (no prefix) forwards to default port
225
+ - [ ] Test unknown prefix falls back to default port
226
+ - [ ] Test close action works with prefix
227
+ - [ ] Test existing non-prefixed routing still works
228
+
229
+ ---
230
+
231
+ # Testing & Linting TODO
232
+
233
+ ## Overview
234
+
235
+ Add proper test coverage and linting to ensure code quality and catch regressions.
236
+
237
+ ## Testing
238
+
239
+ ### Setup
240
+
241
+ - [x] Add test framework (vitest or jest) - **DONE**
242
+ - [x] Add test script to package.json - **DONE**
243
+ - [x] Set up test directory structure - **DONE**
244
+
245
+ ```
246
+ hsync/
247
+ test/
248
+ unit/
249
+ config.test.js
250
+ web-handler.test.js
251
+ peers.test.js
252
+ socket-listeners.test.js
253
+ socket-relays.test.js
254
+ integration/
255
+ connection.test.js
256
+ auth.test.js
257
+ subsub.test.js
258
+ ```
259
+
260
+ ### Unit Tests
261
+
262
+ - [x] **config.js** - env var parsing, multi-server config - **DONE (3 tests)**
263
+ - [x] **web-handler.js** - request handling, socket management - **DONE (15 tests)**
264
+ - [x] **peers.js** - RPC peer creation, message handling - **DONE (27 tests)**
265
+ - [x] **socket-listeners.js** - listener creation, data forwarding - **DONE (19 tests)**
266
+ - [x] **socket-relays.js** - relay creation, connection handling - **DONE (19 tests)**
267
+ - [x] **socket-map.js** - packet handling - **DONE (5 tests)**
268
+ - [x] **fetch.js** - fetch wrapper - **DONE (11 tests)**
269
+
270
+ **Total: 99 tests passing**
271
+
272
+ ### Integration Tests
273
+
274
+ - [ ] Connection to hsync-server (mock or real)
275
+ - [ ] Web request forwarding end-to-end
276
+ - [ ] Auth token validation
277
+ - [ ] Sub-subdomain routing
278
+ - [ ] WebRTC peer connection (if possible to test)
279
+
280
+ ### Test Utilities
281
+
282
+ - [x] Mock MQTT connection - **DONE**
283
+ - [x] Mock net.Socket - **DONE**
284
+ - [ ] Test fixtures for HTTP requests/responses
285
+
286
+ ## Linting
287
+
288
+ ### Setup
289
+
290
+ - [x] Add/update ESLint config (eslint.config.js - flat config) - **DONE**
291
+ - [x] Add lint script to package.json - **DONE**
292
+ - [x] Add pre-commit hook (husky + lint-staged) - **DONE**
293
+
294
+ ```json
295
+ {
296
+ "scripts": {
297
+ "lint": "eslint .",
298
+ "lint:fix": "eslint . --fix",
299
+ "format": "prettier --write \"**/*.js\"",
300
+ "test": "vitest",
301
+ "test:run": "vitest run",
302
+ "test:coverage": "vitest run --coverage"
303
+ }
304
+ }
305
+ ```
306
+
307
+ ### ESLint Config
308
+
309
+ - [x] Create `eslint.config.js` with ESLint 9 flat config - **DONE**
310
+
311
+ ### Lint Fixes Needed
312
+
313
+ - [x] Fix undeclared variables - **DONE**
314
+ - [x] Remove unused variables - **DONE**
315
+ - [x] Consistent quote style (Prettier) - **DONE**
316
+ - [x] Consistent semicolons (Prettier) - **DONE**
317
+
318
+ ## CI/CD
319
+
320
+ - [x] Add GitHub Actions workflow for tests - **DONE**
321
+ - [x] Add GitHub Actions workflow for linting - **DONE**
322
+ - [x] Run on PR and push to main - **DONE**
323
+
324
+ See `.github/workflows/ci.yml`
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['test/**/*.test.js'],
8
+ setupFiles: ['./test/setup.js'],
9
+ coverage: {
10
+ provider: 'v8',
11
+ include: ['**/*.js'],
12
+ exclude: ['cli.js', 'shell.js', 'dist/**', 'test/**', 'vitest.config.js', 'eslint.config.js'],
13
+ },
14
+ },
15
+ });
package/webpack.config.js CHANGED
@@ -1,19 +1,35 @@
1
- const path = require('path');
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
2
6
 
3
7
  const mode = process.env.BUILD_MODE || 'development';
4
8
 
5
- module.exports = {
6
- entry: "./hsync-web.js",
9
+ export default {
10
+ entry: './hsync-web.js',
7
11
  output: {
8
12
  path: path.resolve(__dirname, 'dist'),
9
- filename: mode === 'development' ? 'hsync.js' : 'hsync.min.js'
13
+ filename: mode === 'development' ? 'hsync.js' : 'hsync.min.js',
14
+ library: {
15
+ name: 'hsync',
16
+ type: 'umd',
17
+ export: 'default',
18
+ },
19
+ globalObject: 'this',
10
20
  },
11
21
  mode,
22
+ optimization: {
23
+ usedExports: true,
24
+ sideEffects: true,
25
+ },
12
26
  resolve: {
13
- alias: {
14
- }
27
+ alias: {},
15
28
  },
16
29
  node: {
17
30
  global: true,
18
- }
19
- };
31
+ },
32
+ performance: {
33
+ hints: false,
34
+ },
35
+ };