@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.
- package/README.md +108 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/protocols/events.d.ts +114 -0
- package/dist/protocols/events.d.ts.map +1 -0
- package/dist/protocols/events.js +7 -0
- package/dist/protocols/events.js.map +1 -0
- package/dist/protocols/index.d.ts +3 -0
- package/dist/protocols/index.d.ts.map +1 -0
- package/dist/protocols/index.js +20 -0
- package/dist/protocols/index.js.map +1 -0
- package/dist/protocols/websocket.d.ts +110 -0
- package/dist/protocols/websocket.d.ts.map +1 -0
- package/dist/protocols/websocket.js +28 -0
- package/dist/protocols/websocket.js.map +1 -0
- package/dist/types/build.d.ts +76 -0
- package/dist/types/build.d.ts.map +1 -0
- package/dist/types/build.js +30 -0
- package/dist/types/build.js.map +1 -0
- package/dist/types/device.d.ts +45 -0
- package/dist/types/device.d.ts.map +1 -0
- package/dist/types/device.js +27 -0
- package/dist/types/device.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +22 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/log.d.ts +47 -0
- package/dist/types/log.d.ts.map +1 -0
- package/dist/types/log.js +26 -0
- package/dist/types/log.js.map +1 -0
- package/dist/types/session.d.ts +51 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +22 -0
- package/dist/types/session.js.map +1 -0
- package/dist/utils/constants.d.ts +105 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +123 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +20 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/validation.d.ts +50 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +111 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +63 -0
- package/src/index.ts +8 -0
- package/src/protocols/events.ts +50 -0
- package/src/protocols/index.ts +3 -0
- package/src/protocols/websocket.ts +149 -0
- package/src/types/build.ts +85 -0
- package/src/types/device.ts +50 -0
- package/src/types/index.ts +5 -0
- package/src/types/log.ts +51 -0
- package/src/types/session.ts +57 -0
- package/src/utils/constants.ts +135 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/validation.ts +115 -0
- package/tests/protocols.test.ts +121 -0
- package/tests/setup.ts +16 -0
- package/tests/types.test.ts +103 -0
- package/tests/validation.test.ts +226 -0
- 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
|
+
}
|