@delego/runner 0.1.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/README.md +64 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.js +242 -0
- package/dist/client.js.map +1 -0
- package/dist/connection.js +336 -0
- package/dist/connection.js.map +1 -0
- package/dist/generated-client.d.ts +397 -0
- package/dist/generated-client.js +667 -0
- package/dist/generated-client.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/runner-id.js +96 -0
- package/dist/runner-id.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @delego/sdk
|
|
2
|
+
|
|
3
|
+
Delego SDK for registering and executing actions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @delego/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { DelegoClient } from '@delego/sdk';
|
|
15
|
+
|
|
16
|
+
const client = new DelegoClient({
|
|
17
|
+
apiKey: process.env.DELEGO_API_KEY!,
|
|
18
|
+
baseUrl: 'http://localhost:4000', // optional, defaults to localhost:4000
|
|
19
|
+
runnerName: 'my-runner', // optional, defaults to hostname
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Register an action
|
|
23
|
+
client.registerAction({
|
|
24
|
+
name: 'reset_password',
|
|
25
|
+
description: 'Reset a user password',
|
|
26
|
+
parameters: [
|
|
27
|
+
{
|
|
28
|
+
name: 'email',
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'User email',
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
handler: async ({ email }) => {
|
|
35
|
+
// Your handler logic here
|
|
36
|
+
return { success: true };
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Connect to Delego (registers actions and opens WebSocket)
|
|
41
|
+
await client.connect();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API Reference
|
|
45
|
+
|
|
46
|
+
### `DelegoClient`
|
|
47
|
+
|
|
48
|
+
Main client class for interacting with Delego.
|
|
49
|
+
|
|
50
|
+
#### Constructor
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
new DelegoClient(config: DelegoConfig)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### Methods
|
|
57
|
+
|
|
58
|
+
- `registerAction(action: Action): void` - Register an action with a handler
|
|
59
|
+
- `connect(): Promise<void>` - Connect to Delego and register actions
|
|
60
|
+
- `disconnect(): Promise<void>` - Disconnect from Delego
|
|
61
|
+
|
|
62
|
+
## Types
|
|
63
|
+
|
|
64
|
+
See `src/types.ts` for full type definitions.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Action, DelegoConfig } from './types';
|
|
2
|
+
export declare class DelegoClient {
|
|
3
|
+
private runnerId;
|
|
4
|
+
private runnerIdPromise;
|
|
5
|
+
private config;
|
|
6
|
+
private actions;
|
|
7
|
+
private connected;
|
|
8
|
+
private encoreClient;
|
|
9
|
+
private actionIds;
|
|
10
|
+
private wsConnection;
|
|
11
|
+
constructor(config: DelegoConfig);
|
|
12
|
+
/**
|
|
13
|
+
* Get the runner ID, ensuring it's initialized if needed
|
|
14
|
+
*/
|
|
15
|
+
getRunnerId(): Promise<string>;
|
|
16
|
+
registerAction(action: Action): void;
|
|
17
|
+
getRegisteredActions(): Action[];
|
|
18
|
+
getHandler(actionName: string): Action['handler'] | undefined;
|
|
19
|
+
connect(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Validates if user has required roles to execute an action.
|
|
22
|
+
* Returns true if action is unrestricted (no requiredRoles) or user has at least one required role.
|
|
23
|
+
*/
|
|
24
|
+
private validateRoles;
|
|
25
|
+
private handleInvocation;
|
|
26
|
+
private connectWebSocket;
|
|
27
|
+
disconnect(): Promise<void>;
|
|
28
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import Client from './generated-client';
|
|
3
|
+
import { WebSocketConnection } from './connection';
|
|
4
|
+
import { getOrCreateRunnerId } from './runner-id';
|
|
5
|
+
export class DelegoClient {
|
|
6
|
+
runnerId = null;
|
|
7
|
+
runnerIdPromise = null;
|
|
8
|
+
config;
|
|
9
|
+
actions = new Map();
|
|
10
|
+
connected = false;
|
|
11
|
+
encoreClient = null;
|
|
12
|
+
actionIds = [];
|
|
13
|
+
wsConnection = null;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
// If runnerId is explicitly provided, use it immediately
|
|
16
|
+
if (config.runnerId) {
|
|
17
|
+
this.runnerId = config.runnerId;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// Otherwise, initialize async (will be resolved when first accessed)
|
|
21
|
+
this.runnerIdPromise = getOrCreateRunnerId().then((id) => {
|
|
22
|
+
this.runnerId = id;
|
|
23
|
+
return id;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
this.config = {
|
|
27
|
+
apiKey: config.apiKey,
|
|
28
|
+
baseUrl: config.baseUrl || 'https://production-delego-backend-trui.encr.app',
|
|
29
|
+
runnerName: config.runnerName || os.hostname(),
|
|
30
|
+
debug: config.debug ?? false,
|
|
31
|
+
emailRoles: config.emailRoles,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the runner ID, ensuring it's initialized if needed
|
|
36
|
+
*/
|
|
37
|
+
async getRunnerId() {
|
|
38
|
+
if (this.runnerId !== null) {
|
|
39
|
+
return this.runnerId;
|
|
40
|
+
}
|
|
41
|
+
if (this.runnerIdPromise) {
|
|
42
|
+
return await this.runnerIdPromise;
|
|
43
|
+
}
|
|
44
|
+
// Fallback (shouldn't happen)
|
|
45
|
+
this.runnerIdPromise = getOrCreateRunnerId().then((id) => {
|
|
46
|
+
this.runnerId = id;
|
|
47
|
+
return id;
|
|
48
|
+
});
|
|
49
|
+
return await this.runnerIdPromise;
|
|
50
|
+
}
|
|
51
|
+
registerAction(action) {
|
|
52
|
+
// Validate required fields
|
|
53
|
+
if (!action.name) {
|
|
54
|
+
throw new Error('Action name is required');
|
|
55
|
+
}
|
|
56
|
+
if (!action.description) {
|
|
57
|
+
throw new Error('Action description is required');
|
|
58
|
+
}
|
|
59
|
+
if (!action.handler || typeof action.handler !== 'function') {
|
|
60
|
+
throw new Error('Action handler must be a function');
|
|
61
|
+
}
|
|
62
|
+
// Check for duplicates
|
|
63
|
+
if (this.actions.has(action.name)) {
|
|
64
|
+
throw new Error(`Action "${action.name}" already registered`);
|
|
65
|
+
}
|
|
66
|
+
// Store action
|
|
67
|
+
this.actions.set(action.name, action);
|
|
68
|
+
}
|
|
69
|
+
getRegisteredActions() {
|
|
70
|
+
return Array.from(this.actions.values());
|
|
71
|
+
}
|
|
72
|
+
getHandler(actionName) {
|
|
73
|
+
return this.actions.get(actionName)?.handler;
|
|
74
|
+
}
|
|
75
|
+
async connect() {
|
|
76
|
+
if (this.connected) {
|
|
77
|
+
throw new Error('Already connected');
|
|
78
|
+
}
|
|
79
|
+
if (this.actions.size === 0) {
|
|
80
|
+
throw new Error('No actions registered. Call registerAction() first.');
|
|
81
|
+
}
|
|
82
|
+
// Ensure runner ID is initialized before connecting
|
|
83
|
+
const runnerId = await this.getRunnerId();
|
|
84
|
+
// Create Encore client instance
|
|
85
|
+
this.encoreClient = new Client(this.config.baseUrl);
|
|
86
|
+
try {
|
|
87
|
+
// 1. Establish WebSocket connection first (this creates the runner record)
|
|
88
|
+
await this.connectWebSocket();
|
|
89
|
+
// 2. Register actions via REST using generated client (runner now exists)
|
|
90
|
+
const actions = Array.from(this.actions.values()).map((a) => ({
|
|
91
|
+
name: a.name,
|
|
92
|
+
description: a.description,
|
|
93
|
+
parameters: a.parameters || [],
|
|
94
|
+
requiredRoles: a.requiredRoles,
|
|
95
|
+
}));
|
|
96
|
+
// Set up timeout for registration request
|
|
97
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
98
|
+
setTimeout(() => reject(new Error('Registration request timed out after 30 seconds')), 30000);
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
const result = await Promise.race([
|
|
102
|
+
this.encoreClient.core.registerActions({
|
|
103
|
+
runnerId,
|
|
104
|
+
actions,
|
|
105
|
+
apiKey: this.config.apiKey,
|
|
106
|
+
emailRoles: this.config.emailRoles,
|
|
107
|
+
}),
|
|
108
|
+
timeoutPromise,
|
|
109
|
+
]);
|
|
110
|
+
// Store action IDs if needed
|
|
111
|
+
this.actionIds = result.actionIds;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
// Check if it's a timeout error
|
|
115
|
+
if (error.message === 'Registration request timed out after 30 seconds') {
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
// Handle API errors
|
|
119
|
+
if (error.status) {
|
|
120
|
+
if (error.status === 401) {
|
|
121
|
+
throw new Error('Invalid API key');
|
|
122
|
+
}
|
|
123
|
+
else if (error.status === 404) {
|
|
124
|
+
throw new Error(`Runner ${this.runnerId} not found`);
|
|
125
|
+
}
|
|
126
|
+
else if (error.status === 403) {
|
|
127
|
+
throw new Error('Permission denied: Runner does not belong to tenant');
|
|
128
|
+
}
|
|
129
|
+
else if (error.status === 400) {
|
|
130
|
+
throw new Error(`Invalid request: ${error.message || 'Bad request'}`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
throw new Error(`Registration failed: ${error.message || `HTTP ${error.status}`}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`Failed to register actions: ${error.message || String(error)}`);
|
|
137
|
+
}
|
|
138
|
+
this.connected = true;
|
|
139
|
+
console.log(`Connected to Delego as runner ${runnerId}`);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
// Clean up on failure
|
|
143
|
+
this.connected = false;
|
|
144
|
+
if (this.wsConnection) {
|
|
145
|
+
this.wsConnection.disconnect();
|
|
146
|
+
this.wsConnection = null;
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Validates if user has required roles to execute an action.
|
|
153
|
+
* Returns true if action is unrestricted (no requiredRoles) or user has at least one required role.
|
|
154
|
+
*/
|
|
155
|
+
validateRoles(action, userContext) {
|
|
156
|
+
console.log('validateRoles', action, userContext);
|
|
157
|
+
// No required roles = unrestricted (allow all users)
|
|
158
|
+
if (!action.requiredRoles || action.requiredRoles.length === 0) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
// Get user's roles from email mapping
|
|
162
|
+
const userEmail = userContext?.email;
|
|
163
|
+
if (!userEmail) {
|
|
164
|
+
// User has no email = no roles
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
const userRoles = this.config.emailRoles?.[userEmail] ?? [];
|
|
168
|
+
// User needs at least one of the required roles (OR logic)
|
|
169
|
+
return action.requiredRoles.some((role) => userRoles.includes(role));
|
|
170
|
+
}
|
|
171
|
+
async handleInvocation(invocationId, actionName, parameters, userContext) {
|
|
172
|
+
const action = this.actions.get(actionName);
|
|
173
|
+
if (!action) {
|
|
174
|
+
// Unknown action - send error response immediately
|
|
175
|
+
await this.wsConnection?.sendResult(invocationId, false, undefined, `Unknown action: ${actionName}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Validate roles before executing handler
|
|
179
|
+
if (!this.validateRoles(action, userContext)) {
|
|
180
|
+
// User is unauthorized - send special error format
|
|
181
|
+
const errorMessage = JSON.stringify({
|
|
182
|
+
success: false,
|
|
183
|
+
error: 'UNAUTHORIZED',
|
|
184
|
+
message: 'You do not have permission to execute this action',
|
|
185
|
+
requiredRoles: action.requiredRoles,
|
|
186
|
+
});
|
|
187
|
+
await this.wsConnection?.sendResult(invocationId, false, undefined, errorMessage);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
// Construct InvocationContext - user context defaults to empty if not provided
|
|
192
|
+
const context = {
|
|
193
|
+
invocationId,
|
|
194
|
+
parameters,
|
|
195
|
+
user: userContext || {
|
|
196
|
+
slackUserId: '',
|
|
197
|
+
groupIds: [],
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
console.log(`Executing action: ${actionName}`, context);
|
|
201
|
+
const result = await action.handler(context);
|
|
202
|
+
await this.wsConnection?.sendResult(invocationId, true, result);
|
|
203
|
+
console.log(`Action completed: ${actionName}`, result);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
207
|
+
console.error(`Action failed: ${actionName}`, errorMessage);
|
|
208
|
+
await this.wsConnection?.sendResult(invocationId, false, undefined, errorMessage);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async connectWebSocket() {
|
|
212
|
+
// Ensure runner ID is initialized
|
|
213
|
+
const runnerId = await this.getRunnerId();
|
|
214
|
+
const connectionOptions = {
|
|
215
|
+
baseUrl: this.config.baseUrl,
|
|
216
|
+
apiKey: this.config.apiKey,
|
|
217
|
+
runnerId,
|
|
218
|
+
runnerName: this.config.runnerName,
|
|
219
|
+
debug: this.config.debug,
|
|
220
|
+
onInvoke: (invocationId, actionName, params, userContext) => {
|
|
221
|
+
// Handle async without blocking - fire and forget pattern
|
|
222
|
+
this.handleInvocation(invocationId, actionName, params, userContext).catch((err) => {
|
|
223
|
+
console.error('Unexpected error handling invocation:', err);
|
|
224
|
+
});
|
|
225
|
+
},
|
|
226
|
+
onDisconnect: () => {
|
|
227
|
+
this.connected = false;
|
|
228
|
+
console.log('Disconnected from Delego');
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
this.wsConnection = new WebSocketConnection(connectionOptions);
|
|
232
|
+
await this.wsConnection.connect();
|
|
233
|
+
}
|
|
234
|
+
async disconnect() {
|
|
235
|
+
if (this.wsConnection) {
|
|
236
|
+
this.wsConnection.disconnect();
|
|
237
|
+
this.wsConnection = null;
|
|
238
|
+
}
|
|
239
|
+
this.connected = false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// WebSocket connection management for Delego SDK
|
|
2
|
+
import { WebSocket as WS } from 'ws';
|
|
3
|
+
import { StreamInOut } from './generated-client';
|
|
4
|
+
if (typeof globalThis.WebSocket === 'undefined') {
|
|
5
|
+
globalThis.WebSocket = WS;
|
|
6
|
+
}
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// URL HELPERS
|
|
9
|
+
// ============================================================================
|
|
10
|
+
function buildWebSocketUrl(baseUrl) {
|
|
11
|
+
return baseUrl
|
|
12
|
+
.replace(/^http:\/\//, 'ws://')
|
|
13
|
+
.replace(/^https:\/\//, 'wss://');
|
|
14
|
+
}
|
|
15
|
+
function buildConnectionUrl(baseUrl, apiKey, runnerId, runnerName) {
|
|
16
|
+
const wsUrl = buildWebSocketUrl(baseUrl);
|
|
17
|
+
const queryParams = new URLSearchParams({ apiKey, runnerId });
|
|
18
|
+
if (runnerName) {
|
|
19
|
+
queryParams.set('runnerName', runnerName);
|
|
20
|
+
}
|
|
21
|
+
return `${wsUrl}/core/runners/connect?${queryParams.toString()}`;
|
|
22
|
+
}
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// MESSAGE PARSER
|
|
25
|
+
// ============================================================================
|
|
26
|
+
class MessageParser {
|
|
27
|
+
parse(data) {
|
|
28
|
+
const rawData = this.extractRawData(data);
|
|
29
|
+
return this.parseJson(rawData);
|
|
30
|
+
}
|
|
31
|
+
extractRawData(data) {
|
|
32
|
+
if (data && typeof data === 'object' && 'data' in data) {
|
|
33
|
+
return data.data;
|
|
34
|
+
}
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
parseJson(rawData) {
|
|
38
|
+
if (typeof rawData === 'string') {
|
|
39
|
+
return JSON.parse(rawData);
|
|
40
|
+
}
|
|
41
|
+
if (rawData instanceof Buffer || ArrayBuffer.isView(rawData)) {
|
|
42
|
+
return JSON.parse(rawData.toString());
|
|
43
|
+
}
|
|
44
|
+
throw new Error('Unable to parse message data');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// CONNECTION LOGGER
|
|
49
|
+
// ============================================================================
|
|
50
|
+
class ConnectionLogger {
|
|
51
|
+
debug;
|
|
52
|
+
constructor(debug = false) {
|
|
53
|
+
this.debug = debug;
|
|
54
|
+
}
|
|
55
|
+
log(message, ...args) {
|
|
56
|
+
if (this.debug) {
|
|
57
|
+
console.log(`[Delego] ${message}`, ...args);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
error(message, ...args) {
|
|
61
|
+
console.error(`[Delego] ${message}`, ...args);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
class HeartbeatManager {
|
|
65
|
+
callbacks;
|
|
66
|
+
logger;
|
|
67
|
+
interval = null;
|
|
68
|
+
intervalMs = 10000;
|
|
69
|
+
constructor(callbacks, logger) {
|
|
70
|
+
this.callbacks = callbacks;
|
|
71
|
+
this.logger = logger;
|
|
72
|
+
}
|
|
73
|
+
start() {
|
|
74
|
+
this.stop();
|
|
75
|
+
this.logger.log('Starting heartbeat (every 10s)');
|
|
76
|
+
this.interval = setInterval(() => this.tick(), this.intervalMs);
|
|
77
|
+
}
|
|
78
|
+
stop() {
|
|
79
|
+
if (this.interval) {
|
|
80
|
+
clearInterval(this.interval);
|
|
81
|
+
this.interval = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async tick() {
|
|
85
|
+
this.logger.log('Sending heartbeat...');
|
|
86
|
+
try {
|
|
87
|
+
await this.callbacks.sendHeartbeat();
|
|
88
|
+
this.logger.log('Heartbeat sent');
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
this.logger.error('Failed to send heartbeat:', error);
|
|
92
|
+
this.stop();
|
|
93
|
+
this.callbacks.onHeartbeatFailed(error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
class ReconnectionManager {
|
|
98
|
+
logger;
|
|
99
|
+
attempts = 0;
|
|
100
|
+
stopped = false;
|
|
101
|
+
config;
|
|
102
|
+
constructor(config = {}, logger) {
|
|
103
|
+
this.logger = logger;
|
|
104
|
+
this.config = {
|
|
105
|
+
initialDelayMs: config.initialDelayMs ?? 1000,
|
|
106
|
+
maxDelayMs: config.maxDelayMs ?? 30000,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
reset() {
|
|
110
|
+
this.attempts = 0;
|
|
111
|
+
this.stopped = false;
|
|
112
|
+
}
|
|
113
|
+
canRetry() {
|
|
114
|
+
return !this.stopped;
|
|
115
|
+
}
|
|
116
|
+
stop() {
|
|
117
|
+
this.stopped = true;
|
|
118
|
+
}
|
|
119
|
+
async waitAndIncrement() {
|
|
120
|
+
this.attempts++;
|
|
121
|
+
const delay = Math.min(this.config.initialDelayMs * Math.pow(2, this.attempts - 1), this.config.maxDelayMs);
|
|
122
|
+
this.logger.log(`Waiting ${delay}ms before reconnecting (attempt ${this.attempts})...`);
|
|
123
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
124
|
+
}
|
|
125
|
+
getAttemptCount() {
|
|
126
|
+
return this.attempts;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// MAIN CLASS
|
|
131
|
+
// ============================================================================
|
|
132
|
+
export class WebSocketConnection {
|
|
133
|
+
stream = null;
|
|
134
|
+
state = 'disconnected';
|
|
135
|
+
options;
|
|
136
|
+
logger;
|
|
137
|
+
messageParser = new MessageParser();
|
|
138
|
+
heartbeat;
|
|
139
|
+
reconnection;
|
|
140
|
+
constructor(options) {
|
|
141
|
+
this.options = options;
|
|
142
|
+
this.logger = new ConnectionLogger(options.debug ?? false);
|
|
143
|
+
this.heartbeat = new HeartbeatManager({
|
|
144
|
+
sendHeartbeat: () => this.sendHeartbeat(),
|
|
145
|
+
onHeartbeatFailed: () => this.handleHeartbeatFailure(),
|
|
146
|
+
}, this.logger);
|
|
147
|
+
this.reconnection = new ReconnectionManager({}, this.logger);
|
|
148
|
+
}
|
|
149
|
+
getState() {
|
|
150
|
+
return this.state;
|
|
151
|
+
}
|
|
152
|
+
async connect() {
|
|
153
|
+
this.guardNotAlreadyConnecting();
|
|
154
|
+
this.state = 'connecting';
|
|
155
|
+
this.reconnection.reset();
|
|
156
|
+
await this.attemptConnection();
|
|
157
|
+
}
|
|
158
|
+
async attemptConnection() {
|
|
159
|
+
try {
|
|
160
|
+
this.stream = this.createStream();
|
|
161
|
+
await this.waitForReady();
|
|
162
|
+
this.startMessageLoop();
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
this.state = 'disconnected';
|
|
166
|
+
if (!this.reconnection.canRetry()) {
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
this.logger.error('Connection failed:', error);
|
|
170
|
+
await this.reconnection.waitAndIncrement();
|
|
171
|
+
this.state = 'connecting';
|
|
172
|
+
await this.attemptConnection();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async send(msg) {
|
|
176
|
+
if (!this.stream) {
|
|
177
|
+
throw new Error('Not connected');
|
|
178
|
+
}
|
|
179
|
+
if (this.state !== 'connected') {
|
|
180
|
+
throw new Error('Connection not established');
|
|
181
|
+
}
|
|
182
|
+
await this.stream.send(msg);
|
|
183
|
+
}
|
|
184
|
+
async sendResult(invocationId, success, result, error) {
|
|
185
|
+
await this.send({
|
|
186
|
+
type: 'invocation_result',
|
|
187
|
+
invocationId,
|
|
188
|
+
success,
|
|
189
|
+
result,
|
|
190
|
+
error,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
disconnect() {
|
|
194
|
+
this.state = 'disconnected';
|
|
195
|
+
this.heartbeat.stop();
|
|
196
|
+
this.reconnection.stop();
|
|
197
|
+
if (this.stream) {
|
|
198
|
+
this.stream.close();
|
|
199
|
+
this.stream = null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// ===========================================================================
|
|
203
|
+
// Private: Connection Setup
|
|
204
|
+
// ===========================================================================
|
|
205
|
+
guardNotAlreadyConnecting() {
|
|
206
|
+
if (this.state === 'connected' || this.state === 'connecting') {
|
|
207
|
+
throw new Error('Already connected or connecting');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
createStream() {
|
|
211
|
+
const url = buildConnectionUrl(this.options.baseUrl, this.options.apiKey, this.options.runnerId, this.options.runnerName);
|
|
212
|
+
return new StreamInOut(url);
|
|
213
|
+
}
|
|
214
|
+
waitForReady() {
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
const timeout = setTimeout(() => {
|
|
217
|
+
reject(new Error('WebSocket connection timeout'));
|
|
218
|
+
}, 10000);
|
|
219
|
+
const cleanup = () => clearTimeout(timeout);
|
|
220
|
+
let isOpen = false;
|
|
221
|
+
this.stream.socket.on('open', () => {
|
|
222
|
+
isOpen = true;
|
|
223
|
+
this.logger.log('WebSocket opened, waiting for ready message...');
|
|
224
|
+
});
|
|
225
|
+
this.stream.socket.on('message', (data) => {
|
|
226
|
+
if (!isOpen)
|
|
227
|
+
return;
|
|
228
|
+
try {
|
|
229
|
+
const msg = this.messageParser.parse(data);
|
|
230
|
+
if (msg.type === 'ready') {
|
|
231
|
+
cleanup();
|
|
232
|
+
this.onReady();
|
|
233
|
+
resolve();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Ignore parse errors for non-ready messages
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
this.stream.socket.on('error', (error) => {
|
|
241
|
+
cleanup();
|
|
242
|
+
this.state = 'disconnected';
|
|
243
|
+
reject(error);
|
|
244
|
+
});
|
|
245
|
+
this.stream.socket.on('close', () => {
|
|
246
|
+
if (this.state !== 'connected') {
|
|
247
|
+
cleanup();
|
|
248
|
+
this.state = 'disconnected';
|
|
249
|
+
reject(new Error('WebSocket closed before ready'));
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
onReady() {
|
|
255
|
+
this.state = 'connected';
|
|
256
|
+
this.reconnection.reset();
|
|
257
|
+
this.logger.log('Connected - received ready message');
|
|
258
|
+
this.heartbeat.start();
|
|
259
|
+
}
|
|
260
|
+
// ===========================================================================
|
|
261
|
+
// Private: Message Loop
|
|
262
|
+
// ===========================================================================
|
|
263
|
+
async startMessageLoop() {
|
|
264
|
+
if (!this.stream)
|
|
265
|
+
return;
|
|
266
|
+
try {
|
|
267
|
+
for await (const msg of this.stream) {
|
|
268
|
+
this.handleMessage(msg);
|
|
269
|
+
}
|
|
270
|
+
this.logger.log('Stream ended');
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
this.logger.error('Stream error:', error);
|
|
274
|
+
}
|
|
275
|
+
await this.handleDisconnection();
|
|
276
|
+
}
|
|
277
|
+
handleMessage(msg) {
|
|
278
|
+
switch (msg.type) {
|
|
279
|
+
case 'heartbeat_ack':
|
|
280
|
+
this.logger.log('Heartbeat acknowledged');
|
|
281
|
+
break;
|
|
282
|
+
case 'ready':
|
|
283
|
+
// Already handled during connection setup, ignore
|
|
284
|
+
break;
|
|
285
|
+
case 'invoke':
|
|
286
|
+
if (msg.invocationId && msg.actionName) {
|
|
287
|
+
this.logger.log(`Received invocation: ${msg.actionName} (${msg.invocationId})`);
|
|
288
|
+
this.options.onInvoke(msg.invocationId, msg.actionName, msg.parameters || {}, msg.userContext);
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// ===========================================================================
|
|
294
|
+
// Private: Heartbeat
|
|
295
|
+
// ===========================================================================
|
|
296
|
+
async sendHeartbeat() {
|
|
297
|
+
if (this.stream && this.state === 'connected') {
|
|
298
|
+
await this.send({ type: 'heartbeat' });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
handleHeartbeatFailure() {
|
|
302
|
+
this.state = 'disconnected';
|
|
303
|
+
this.options.onDisconnect?.();
|
|
304
|
+
}
|
|
305
|
+
// ===========================================================================
|
|
306
|
+
// Private: Reconnection
|
|
307
|
+
// ===========================================================================
|
|
308
|
+
async handleDisconnection() {
|
|
309
|
+
this.logger.log('Disconnected');
|
|
310
|
+
this.state = 'disconnected';
|
|
311
|
+
this.heartbeat.stop();
|
|
312
|
+
this.options.onDisconnect?.();
|
|
313
|
+
if (this.reconnection.canRetry()) {
|
|
314
|
+
this.logger.log('Will attempt to reconnect...');
|
|
315
|
+
await this.attemptReconnect();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async attemptReconnect() {
|
|
319
|
+
await this.reconnection.waitAndIncrement();
|
|
320
|
+
try {
|
|
321
|
+
this.state = 'connecting';
|
|
322
|
+
this.stream = this.createStream();
|
|
323
|
+
await this.waitForReady();
|
|
324
|
+
this.logger.log('Reconnected successfully');
|
|
325
|
+
this.startMessageLoop();
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
this.state = 'disconnected';
|
|
329
|
+
if (this.reconnection.canRetry()) {
|
|
330
|
+
this.logger.error('Reconnection failed:', error);
|
|
331
|
+
await this.attemptReconnect();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=connection.js.map
|