@mytmpvpn/mytmpvpn-common 10.0.2 → 16.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.
@@ -1,5 +1,10 @@
1
1
  import * as vpn from './vpn';
2
- import { VpnConfig } from './vpnConfig';
2
+ import { VpnConfig, VpnConfigLimits } from './vpnConfig';
3
+ export interface UserConfig {
4
+ vpnsQuota: number;
5
+ vpnConfigLimits: VpnConfigLimits;
6
+ giftedPeanuts: number;
7
+ }
3
8
  export declare enum UserVpnVersion {
4
9
  INITIAL = 0,
5
10
  LOCATIONS = 1
@@ -220,7 +220,7 @@ function vpnIdToWgFileName(vpn) {
220
220
  const result = vpn.createdAt.toISOString() // This will create YYYY-MM-DDThh:mm:ss....
221
221
  .replace(/[^\d]/g, '') // Remove meanless characters: YYYYMMDDhhmmss
222
222
  .substring(4, 14); // Use only MMDDhhmmss
223
- return result;
223
+ return `${result}.conf`;
224
224
  }
225
225
  exports.vpnIdToWgFileName = vpnIdToWgFileName;
226
226
  function vpnToClient(vpn) {
@@ -0,0 +1,196 @@
1
+ import { VpnState } from './vpn';
2
+ /**
3
+ * WebSocket Message Protocol Types
4
+ *
5
+ * This module defines the complete message protocol for WebSocket communication
6
+ * between clients and the backend for real-time VPN state notifications.
7
+ */
8
+ /**
9
+ * Base interface for all client-to-server messages
10
+ */
11
+ export interface BaseClientMessage {
12
+ action: string;
13
+ requestId: string;
14
+ }
15
+ /**
16
+ * Subscribe to VPN state change notifications
17
+ * - vpnId: specific VPN ID to subscribe to, or "all" for all user VPNs
18
+ */
19
+ export interface SubscribeMessage extends BaseClientMessage {
20
+ action: 'subscribe';
21
+ vpnId: string | 'all';
22
+ }
23
+ /**
24
+ * Unsubscribe from VPN state change notifications
25
+ */
26
+ export interface UnsubscribeMessage extends BaseClientMessage {
27
+ action: 'unsubscribe';
28
+ vpnId: string;
29
+ }
30
+ /**
31
+ * Ping message to keep connection alive
32
+ */
33
+ export interface PingMessage extends BaseClientMessage {
34
+ action: 'ping';
35
+ }
36
+ /**
37
+ * Union type of all possible client messages
38
+ */
39
+ export type ClientMessage = SubscribeMessage | UnsubscribeMessage | PingMessage;
40
+ /**
41
+ * Base interface for all server-to-client messages
42
+ */
43
+ export interface BaseServerMessage {
44
+ type: string;
45
+ timestamp: number;
46
+ }
47
+ /**
48
+ * VPN state change notification sent to subscribed clients
49
+ */
50
+ export interface StateChangeNotification extends BaseServerMessage {
51
+ type: 'state_change';
52
+ vpnId: string;
53
+ previousState: VpnState;
54
+ newState: VpnState;
55
+ }
56
+ /**
57
+ * Acknowledgment message for successful operations
58
+ */
59
+ export interface AcknowledgmentMessage extends BaseServerMessage {
60
+ type: 'ack';
61
+ requestId: string;
62
+ action: string;
63
+ success: boolean;
64
+ message?: string;
65
+ }
66
+ /**
67
+ * Error message for failed operations
68
+ */
69
+ export interface ErrorMessage extends BaseServerMessage {
70
+ type: 'error';
71
+ code: WebSocketErrorCode;
72
+ message: string;
73
+ requestId?: string;
74
+ }
75
+ /**
76
+ * Pong response to ping message
77
+ */
78
+ export interface PongMessage extends BaseServerMessage {
79
+ type: 'pong';
80
+ requestId: string;
81
+ }
82
+ /**
83
+ * Union type of all possible server messages
84
+ */
85
+ export type ServerMessage = StateChangeNotification | AcknowledgmentMessage | ErrorMessage | PongMessage;
86
+ /**
87
+ * Standard error codes for WebSocket operations
88
+ */
89
+ export declare enum WebSocketErrorCode {
90
+ AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED",
91
+ TOKEN_EXPIRED = "TOKEN_EXPIRED",
92
+ TOKEN_INVALID = "TOKEN_INVALID",
93
+ TOKEN_MISSING = "TOKEN_MISSING",
94
+ PERMISSION_DENIED = "PERMISSION_DENIED",
95
+ VPN_NOT_OWNED = "VPN_NOT_OWNED",
96
+ INVALID_MESSAGE_FORMAT = "INVALID_MESSAGE_FORMAT",
97
+ INVALID_VPN_ID = "INVALID_VPN_ID",
98
+ MISSING_REQUIRED_FIELD = "MISSING_REQUIRED_FIELD",
99
+ UNKNOWN_ACTION = "UNKNOWN_ACTION",
100
+ VPN_NOT_FOUND = "VPN_NOT_FOUND",
101
+ SUBSCRIPTION_NOT_FOUND = "SUBSCRIPTION_NOT_FOUND",
102
+ RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED",
103
+ TOO_MANY_MALFORMED_MESSAGES = "TOO_MANY_MALFORMED_MESSAGES",
104
+ CONNECTION_FAILED = "CONNECTION_FAILED",
105
+ CONNECTION_TIMEOUT = "CONNECTION_TIMEOUT",
106
+ INTERNAL_ERROR = "INTERNAL_ERROR",
107
+ NOTIFICATION_DELIVERY_FAILED = "NOTIFICATION_DELIVERY_FAILED"
108
+ }
109
+ /**
110
+ * WebSocket connection record stored in DynamoDB
111
+ */
112
+ export interface ConnectionRecord {
113
+ connectionId: string;
114
+ userId: string;
115
+ connectedAt: number;
116
+ lastPingAt?: number;
117
+ ttl: number;
118
+ }
119
+ /**
120
+ * WebSocket subscription record stored in DynamoDB
121
+ */
122
+ export interface SubscriptionRecord {
123
+ subscriptionId: string;
124
+ connectionId: string;
125
+ userId: string;
126
+ vpnId: string;
127
+ subscribedAt: number;
128
+ ttl: number;
129
+ }
130
+ /**
131
+ * VPN state change event published to EventBridge
132
+ */
133
+ export interface VpnStateChangeEvent {
134
+ version: '0';
135
+ id: string;
136
+ 'detail-type': 'VPN State Change';
137
+ source: 'mytmpvpn.vpn';
138
+ account: string;
139
+ time: string;
140
+ region: string;
141
+ resources: string[];
142
+ detail: VpnStateChangeEventDetail;
143
+ }
144
+ /**
145
+ * Detail section of VPN state change event
146
+ */
147
+ export interface VpnStateChangeEventDetail {
148
+ vpnId: string;
149
+ userId: string;
150
+ locationId?: string;
151
+ geonamesId?: number;
152
+ previousState: VpnState;
153
+ newState: VpnState;
154
+ timestamp: number;
155
+ metadata?: Record<string, any>;
156
+ }
157
+ /**
158
+ * Configuration for WebSocket client
159
+ */
160
+ export interface WebSocketConfig {
161
+ websocketUrl: string;
162
+ getAuthToken: () => Promise<string>;
163
+ reconnect?: boolean;
164
+ reconnectDelay?: number;
165
+ maxReconnectAttempts?: number;
166
+ }
167
+ /**
168
+ * WebSocket connection state
169
+ */
170
+ export declare enum WebSocketConnectionState {
171
+ DISCONNECTED = "DISCONNECTED",
172
+ CONNECTING = "CONNECTING",
173
+ CONNECTED = "CONNECTED",
174
+ RECONNECTING = "RECONNECTING",
175
+ FAILED = "FAILED"
176
+ }
177
+ /**
178
+ * Callback function for state change events
179
+ */
180
+ export type StateChangeCallback = (event: StateChangeNotification) => void;
181
+ /**
182
+ * Callback function for error events
183
+ */
184
+ export type ErrorCallback = (error: ErrorMessage) => void;
185
+ /**
186
+ * Callback function for connection state changes
187
+ */
188
+ export type ConnectionStateCallback = (state: WebSocketConnectionState) => void;
189
+ /**
190
+ * Options for waiting for a specific VPN state
191
+ */
192
+ export interface WaitForStateOptions {
193
+ vpnId: string;
194
+ targetState: VpnState;
195
+ timeoutMs?: number;
196
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebSocketConnectionState = exports.WebSocketErrorCode = void 0;
4
+ // ============================================================================
5
+ // Error Codes
6
+ // ============================================================================
7
+ /**
8
+ * Standard error codes for WebSocket operations
9
+ */
10
+ var WebSocketErrorCode;
11
+ (function (WebSocketErrorCode) {
12
+ // Authentication errors
13
+ WebSocketErrorCode["AUTHENTICATION_FAILED"] = "AUTHENTICATION_FAILED";
14
+ WebSocketErrorCode["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
15
+ WebSocketErrorCode["TOKEN_INVALID"] = "TOKEN_INVALID";
16
+ WebSocketErrorCode["TOKEN_MISSING"] = "TOKEN_MISSING";
17
+ // Authorization errors
18
+ WebSocketErrorCode["PERMISSION_DENIED"] = "PERMISSION_DENIED";
19
+ WebSocketErrorCode["VPN_NOT_OWNED"] = "VPN_NOT_OWNED";
20
+ // Validation errors
21
+ WebSocketErrorCode["INVALID_MESSAGE_FORMAT"] = "INVALID_MESSAGE_FORMAT";
22
+ WebSocketErrorCode["INVALID_VPN_ID"] = "INVALID_VPN_ID";
23
+ WebSocketErrorCode["MISSING_REQUIRED_FIELD"] = "MISSING_REQUIRED_FIELD";
24
+ WebSocketErrorCode["UNKNOWN_ACTION"] = "UNKNOWN_ACTION";
25
+ // Resource errors
26
+ WebSocketErrorCode["VPN_NOT_FOUND"] = "VPN_NOT_FOUND";
27
+ WebSocketErrorCode["SUBSCRIPTION_NOT_FOUND"] = "SUBSCRIPTION_NOT_FOUND";
28
+ // Rate limiting
29
+ WebSocketErrorCode["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED";
30
+ WebSocketErrorCode["TOO_MANY_MALFORMED_MESSAGES"] = "TOO_MANY_MALFORMED_MESSAGES";
31
+ // Connection errors
32
+ WebSocketErrorCode["CONNECTION_FAILED"] = "CONNECTION_FAILED";
33
+ WebSocketErrorCode["CONNECTION_TIMEOUT"] = "CONNECTION_TIMEOUT";
34
+ // Internal errors
35
+ WebSocketErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
36
+ WebSocketErrorCode["NOTIFICATION_DELIVERY_FAILED"] = "NOTIFICATION_DELIVERY_FAILED";
37
+ })(WebSocketErrorCode = exports.WebSocketErrorCode || (exports.WebSocketErrorCode = {}));
38
+ /**
39
+ * WebSocket connection state
40
+ */
41
+ var WebSocketConnectionState;
42
+ (function (WebSocketConnectionState) {
43
+ WebSocketConnectionState["DISCONNECTED"] = "DISCONNECTED";
44
+ WebSocketConnectionState["CONNECTING"] = "CONNECTING";
45
+ WebSocketConnectionState["CONNECTED"] = "CONNECTED";
46
+ WebSocketConnectionState["RECONNECTING"] = "RECONNECTING";
47
+ WebSocketConnectionState["FAILED"] = "FAILED";
48
+ })(WebSocketConnectionState = exports.WebSocketConnectionState || (exports.WebSocketConnectionState = {}));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vpn_1 = require("../../src/models/vpn");
4
+ const websocket_1 = require("../../src/models/websocket");
5
+ describe('WebSocket Types', () => {
6
+ describe('Client Messages', () => {
7
+ it('should create a valid SubscribeMessage', () => {
8
+ const message = {
9
+ action: 'subscribe',
10
+ vpnId: 'test-vpn-id',
11
+ requestId: 'req-123',
12
+ };
13
+ expect(message.action).toBe('subscribe');
14
+ expect(message.vpnId).toBe('test-vpn-id');
15
+ });
16
+ it('should create a SubscribeMessage with "all"', () => {
17
+ const message = {
18
+ action: 'subscribe',
19
+ vpnId: 'all',
20
+ requestId: 'req-123',
21
+ };
22
+ expect(message.vpnId).toBe('all');
23
+ });
24
+ it('should create a valid UnsubscribeMessage', () => {
25
+ const message = {
26
+ action: 'unsubscribe',
27
+ vpnId: 'test-vpn-id',
28
+ requestId: 'req-456',
29
+ };
30
+ expect(message.action).toBe('unsubscribe');
31
+ });
32
+ it('should create a valid PingMessage', () => {
33
+ const message = {
34
+ action: 'ping',
35
+ requestId: 'req-789',
36
+ };
37
+ expect(message.action).toBe('ping');
38
+ });
39
+ it('should accept ClientMessage union type', () => {
40
+ const messages = [
41
+ { action: 'subscribe', vpnId: 'vpn-1', requestId: 'req-1' },
42
+ { action: 'unsubscribe', vpnId: 'vpn-2', requestId: 'req-2' },
43
+ { action: 'ping', requestId: 'req-3' },
44
+ ];
45
+ expect(messages).toHaveLength(3);
46
+ });
47
+ });
48
+ describe('Server Messages', () => {
49
+ it('should create a valid StateChangeNotification', () => {
50
+ const notification = {
51
+ type: 'state_change',
52
+ vpnId: 'test-vpn-id',
53
+ previousState: vpn_1.VpnState.Provisioning,
54
+ newState: vpn_1.VpnState.Running,
55
+ timestamp: Date.now(),
56
+ };
57
+ expect(notification.type).toBe('state_change');
58
+ expect(notification.newState).toBe(vpn_1.VpnState.Running);
59
+ });
60
+ it('should create a valid AcknowledgmentMessage', () => {
61
+ const ack = {
62
+ type: 'ack',
63
+ requestId: 'req-123',
64
+ action: 'subscribe',
65
+ success: true,
66
+ timestamp: Date.now(),
67
+ };
68
+ expect(ack.success).toBe(true);
69
+ });
70
+ it('should create a valid ErrorMessage', () => {
71
+ const error = {
72
+ type: 'error',
73
+ code: websocket_1.WebSocketErrorCode.VPN_NOT_FOUND,
74
+ message: 'VPN not found',
75
+ requestId: 'req-456',
76
+ timestamp: Date.now(),
77
+ };
78
+ expect(error.code).toBe(websocket_1.WebSocketErrorCode.VPN_NOT_FOUND);
79
+ });
80
+ it('should create a valid PongMessage', () => {
81
+ const pong = {
82
+ type: 'pong',
83
+ requestId: 'req-789',
84
+ timestamp: Date.now(),
85
+ };
86
+ expect(pong.type).toBe('pong');
87
+ });
88
+ it('should accept ServerMessage union type', () => {
89
+ const messages = [
90
+ {
91
+ type: 'state_change',
92
+ vpnId: 'vpn-1',
93
+ previousState: vpn_1.VpnState.Creating,
94
+ newState: vpn_1.VpnState.Running,
95
+ timestamp: Date.now(),
96
+ },
97
+ {
98
+ type: 'ack',
99
+ requestId: 'req-1',
100
+ action: 'subscribe',
101
+ success: true,
102
+ timestamp: Date.now(),
103
+ },
104
+ {
105
+ type: 'error',
106
+ code: websocket_1.WebSocketErrorCode.INTERNAL_ERROR,
107
+ message: 'Error',
108
+ timestamp: Date.now(),
109
+ },
110
+ {
111
+ type: 'pong',
112
+ requestId: 'req-2',
113
+ timestamp: Date.now(),
114
+ },
115
+ ];
116
+ expect(messages).toHaveLength(4);
117
+ });
118
+ });
119
+ describe('Error Codes', () => {
120
+ it('should have authentication error codes', () => {
121
+ expect(websocket_1.WebSocketErrorCode.AUTHENTICATION_FAILED).toBe('AUTHENTICATION_FAILED');
122
+ expect(websocket_1.WebSocketErrorCode.TOKEN_EXPIRED).toBe('TOKEN_EXPIRED');
123
+ expect(websocket_1.WebSocketErrorCode.TOKEN_INVALID).toBe('TOKEN_INVALID');
124
+ expect(websocket_1.WebSocketErrorCode.TOKEN_MISSING).toBe('TOKEN_MISSING');
125
+ });
126
+ it('should have authorization error codes', () => {
127
+ expect(websocket_1.WebSocketErrorCode.PERMISSION_DENIED).toBe('PERMISSION_DENIED');
128
+ expect(websocket_1.WebSocketErrorCode.VPN_NOT_OWNED).toBe('VPN_NOT_OWNED');
129
+ });
130
+ it('should have validation error codes', () => {
131
+ expect(websocket_1.WebSocketErrorCode.INVALID_MESSAGE_FORMAT).toBe('INVALID_MESSAGE_FORMAT');
132
+ expect(websocket_1.WebSocketErrorCode.INVALID_VPN_ID).toBe('INVALID_VPN_ID');
133
+ });
134
+ });
135
+ describe('DynamoDB Records', () => {
136
+ it('should create a valid ConnectionRecord', () => {
137
+ const record = {
138
+ connectionId: 'conn-123',
139
+ userId: 'user-456',
140
+ connectedAt: Date.now(),
141
+ ttl: Date.now() + 7200000, // 2 hours
142
+ };
143
+ expect(record.connectionId).toBe('conn-123');
144
+ expect(record.userId).toBe('user-456');
145
+ });
146
+ it('should create a valid SubscriptionRecord', () => {
147
+ const record = {
148
+ subscriptionId: 'conn-123#vpn-456',
149
+ connectionId: 'conn-123',
150
+ userId: 'user-789',
151
+ vpnId: 'vpn-456',
152
+ subscribedAt: Date.now(),
153
+ ttl: Date.now() + 7200000,
154
+ };
155
+ expect(record.subscriptionId).toBe('conn-123#vpn-456');
156
+ expect(record.vpnId).toBe('vpn-456');
157
+ });
158
+ it('should support "all" subscription', () => {
159
+ const record = {
160
+ subscriptionId: 'conn-123#all',
161
+ connectionId: 'conn-123',
162
+ userId: 'user-789',
163
+ vpnId: 'all',
164
+ subscribedAt: Date.now(),
165
+ ttl: Date.now() + 7200000,
166
+ };
167
+ expect(record.vpnId).toBe('all');
168
+ });
169
+ });
170
+ describe('EventBridge Events', () => {
171
+ it('should create a valid VpnStateChangeEvent', () => {
172
+ const event = {
173
+ version: '0',
174
+ id: 'event-123',
175
+ 'detail-type': 'VPN State Change',
176
+ source: 'mytmpvpn.vpn',
177
+ account: '123456789012',
178
+ time: new Date().toISOString(),
179
+ region: 'us-east-1',
180
+ resources: [],
181
+ detail: {
182
+ vpnId: 'vpn-123',
183
+ userId: 'user-456',
184
+ geonamesId: 5128581,
185
+ previousState: vpn_1.VpnState.Provisioning,
186
+ newState: vpn_1.VpnState.Running,
187
+ timestamp: Date.now(),
188
+ },
189
+ };
190
+ expect(event.source).toBe('mytmpvpn.vpn');
191
+ expect(event.detail.newState).toBe(vpn_1.VpnState.Running);
192
+ });
193
+ });
194
+ describe('Connection States', () => {
195
+ it('should have all connection states', () => {
196
+ expect(websocket_1.WebSocketConnectionState.DISCONNECTED).toBe('DISCONNECTED');
197
+ expect(websocket_1.WebSocketConnectionState.CONNECTING).toBe('CONNECTING');
198
+ expect(websocket_1.WebSocketConnectionState.CONNECTED).toBe('CONNECTED');
199
+ expect(websocket_1.WebSocketConnectionState.RECONNECTING).toBe('RECONNECTING');
200
+ expect(websocket_1.WebSocketConnectionState.FAILED).toBe('FAILED');
201
+ });
202
+ });
203
+ });
@@ -126,6 +126,7 @@ describe('Testing vpnId to Wireguard file name functions ', () => {
126
126
  const fileName = (0, vpn_1.vpnIdToWgFileName)(vpn);
127
127
  expect(fileName).toBeDefined();
128
128
  expect(fileName.length).toBeLessThanOrEqual(15);
129
+ expect(fileName.endsWith('.conf')).toBe(true);
129
130
  });
130
131
  });
131
132
  describe('Testing vpn config', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mytmpvpn/mytmpvpn-common",
3
- "version": "10.0.2",
3
+ "version": "16.0.0",
4
4
  "description": "Common library for all MyTmpVpn related projects",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "files": [