hsync 0.30.2 → 0.32.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/package.json CHANGED
@@ -1,13 +1,49 @@
1
1
  {
2
2
  "name": "hsync",
3
- "version": "0.30.2",
3
+ "version": "0.32.0",
4
+ "type": "module",
4
5
  "description": "client for hsync-server",
5
- "main": "hsync.js",
6
+ "main": "./hsync.js",
7
+ "exports": {
8
+ ".": {
9
+ "browser": {
10
+ "import": "./hsync-web.js"
11
+ },
12
+ "node": {
13
+ "import": "./hsync.js"
14
+ },
15
+ "default": "./hsync.js"
16
+ },
17
+ "./web": {
18
+ "import": "./hsync-web.js"
19
+ },
20
+ "./node": {
21
+ "import": "./hsync.js"
22
+ },
23
+ "./config": {
24
+ "import": "./config.js"
25
+ },
26
+ "./connection": {
27
+ "import": "./connection.js"
28
+ }
29
+ },
6
30
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1",
31
+ "test": "vitest",
32
+ "test:run": "vitest run",
33
+ "test:coverage": "vitest run --coverage",
8
34
  "dev": "nodemon --inspect index.js",
9
35
  "start": "node index.js",
10
- "build": "webpack && BUILD_MODE=production webpack"
36
+ "build": "webpack && BUILD_MODE=production webpack",
37
+ "lint": "eslint .",
38
+ "lint:fix": "eslint . --fix",
39
+ "format": "prettier --write \"**/*.js\"",
40
+ "prepare": "husky"
41
+ },
42
+ "lint-staged": {
43
+ "*.js": [
44
+ "eslint --fix",
45
+ "prettier --write"
46
+ ]
11
47
  },
12
48
  "repository": {
13
49
  "type": "git",
@@ -25,7 +61,7 @@
25
61
  "tunnel"
26
62
  ],
27
63
  "browser": {
28
- "./hsync": "./hsync-web"
64
+ "./hsync.js": "./hsync-web.js"
29
65
  },
30
66
  "bin": {
31
67
  "hsync": "./cli.js",
@@ -34,23 +70,30 @@
34
70
  "author": "Luis Montes",
35
71
  "license": "ISC",
36
72
  "dependencies": {
37
- "b64id": "^1.0.0",
73
+ "b64id": "^1.1.1",
38
74
  "buffer": "^6.0.3",
39
- "commander": "^10.0.0",
40
- "debug": "^4.3.1",
41
- "isomorphic-fetch": "^3.0.0",
42
- "mqtt": "^4.2.6",
43
- "mqtt-packet-web": "^8.2.0",
44
- "net-web": "^0.2.0",
45
- "precompiled-mqtt": "^4.3.14-beta",
46
- "rawr": "^0.15.1"
75
+ "commander": "^14.0.2",
76
+ "debug": "^4.4.3",
77
+ "mqtt": "^5.14.1",
78
+ "mqtt-packet": "^9.0.2",
79
+ "net-web": "^0.3.0",
80
+ "rawr": "^0.19.0"
47
81
  },
48
82
  "optionalDependencies": {
49
- "node-datachannel": "^0.23.0"
83
+ "node-datachannel": "^0.32.0"
50
84
  },
51
85
  "devDependencies": {
52
- "nodemon": "^3.0.1",
53
- "webpack": "^5.88.2",
54
- "webpack-cli": "^4.10.0"
86
+ "@eslint/js": "^9.39.2",
87
+ "@vitest/coverage-v8": "^4.0.17",
88
+ "eslint": "^9.39.2",
89
+ "eslint-config-prettier": "^10.1.8",
90
+ "globals": "^17.0.0",
91
+ "husky": "^9.1.7",
92
+ "lint-staged": "^16.2.7",
93
+ "nodemon": "^3.1.11",
94
+ "prettier": "^3.8.0",
95
+ "vitest": "^4.0.17",
96
+ "webpack": "^5.104.1",
97
+ "webpack-cli": "^6.0.1"
55
98
  }
56
99
  }
package/shell.js CHANGED
@@ -1,7 +1,7 @@
1
- const net = require('net');
1
+ import net from 'net';
2
2
 
3
- function run(port = 2323) {
4
- process.stdin.setRawMode( true );
3
+ export default function run(port = 2323) {
4
+ process.stdin.setRawMode(true);
5
5
  console.log('connecting to localhost:', port, ' ...');
6
6
 
7
7
  const client = net.createConnection({ port }, () => {
@@ -12,7 +12,7 @@ function run(port = 2323) {
12
12
  process.stdin.on('data', (data) => {
13
13
  client.write(data);
14
14
  // ctrl-c ( end of text )
15
- if ( String(data) === '\u0003' ) {
15
+ if (String(data) === '\u0003') {
16
16
  process.exit();
17
17
  }
18
18
  });
@@ -25,9 +25,5 @@ function run(port = 2323) {
25
25
  client.on('error', (err) => {
26
26
  console.error(err);
27
27
  });
28
-
29
28
  });
30
29
  }
31
-
32
- module.exports = run;
33
-
@@ -0,0 +1,25 @@
1
+ import { vi } from 'vitest';
2
+ import { EventEmitter } from 'events';
3
+
4
+ export function createMockMqttClient() {
5
+ const client = new EventEmitter();
6
+ client.publish = vi.fn();
7
+ client.subscribe = vi.fn();
8
+ client.unsubscribe = vi.fn();
9
+ client.end = vi.fn((force, callback) => {
10
+ if (typeof force === 'function') {
11
+ force();
12
+ } else if (callback) {
13
+ callback();
14
+ }
15
+ });
16
+ return client;
17
+ }
18
+
19
+ export function createMockMqtt() {
20
+ const mockClient = createMockMqttClient();
21
+ return {
22
+ connect: vi.fn(() => mockClient),
23
+ _mockClient: mockClient,
24
+ };
25
+ }
@@ -0,0 +1,34 @@
1
+ import { vi } from 'vitest';
2
+ import { EventEmitter } from 'events';
3
+
4
+ export function createMockSocket() {
5
+ const socket = new EventEmitter();
6
+ socket.write = vi.fn();
7
+ socket.end = vi.fn();
8
+ socket.destroy = vi.fn();
9
+ socket.connect = vi.fn();
10
+ socket.setEncoding = vi.fn();
11
+ socket.setTimeout = vi.fn();
12
+ return socket;
13
+ }
14
+
15
+ export function createMockServer() {
16
+ const server = new EventEmitter();
17
+ server.listen = vi.fn((port, callback) => {
18
+ if (callback) callback();
19
+ return server;
20
+ });
21
+ server.close = vi.fn((callback) => {
22
+ if (callback) callback();
23
+ });
24
+ server.address = vi.fn(() => ({ port: 3000 }));
25
+ return server;
26
+ }
27
+
28
+ export function createMockNet() {
29
+ return {
30
+ createConnection: vi.fn(() => createMockSocket()),
31
+ createServer: vi.fn(() => createMockServer()),
32
+ Socket: vi.fn(() => createMockSocket()),
33
+ };
34
+ }
@@ -0,0 +1,9 @@
1
+ import { vi } from 'vitest';
2
+
3
+ export function createMockRTC() {
4
+ return {
5
+ PeerConnection: vi.fn(),
6
+ offerPeer: vi.fn().mockResolvedValue({ type: 'offer', sdp: 'mock-sdp' }),
7
+ answerPeer: vi.fn().mockResolvedValue({ type: 'answer', sdp: 'mock-answer-sdp' }),
8
+ };
9
+ }
package/test/setup.js ADDED
@@ -0,0 +1,10 @@
1
+ import { vi } from 'vitest';
2
+
3
+ // Mock debug to prevent console output during tests
4
+ vi.mock('debug', () => ({
5
+ default: () => {
6
+ const debug = () => {};
7
+ debug.color = 0;
8
+ return debug;
9
+ },
10
+ }));
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+
3
+ describe('config', () => {
4
+ beforeEach(() => {
5
+ vi.resetModules();
6
+ });
7
+
8
+ it('should export default config with baseConfig properties', async () => {
9
+ const { default: config, baseConfig } = await import('../../config.js');
10
+
11
+ expect(config).toBeDefined();
12
+ expect(config.localHost).toBe('localhost');
13
+ expect(config.port).toBe(3000);
14
+ expect(config.hsyncBase).toBe('_hs');
15
+ expect(config.keepalive).toBe(300);
16
+ expect(config.defaultDynamicHost).toBe('https://demo.hsync.tech');
17
+ expect(baseConfig).toBeDefined();
18
+ });
19
+
20
+ it('should have connections array with at least one connection', async () => {
21
+ const { default: config } = await import('../../config.js');
22
+
23
+ expect(config.connections).toBeDefined();
24
+ expect(Array.isArray(config.connections)).toBe(true);
25
+ expect(config.connections.length).toBeGreaterThanOrEqual(1);
26
+ });
27
+
28
+ it('should include baseConfig as first connection', async () => {
29
+ const { default: config, baseConfig } = await import('../../config.js');
30
+
31
+ const firstConnection = config.connections[0];
32
+ expect(firstConnection.localHost).toBe(baseConfig.localHost);
33
+ expect(firstConnection.port).toBe(baseConfig.port);
34
+ expect(firstConnection.hsyncBase).toBe(baseConfig.hsyncBase);
35
+ });
36
+ });
@@ -0,0 +1,189 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+
3
+ describe('fetch', () => {
4
+ let apiFetch;
5
+ let mockFetch;
6
+
7
+ beforeEach(async () => {
8
+ vi.resetModules();
9
+ mockFetch = vi.fn();
10
+ globalThis.fetch = mockFetch;
11
+ const apiModule = await import('../../lib/fetch.js');
12
+ apiFetch = apiModule.default;
13
+ });
14
+
15
+ afterEach(() => {
16
+ vi.restoreAllMocks();
17
+ });
18
+
19
+ it('should be a function', () => {
20
+ expect(typeof apiFetch).toBe('function');
21
+ });
22
+
23
+ it('should have post, put, and del methods', () => {
24
+ expect(typeof apiFetch.post).toBe('function');
25
+ expect(typeof apiFetch.put).toBe('function');
26
+ expect(typeof apiFetch.del).toBe('function');
27
+ });
28
+
29
+ describe('apiFetch', () => {
30
+ it('should call fetch with correct headers', async () => {
31
+ mockFetch.mockResolvedValueOnce({
32
+ status: 200,
33
+ headers: {
34
+ get: () => 'application/json',
35
+ },
36
+ json: () => Promise.resolve({ data: 'test' }),
37
+ });
38
+
39
+ await apiFetch('/test');
40
+
41
+ expect(mockFetch).toHaveBeenCalledWith(
42
+ '/test',
43
+ expect.objectContaining({
44
+ credentials: 'include',
45
+ headers: expect.objectContaining({
46
+ 'Content-Type': 'application/json',
47
+ }),
48
+ })
49
+ );
50
+ });
51
+
52
+ it('should parse JSON responses', async () => {
53
+ const mockData = { key: 'value' };
54
+ mockFetch.mockResolvedValueOnce({
55
+ status: 200,
56
+ headers: {
57
+ get: () => 'application/json',
58
+ },
59
+ json: () => Promise.resolve(mockData),
60
+ });
61
+
62
+ const result = await apiFetch('/test');
63
+
64
+ expect(result).toEqual(mockData);
65
+ });
66
+
67
+ it('should parse text responses', async () => {
68
+ mockFetch.mockResolvedValueOnce({
69
+ status: 200,
70
+ headers: {
71
+ get: () => 'text/plain',
72
+ },
73
+ text: () => Promise.resolve('plain text'),
74
+ });
75
+
76
+ const result = await apiFetch('/test');
77
+
78
+ expect(result).toBe('plain text');
79
+ });
80
+
81
+ it('should throw error on 4xx/5xx responses', async () => {
82
+ mockFetch.mockResolvedValueOnce({
83
+ status: 404,
84
+ headers: {
85
+ get: () => 'application/json',
86
+ },
87
+ json: () => Promise.resolve({ error: 'Not found' }),
88
+ });
89
+
90
+ await expect(apiFetch('/test')).rejects.toThrow('Bad response from server');
91
+ });
92
+
93
+ it('should stringify object body', async () => {
94
+ mockFetch.mockResolvedValueOnce({
95
+ status: 200,
96
+ headers: {
97
+ get: () => 'application/json',
98
+ },
99
+ json: () => Promise.resolve({}),
100
+ });
101
+
102
+ await apiFetch('/test', { body: { foo: 'bar' } });
103
+
104
+ expect(mockFetch).toHaveBeenCalledWith(
105
+ '/test',
106
+ expect.objectContaining({
107
+ body: '{"foo":"bar"}',
108
+ })
109
+ );
110
+ });
111
+
112
+ it('should append query string', async () => {
113
+ mockFetch.mockResolvedValueOnce({
114
+ status: 200,
115
+ headers: {
116
+ get: () => 'application/json',
117
+ },
118
+ json: () => Promise.resolve({}),
119
+ });
120
+
121
+ await apiFetch('/test', { query: { foo: 'bar', baz: 'qux' } });
122
+
123
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/test?'), expect.any(Object));
124
+ });
125
+ });
126
+
127
+ describe('apiFetch.post', () => {
128
+ it('should call apiFetch with POST method', async () => {
129
+ mockFetch.mockResolvedValueOnce({
130
+ status: 200,
131
+ headers: {
132
+ get: () => 'application/json',
133
+ },
134
+ json: () => Promise.resolve({ success: true }),
135
+ });
136
+
137
+ await apiFetch.post('/test', { data: 'value' });
138
+
139
+ expect(mockFetch).toHaveBeenCalledWith(
140
+ '/test',
141
+ expect.objectContaining({
142
+ method: 'POST',
143
+ })
144
+ );
145
+ });
146
+ });
147
+
148
+ describe('apiFetch.put', () => {
149
+ it('should call apiFetch with PUT method', async () => {
150
+ mockFetch.mockResolvedValueOnce({
151
+ status: 200,
152
+ headers: {
153
+ get: () => 'application/json',
154
+ },
155
+ json: () => Promise.resolve({ success: true }),
156
+ });
157
+
158
+ await apiFetch.put('/test', { data: 'value' });
159
+
160
+ expect(mockFetch).toHaveBeenCalledWith(
161
+ '/test',
162
+ expect.objectContaining({
163
+ method: 'PUT',
164
+ })
165
+ );
166
+ });
167
+ });
168
+
169
+ describe('apiFetch.del', () => {
170
+ it('should call apiFetch with DELETE method', async () => {
171
+ mockFetch.mockResolvedValueOnce({
172
+ status: 200,
173
+ headers: {
174
+ get: () => 'application/json',
175
+ },
176
+ json: () => Promise.resolve({ success: true }),
177
+ });
178
+
179
+ await apiFetch.del('/test');
180
+
181
+ expect(mockFetch).toHaveBeenCalledWith(
182
+ '/test',
183
+ expect.objectContaining({
184
+ method: 'DELETE',
185
+ })
186
+ );
187
+ });
188
+ });
189
+ });