@jetstart/shared 1.1.1

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.
Files changed (67) hide show
  1. package/README.md +108 -0
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +23 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/protocols/events.d.ts +114 -0
  7. package/dist/protocols/events.d.ts.map +1 -0
  8. package/dist/protocols/events.js +7 -0
  9. package/dist/protocols/events.js.map +1 -0
  10. package/dist/protocols/index.d.ts +3 -0
  11. package/dist/protocols/index.d.ts.map +1 -0
  12. package/dist/protocols/index.js +20 -0
  13. package/dist/protocols/index.js.map +1 -0
  14. package/dist/protocols/websocket.d.ts +110 -0
  15. package/dist/protocols/websocket.d.ts.map +1 -0
  16. package/dist/protocols/websocket.js +28 -0
  17. package/dist/protocols/websocket.js.map +1 -0
  18. package/dist/types/build.d.ts +76 -0
  19. package/dist/types/build.d.ts.map +1 -0
  20. package/dist/types/build.js +30 -0
  21. package/dist/types/build.js.map +1 -0
  22. package/dist/types/device.d.ts +45 -0
  23. package/dist/types/device.d.ts.map +1 -0
  24. package/dist/types/device.js +27 -0
  25. package/dist/types/device.js.map +1 -0
  26. package/dist/types/index.d.ts +5 -0
  27. package/dist/types/index.d.ts.map +1 -0
  28. package/dist/types/index.js +22 -0
  29. package/dist/types/index.js.map +1 -0
  30. package/dist/types/log.d.ts +47 -0
  31. package/dist/types/log.d.ts.map +1 -0
  32. package/dist/types/log.js +26 -0
  33. package/dist/types/log.js.map +1 -0
  34. package/dist/types/session.d.ts +51 -0
  35. package/dist/types/session.d.ts.map +1 -0
  36. package/dist/types/session.js +22 -0
  37. package/dist/types/session.js.map +1 -0
  38. package/dist/utils/constants.d.ts +105 -0
  39. package/dist/utils/constants.d.ts.map +1 -0
  40. package/dist/utils/constants.js +123 -0
  41. package/dist/utils/constants.js.map +1 -0
  42. package/dist/utils/index.d.ts +3 -0
  43. package/dist/utils/index.d.ts.map +1 -0
  44. package/dist/utils/index.js +20 -0
  45. package/dist/utils/index.js.map +1 -0
  46. package/dist/utils/validation.d.ts +50 -0
  47. package/dist/utils/validation.d.ts.map +1 -0
  48. package/dist/utils/validation.js +111 -0
  49. package/dist/utils/validation.js.map +1 -0
  50. package/package.json +63 -0
  51. package/src/index.ts +8 -0
  52. package/src/protocols/events.ts +50 -0
  53. package/src/protocols/index.ts +3 -0
  54. package/src/protocols/websocket.ts +149 -0
  55. package/src/types/build.ts +85 -0
  56. package/src/types/device.ts +50 -0
  57. package/src/types/index.ts +5 -0
  58. package/src/types/log.ts +51 -0
  59. package/src/types/session.ts +57 -0
  60. package/src/utils/constants.ts +135 -0
  61. package/src/utils/index.ts +3 -0
  62. package/src/utils/validation.ts +115 -0
  63. package/tests/protocols.test.ts +121 -0
  64. package/tests/setup.ts +16 -0
  65. package/tests/types.test.ts +103 -0
  66. package/tests/validation.test.ts +226 -0
  67. package/tsconfig.json +22 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Validation Utilities
3
+ * Common validation functions used across packages
4
+ */
5
+
6
+ import { QRCodeData, SessionToken, DeviceInfo } from '../types';
7
+
8
+ /**
9
+ * Validate session ID format
10
+ */
11
+ export function isValidSessionId(sessionId: string): boolean {
12
+ return /^[a-zA-Z0-9-_]{8,64}$/.test(sessionId);
13
+ }
14
+
15
+ /**
16
+ * Validate token format
17
+ */
18
+ export function isValidToken(token: string): boolean {
19
+ return /^[a-zA-Z0-9-_]{16,128}$/.test(token);
20
+ }
21
+
22
+ /**
23
+ * Validate project name
24
+ */
25
+ export function isValidProjectName(name: string): boolean {
26
+ return /^[a-zA-Z][a-zA-Z0-9-_]{0,63}$/.test(name);
27
+ }
28
+
29
+ /**
30
+ * Validate package name (Android)
31
+ */
32
+ export function isValidPackageName(packageName: string): boolean {
33
+ return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(packageName);
34
+ }
35
+
36
+ /**
37
+ * Validate port number
38
+ */
39
+ export function isValidPort(port: number): boolean {
40
+ return Number.isInteger(port) && port >= 1024 && port <= 65535;
41
+ }
42
+
43
+ /**
44
+ * Validate URL format
45
+ */
46
+ export function isValidUrl(url: string): boolean {
47
+ try {
48
+ new URL(url);
49
+ return true;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Validate QR code data structure
57
+ */
58
+ export function isValidQRCodeData(data: any): data is QRCodeData {
59
+ return (
60
+ data !== null &&
61
+ typeof data === 'object' &&
62
+ isValidSessionId(data.sessionId) &&
63
+ isValidUrl(data.serverUrl) &&
64
+ isValidUrl(data.wsUrl) &&
65
+ isValidToken(data.token) &&
66
+ typeof data.projectName === 'string' &&
67
+ typeof data.version === 'string'
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Validate session token structure
73
+ */
74
+ export function isValidSessionToken(token: any): token is SessionToken {
75
+ return (
76
+ token !== null &&
77
+ typeof token === 'object' &&
78
+ isValidSessionId(token.sessionId) &&
79
+ isValidToken(token.token) &&
80
+ typeof token.expiresAt === 'number' &&
81
+ isValidUrl(token.serverUrl) &&
82
+ isValidUrl(token.wsUrl)
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Validate device info structure
88
+ */
89
+ export function isValidDeviceInfo(info: any): info is DeviceInfo {
90
+ return (
91
+ info !== null &&
92
+ typeof info === 'object' &&
93
+ typeof info.id === 'string' &&
94
+ typeof info.name === 'string' &&
95
+ typeof info.model === 'string' &&
96
+ typeof info.manufacturer === 'string' &&
97
+ typeof info.platform === 'string' &&
98
+ typeof info.osVersion === 'string' &&
99
+ typeof info.apiLevel === 'number'
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Sanitize user input
105
+ */
106
+ export function sanitizeInput(input: string): string {
107
+ return input.trim().replace(/[<>]/g, '');
108
+ }
109
+
110
+ /**
111
+ * Validate version string (semver)
112
+ */
113
+ export function isValidVersion(version: string): boolean {
114
+ return /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.test(version);
115
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Tests for protocol definitions
3
+ */
4
+
5
+ import {
6
+ WSState,
7
+ WSErrorCode,
8
+ ClientConnectMessage,
9
+ CoreConnectedMessage,
10
+ CoreBuildCompleteMessage,
11
+ } from '../src/protocols/websocket';
12
+ import { Platform, Architecture } from '../src/types';
13
+
14
+ describe('WebSocket Protocol', () => {
15
+ describe('WSState enum', () => {
16
+ it('should have correct state values', () => {
17
+ expect(WSState.CONNECTING).toBe('connecting');
18
+ expect(WSState.CONNECTED).toBe('connected');
19
+ expect(WSState.DISCONNECTING).toBe('disconnecting');
20
+ expect(WSState.DISCONNECTED).toBe('disconnected');
21
+ expect(WSState.ERROR).toBe('error');
22
+ });
23
+ });
24
+
25
+ describe('WSErrorCode enum', () => {
26
+ it('should have correct error codes', () => {
27
+ expect(WSErrorCode.CONNECTION_FAILED).toBe('connection_failed');
28
+ expect(WSErrorCode.AUTHENTICATION_FAILED).toBe('authentication_failed');
29
+ expect(WSErrorCode.TIMEOUT).toBe('timeout');
30
+ expect(WSErrorCode.INVALID_MESSAGE).toBe('invalid_message');
31
+ expect(WSErrorCode.SESSION_EXPIRED).toBe('session_expired');
32
+ expect(WSErrorCode.UNKNOWN).toBe('unknown');
33
+ });
34
+ });
35
+
36
+ describe('Message structures', () => {
37
+ it('should create valid ClientConnectMessage', () => {
38
+ const message: ClientConnectMessage = {
39
+ type: 'client:connect',
40
+ timestamp: Date.now(),
41
+ sessionId: 'session12345',
42
+ token: 'token1234567890abc',
43
+ deviceInfo: {
44
+ id: 'device123',
45
+ name: 'Pixel 6',
46
+ model: 'Pixel 6',
47
+ manufacturer: 'Google',
48
+ platform: Platform.ANDROID,
49
+ osVersion: '14',
50
+ apiLevel: 34,
51
+ screenResolution: { width: 1080, height: 2400 },
52
+ density: 3.0,
53
+ isEmulator: false,
54
+ architecture: Architecture.ARM64_V8A,
55
+ locale: 'en-US',
56
+ timezone: 'America/New_York',
57
+ },
58
+ };
59
+
60
+ expect(message.type).toBe('client:connect');
61
+ expect(message.sessionId).toBe('session12345');
62
+ expect(message.deviceInfo.platform).toBe(Platform.ANDROID);
63
+ });
64
+
65
+ it('should create valid CoreConnectedMessage', () => {
66
+ const message: CoreConnectedMessage = {
67
+ type: 'core:connected',
68
+ timestamp: Date.now(),
69
+ sessionId: 'session12345',
70
+ projectName: 'MyProject',
71
+ };
72
+
73
+ expect(message.type).toBe('core:connected');
74
+ expect(message.sessionId).toBe('session12345');
75
+ expect(message.projectName).toBe('MyProject');
76
+ });
77
+
78
+ it('should create valid CoreBuildCompleteMessage', () => {
79
+ const message: CoreBuildCompleteMessage = {
80
+ type: 'core:build-complete',
81
+ timestamp: Date.now(),
82
+ sessionId: 'session12345',
83
+ apkInfo: {
84
+ path: '/path/to/app.apk',
85
+ size: 5242880,
86
+ hash: 'abc123',
87
+ versionCode: 1,
88
+ versionName: '1.0.0',
89
+ minSdkVersion: 24,
90
+ targetSdkVersion: 34,
91
+ applicationId: 'com.example.app',
92
+ },
93
+ downloadUrl: 'http://localhost:8765/download/app.apk',
94
+ };
95
+
96
+ expect(message.type).toBe('core:build-complete');
97
+ expect(message.apkInfo.size).toBe(5242880);
98
+ expect(message.downloadUrl).toContain('/download/');
99
+ });
100
+ });
101
+
102
+ describe('Message type guards', () => {
103
+ it('should identify message types by type field', () => {
104
+ const connectMessage = {
105
+ type: 'client:connect',
106
+ timestamp: Date.now(),
107
+ };
108
+
109
+ expect(connectMessage.type).toBe('client:connect');
110
+ expect(connectMessage.type.startsWith('client:')).toBe(true);
111
+
112
+ const coreMessage = {
113
+ type: 'core:build-start',
114
+ timestamp: Date.now(),
115
+ };
116
+
117
+ expect(coreMessage.type).toBe('core:build-start');
118
+ expect(coreMessage.type.startsWith('core:')).toBe(true);
119
+ });
120
+ });
121
+ });
package/tests/setup.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Test setup and global configuration
3
+ */
4
+
5
+ // Mock console methods to reduce noise in tests
6
+ global.console = {
7
+ ...console,
8
+ log: jest.fn(),
9
+ debug: jest.fn(),
10
+ info: jest.fn(),
11
+ warn: jest.fn(),
12
+ error: jest.fn(),
13
+ };
14
+
15
+ // Set test timeout
16
+ jest.setTimeout(10000);
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Tests for type definitions and enums
3
+ */
4
+
5
+ import {
6
+ SessionStatus,
7
+ ConnectionType,
8
+ Platform,
9
+ Architecture,
10
+ NetworkType,
11
+ BuildType,
12
+ ErrorSeverity,
13
+ BuildPhase,
14
+ LogLevel,
15
+ LogSource,
16
+ } from '../src/types';
17
+
18
+ describe('Type Definitions', () => {
19
+ describe('Enums', () => {
20
+ it('should have correct SessionStatus values', () => {
21
+ expect(SessionStatus.PENDING).toBe('pending');
22
+ expect(SessionStatus.CONNECTED).toBe('connected');
23
+ expect(SessionStatus.BUILDING).toBe('building');
24
+ expect(SessionStatus.READY).toBe('ready');
25
+ expect(SessionStatus.ERROR).toBe('error');
26
+ expect(SessionStatus.DISCONNECTED).toBe('disconnected');
27
+ });
28
+
29
+ it('should have correct ConnectionType values', () => {
30
+ expect(ConnectionType.MINIMAL).toBe('minimal');
31
+ expect(ConnectionType.FASTER).toBe('faster');
32
+ });
33
+
34
+ it('should have correct Platform values', () => {
35
+ expect(Platform.ANDROID).toBe('android');
36
+ expect(Platform.WEB).toBe('web');
37
+ });
38
+
39
+ it('should have correct Architecture values', () => {
40
+ expect(Architecture.ARM64_V8A).toBe('arm64-v8a');
41
+ expect(Architecture.ARMEABI_V7A).toBe('armeabi-v7a');
42
+ expect(Architecture.X86).toBe('x86');
43
+ expect(Architecture.X86_64).toBe('x86_64');
44
+ });
45
+
46
+ it('should have correct NetworkType values', () => {
47
+ expect(NetworkType.WIFI).toBe('wifi');
48
+ expect(NetworkType.CELLULAR).toBe('cellular');
49
+ expect(NetworkType.ETHERNET).toBe('ethernet');
50
+ expect(NetworkType.UNKNOWN).toBe('unknown');
51
+ });
52
+
53
+ it('should have correct BuildType values', () => {
54
+ expect(BuildType.DEBUG).toBe('debug');
55
+ expect(BuildType.RELEASE).toBe('release');
56
+ });
57
+
58
+ it('should have correct ErrorSeverity values', () => {
59
+ expect(ErrorSeverity.ERROR).toBe('error');
60
+ expect(ErrorSeverity.WARNING).toBe('warning');
61
+ expect(ErrorSeverity.INFO).toBe('info');
62
+ });
63
+
64
+ it('should have correct BuildPhase values', () => {
65
+ expect(BuildPhase.IDLE).toBe('idle');
66
+ expect(BuildPhase.INITIALIZING).toBe('initializing');
67
+ expect(BuildPhase.COMPILING).toBe('compiling');
68
+ expect(BuildPhase.PACKAGING).toBe('packaging');
69
+ expect(BuildPhase.SIGNING).toBe('signing');
70
+ expect(BuildPhase.OPTIMIZING).toBe('optimizing');
71
+ expect(BuildPhase.COMPLETE).toBe('complete');
72
+ expect(BuildPhase.FAILED).toBe('failed');
73
+ });
74
+
75
+ it('should have correct LogLevel values', () => {
76
+ expect(LogLevel.VERBOSE).toBe('verbose');
77
+ expect(LogLevel.DEBUG).toBe('debug');
78
+ expect(LogLevel.INFO).toBe('info');
79
+ expect(LogLevel.WARN).toBe('warn');
80
+ expect(LogLevel.ERROR).toBe('error');
81
+ expect(LogLevel.FATAL).toBe('fatal');
82
+ });
83
+
84
+ it('should have correct LogSource values', () => {
85
+ expect(LogSource.CLI).toBe('cli');
86
+ expect(LogSource.CORE).toBe('core');
87
+ expect(LogSource.CLIENT).toBe('client');
88
+ expect(LogSource.BUILD).toBe('build');
89
+ expect(LogSource.NETWORK).toBe('network');
90
+ expect(LogSource.SYSTEM).toBe('system');
91
+ });
92
+ });
93
+
94
+ describe('Type Guards', () => {
95
+ it('should correctly identify enum values', () => {
96
+ const status: SessionStatus = SessionStatus.CONNECTED;
97
+ expect(Object.values(SessionStatus)).toContain(status);
98
+
99
+ const invalidStatus = 'invalid' as any;
100
+ expect(Object.values(SessionStatus)).not.toContain(invalidStatus);
101
+ });
102
+ });
103
+ });
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Tests for validation utilities
3
+ */
4
+
5
+ import {
6
+ isValidSessionId,
7
+ isValidToken,
8
+ isValidProjectName,
9
+ isValidPackageName,
10
+ isValidPort,
11
+ isValidUrl,
12
+ isValidQRCodeData,
13
+ isValidSessionToken,
14
+ isValidDeviceInfo,
15
+ sanitizeInput,
16
+ isValidVersion,
17
+ } from '../src/utils/validation';
18
+ import { Platform, Architecture } from '../src/types';
19
+
20
+ describe('Validation Utilities', () => {
21
+ describe('isValidSessionId', () => {
22
+ it('should accept valid session IDs', () => {
23
+ expect(isValidSessionId('abc12345')).toBe(true);
24
+ expect(isValidSessionId('session-123-abc')).toBe(true);
25
+ expect(isValidSessionId('ABC_123_xyz')).toBe(true);
26
+ expect(isValidSessionId('a'.repeat(64))).toBe(true);
27
+ });
28
+
29
+ it('should reject invalid session IDs', () => {
30
+ expect(isValidSessionId('short')).toBe(false); // Too short
31
+ expect(isValidSessionId('a'.repeat(65))).toBe(false); // Too long
32
+ expect(isValidSessionId('invalid@session')).toBe(false); // Invalid char
33
+ expect(isValidSessionId('spaces not allowed')).toBe(false);
34
+ expect(isValidSessionId('')).toBe(false); // Empty
35
+ });
36
+ });
37
+
38
+ describe('isValidToken', () => {
39
+ it('should accept valid tokens', () => {
40
+ expect(isValidToken('a'.repeat(16))).toBe(true);
41
+ expect(isValidToken('token-123-abc-xyz')).toBe(true);
42
+ expect(isValidToken('a'.repeat(128))).toBe(true);
43
+ });
44
+
45
+ it('should reject invalid tokens', () => {
46
+ expect(isValidToken('short')).toBe(false); // Too short
47
+ expect(isValidToken('a'.repeat(129))).toBe(false); // Too long
48
+ expect(isValidToken('invalid@token')).toBe(false); // Invalid char
49
+ expect(isValidToken('')).toBe(false); // Empty
50
+ });
51
+ });
52
+
53
+ describe('isValidProjectName', () => {
54
+ it('should accept valid project names', () => {
55
+ expect(isValidProjectName('myApp')).toBe(true);
56
+ expect(isValidProjectName('MyProject123')).toBe(true);
57
+ expect(isValidProjectName('test-app')).toBe(true);
58
+ expect(isValidProjectName('app_name')).toBe(true);
59
+ });
60
+
61
+ it('should reject invalid project names', () => {
62
+ expect(isValidProjectName('123app')).toBe(false); // Starts with number
63
+ expect(isValidProjectName('-myapp')).toBe(false); // Starts with hyphen
64
+ expect(isValidProjectName('my app')).toBe(false); // Contains space
65
+ expect(isValidProjectName('app@name')).toBe(false); // Invalid char
66
+ expect(isValidProjectName('')).toBe(false); // Empty
67
+ expect(isValidProjectName('a'.repeat(65))).toBe(false); // Too long
68
+ });
69
+ });
70
+
71
+ describe('isValidPackageName', () => {
72
+ it('should accept valid package names', () => {
73
+ expect(isValidPackageName('com.example.app')).toBe(true);
74
+ expect(isValidPackageName('com.mycompany.myapp')).toBe(true);
75
+ expect(isValidPackageName('org.jetstart.client')).toBe(true);
76
+ });
77
+
78
+ it('should reject invalid package names', () => {
79
+ expect(isValidPackageName('com.Example.app')).toBe(false); // Uppercase
80
+ expect(isValidPackageName('singlename')).toBe(false); // No dots
81
+ expect(isValidPackageName('com..app')).toBe(false); // Double dot
82
+ expect(isValidPackageName('123.example.app')).toBe(false); // Starts with number
83
+ expect(isValidPackageName('')).toBe(false); // Empty
84
+ });
85
+ });
86
+
87
+ describe('isValidPort', () => {
88
+ it('should accept valid ports', () => {
89
+ expect(isValidPort(1024)).toBe(true);
90
+ expect(isValidPort(8080)).toBe(true);
91
+ expect(isValidPort(65535)).toBe(true);
92
+ });
93
+
94
+ it('should reject invalid ports', () => {
95
+ expect(isValidPort(0)).toBe(false);
96
+ expect(isValidPort(1023)).toBe(false); // Too low
97
+ expect(isValidPort(65536)).toBe(false); // Too high
98
+ expect(isValidPort(8080.5)).toBe(false); // Not integer
99
+ expect(isValidPort(-1)).toBe(false); // Negative
100
+ });
101
+ });
102
+
103
+ describe('isValidUrl', () => {
104
+ it('should accept valid URLs', () => {
105
+ expect(isValidUrl('http://localhost:8080')).toBe(true);
106
+ expect(isValidUrl('https://example.com')).toBe(true);
107
+ expect(isValidUrl('ws://192.168.1.1:8765')).toBe(true);
108
+ expect(isValidUrl('wss://jetstart.dev/ws')).toBe(true);
109
+ });
110
+
111
+ it('should reject invalid URLs', () => {
112
+ expect(isValidUrl('not-a-url')).toBe(false);
113
+ expect(isValidUrl('ftp://invalid')).toBe(true); // Actually valid URL format
114
+ expect(isValidUrl('')).toBe(false);
115
+ expect(isValidUrl('http://')).toBe(false);
116
+ });
117
+ });
118
+
119
+ describe('isValidQRCodeData', () => {
120
+ const validQRData = {
121
+ sessionId: 'session12345',
122
+ serverUrl: 'http://localhost:8765',
123
+ wsUrl: 'ws://localhost:8766',
124
+ token: 'token1234567890abc',
125
+ projectName: 'MyProject',
126
+ version: '0.1.0',
127
+ };
128
+
129
+ it('should accept valid QR code data', () => {
130
+ expect(isValidQRCodeData(validQRData)).toBe(true);
131
+ });
132
+
133
+ it('should reject invalid QR code data', () => {
134
+ expect(isValidQRCodeData({ ...validQRData, sessionId: 'bad' })).toBe(false);
135
+ expect(isValidQRCodeData({ ...validQRData, serverUrl: 'not-url' })).toBe(false);
136
+ expect(isValidQRCodeData({ ...validQRData, token: 'short' })).toBe(false);
137
+ expect(isValidQRCodeData(null)).toBe(false);
138
+ expect(isValidQRCodeData({})).toBe(false);
139
+ });
140
+ });
141
+
142
+ describe('isValidSessionToken', () => {
143
+ const validToken = {
144
+ sessionId: 'session12345',
145
+ token: 'token1234567890abc',
146
+ expiresAt: Date.now() + 3600000,
147
+ serverUrl: 'http://localhost:8765',
148
+ wsUrl: 'ws://localhost:8766',
149
+ };
150
+
151
+ it('should accept valid session tokens', () => {
152
+ expect(isValidSessionToken(validToken)).toBe(true);
153
+ });
154
+
155
+ it('should reject invalid session tokens', () => {
156
+ expect(isValidSessionToken({ ...validToken, sessionId: 'bad' })).toBe(false);
157
+ expect(isValidSessionToken({ ...validToken, token: 'short' })).toBe(false);
158
+ expect(isValidSessionToken({ ...validToken, expiresAt: 'not-number' })).toBe(false);
159
+ expect(isValidSessionToken(null)).toBe(false);
160
+ });
161
+ });
162
+
163
+ describe('isValidDeviceInfo', () => {
164
+ const validDevice = {
165
+ id: 'device123',
166
+ name: 'Pixel 6',
167
+ model: 'Pixel 6',
168
+ manufacturer: 'Google',
169
+ platform: Platform.ANDROID,
170
+ osVersion: '14',
171
+ apiLevel: 34,
172
+ screenResolution: { width: 1080, height: 2400 },
173
+ density: 3.0,
174
+ isEmulator: false,
175
+ architecture: Architecture.ARM64_V8A,
176
+ locale: 'en-US',
177
+ timezone: 'America/New_York',
178
+ };
179
+
180
+ it('should accept valid device info', () => {
181
+ expect(isValidDeviceInfo(validDevice)).toBe(true);
182
+ });
183
+
184
+ it('should reject invalid device info', () => {
185
+ expect(isValidDeviceInfo({ ...validDevice, id: 123 })).toBe(false);
186
+ expect(isValidDeviceInfo({ ...validDevice, apiLevel: '34' })).toBe(false);
187
+ expect(isValidDeviceInfo(null)).toBe(false);
188
+ expect(isValidDeviceInfo({})).toBe(false);
189
+ });
190
+ });
191
+
192
+ describe('sanitizeInput', () => {
193
+ it('should trim whitespace', () => {
194
+ expect(sanitizeInput(' hello ')).toBe('hello');
195
+ expect(sanitizeInput('\nhello\n')).toBe('hello');
196
+ });
197
+
198
+ it('should remove dangerous characters', () => {
199
+ expect(sanitizeInput('<script>alert("xss")</script>')).toBe('scriptalert("xss")/script');
200
+ expect(sanitizeInput('hello<>world')).toBe('helloworld');
201
+ });
202
+
203
+ it('should handle empty strings', () => {
204
+ expect(sanitizeInput('')).toBe('');
205
+ expect(sanitizeInput(' ')).toBe('');
206
+ });
207
+ });
208
+
209
+ describe('isValidVersion', () => {
210
+ it('should accept valid semantic versions', () => {
211
+ expect(isValidVersion('0.1.0')).toBe(true);
212
+ expect(isValidVersion('1.0.0')).toBe(true);
213
+ expect(isValidVersion('2.3.4')).toBe(true);
214
+ expect(isValidVersion('1.0.0-alpha')).toBe(true);
215
+ expect(isValidVersion('1.0.0-beta.1')).toBe(true);
216
+ });
217
+
218
+ it('should reject invalid versions', () => {
219
+ expect(isValidVersion('1.0')).toBe(false);
220
+ expect(isValidVersion('v1.0.0')).toBe(false);
221
+ expect(isValidVersion('1.0.0.0')).toBe(false);
222
+ expect(isValidVersion('abc')).toBe(false);
223
+ expect(isValidVersion('')).toBe(false);
224
+ });
225
+ });
226
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "node",
7
+ "esModuleInterop": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "composite": true,
17
+ "outDir": "./dist",
18
+ "rootDir": "./src"
19
+ },
20
+ "include": ["src/**/*"],
21
+ "exclude": ["node_modules", "dist", "tests"]
22
+ }