@stepflowjs/trigger-websocket 0.0.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/dist/index.d.ts +74 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Trigger, TriggerHandler } from '@stepflowjs/core';
|
|
2
|
+
|
|
3
|
+
interface WebSocketTriggerConfig {
|
|
4
|
+
/** WebSocket server URL to connect to */
|
|
5
|
+
url: string;
|
|
6
|
+
/** WebSocket sub-protocols */
|
|
7
|
+
protocols?: string[];
|
|
8
|
+
/** Custom headers for connection */
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
/** Auto-reconnect on disconnect */
|
|
11
|
+
reconnect?: boolean;
|
|
12
|
+
/** Reconnect interval in milliseconds */
|
|
13
|
+
reconnectInterval?: number;
|
|
14
|
+
/** Maximum reconnect attempts */
|
|
15
|
+
maxReconnectAttempts?: number;
|
|
16
|
+
/** Message parsing type */
|
|
17
|
+
messageType?: "json" | "text";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* WebSocket trigger for Stepflow workflows
|
|
21
|
+
*
|
|
22
|
+
* Connects to a WebSocket server and triggers workflows on incoming messages.
|
|
23
|
+
* Supports auto-reconnect with configurable retry logic.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const trigger = new WebSocketTrigger({
|
|
28
|
+
* url: 'wss://api.example.com/events',
|
|
29
|
+
* messageType: 'json',
|
|
30
|
+
* reconnect: true,
|
|
31
|
+
* reconnectInterval: 5000,
|
|
32
|
+
* maxReconnectAttempts: 10,
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* await trigger.start(async (event) => {
|
|
36
|
+
* await stepflow.trigger('process-event', event.data);
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare class WebSocketTrigger implements Trigger<WebSocketTriggerConfig> {
|
|
41
|
+
readonly config: WebSocketTriggerConfig;
|
|
42
|
+
readonly type = "websocket";
|
|
43
|
+
private ws?;
|
|
44
|
+
private handler?;
|
|
45
|
+
private reconnectAttempts;
|
|
46
|
+
private shouldReconnect;
|
|
47
|
+
private reconnectTimeout?;
|
|
48
|
+
constructor(config: WebSocketTriggerConfig);
|
|
49
|
+
/**
|
|
50
|
+
* Start the trigger with a handler function
|
|
51
|
+
* @param handler Function to call when WebSocket messages are received
|
|
52
|
+
*/
|
|
53
|
+
start(handler: TriggerHandler): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Connect to the WebSocket server
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
private connect;
|
|
59
|
+
/**
|
|
60
|
+
* Attempt to reconnect to the WebSocket server
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
private attemptReconnect;
|
|
64
|
+
/**
|
|
65
|
+
* Stop the trigger and close the WebSocket connection
|
|
66
|
+
*/
|
|
67
|
+
stop(): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Health check - returns true if WebSocket is connected
|
|
70
|
+
*/
|
|
71
|
+
healthCheck(): Promise<boolean>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { WebSocketTrigger, type WebSocketTriggerConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
var WebSocketTrigger = class {
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.config = config;
|
|
6
|
+
}
|
|
7
|
+
type = "websocket";
|
|
8
|
+
ws;
|
|
9
|
+
handler;
|
|
10
|
+
reconnectAttempts = 0;
|
|
11
|
+
shouldReconnect = true;
|
|
12
|
+
reconnectTimeout;
|
|
13
|
+
/**
|
|
14
|
+
* Start the trigger with a handler function
|
|
15
|
+
* @param handler Function to call when WebSocket messages are received
|
|
16
|
+
*/
|
|
17
|
+
async start(handler) {
|
|
18
|
+
this.handler = handler;
|
|
19
|
+
this.shouldReconnect = true;
|
|
20
|
+
await this.connect();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Connect to the WebSocket server
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
async connect() {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
try {
|
|
29
|
+
const options = {};
|
|
30
|
+
if (this.config.headers) {
|
|
31
|
+
options.headers = this.config.headers;
|
|
32
|
+
}
|
|
33
|
+
this.ws = new WebSocket(
|
|
34
|
+
this.config.url,
|
|
35
|
+
this.config.protocols,
|
|
36
|
+
options
|
|
37
|
+
);
|
|
38
|
+
this.ws.on("open", () => {
|
|
39
|
+
this.reconnectAttempts = 0;
|
|
40
|
+
resolve();
|
|
41
|
+
});
|
|
42
|
+
this.ws.on("message", async (rawData) => {
|
|
43
|
+
try {
|
|
44
|
+
let data;
|
|
45
|
+
const messageStr = rawData.toString();
|
|
46
|
+
if (this.config.messageType === "text") {
|
|
47
|
+
data = messageStr;
|
|
48
|
+
} else {
|
|
49
|
+
try {
|
|
50
|
+
data = JSON.parse(messageStr);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(
|
|
53
|
+
"Failed to parse WebSocket message as JSON:",
|
|
54
|
+
error
|
|
55
|
+
);
|
|
56
|
+
data = messageStr;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const triggerEvent = {
|
|
60
|
+
id: crypto.randomUUID(),
|
|
61
|
+
type: this.type,
|
|
62
|
+
source: this.config.url,
|
|
63
|
+
data,
|
|
64
|
+
metadata: {
|
|
65
|
+
messageType: this.config.messageType || "json"
|
|
66
|
+
},
|
|
67
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
68
|
+
};
|
|
69
|
+
await this.handler?.(triggerEvent);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error("Error handling WebSocket message:", error);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
this.ws.on("close", () => {
|
|
75
|
+
if (this.shouldReconnect && this.config.reconnect !== false) {
|
|
76
|
+
this.attemptReconnect();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
this.ws.on("error", (error) => {
|
|
80
|
+
console.error("WebSocket error:", error);
|
|
81
|
+
if (this.reconnectAttempts === 0) {
|
|
82
|
+
reject(error);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
reject(error);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Attempt to reconnect to the WebSocket server
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
attemptReconnect() {
|
|
95
|
+
const maxAttempts = this.config.maxReconnectAttempts ?? 10;
|
|
96
|
+
const interval = this.config.reconnectInterval ?? 5e3;
|
|
97
|
+
if (this.reconnectAttempts < maxAttempts) {
|
|
98
|
+
this.reconnectAttempts++;
|
|
99
|
+
console.log(
|
|
100
|
+
`WebSocket reconnect attempt ${this.reconnectAttempts}/${maxAttempts}`
|
|
101
|
+
);
|
|
102
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
103
|
+
this.connect().catch((error) => {
|
|
104
|
+
console.error("WebSocket reconnect failed:", error);
|
|
105
|
+
});
|
|
106
|
+
}, interval);
|
|
107
|
+
} else {
|
|
108
|
+
console.error(
|
|
109
|
+
`WebSocket max reconnect attempts (${maxAttempts}) reached`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Stop the trigger and close the WebSocket connection
|
|
115
|
+
*/
|
|
116
|
+
async stop() {
|
|
117
|
+
this.shouldReconnect = false;
|
|
118
|
+
this.handler = void 0;
|
|
119
|
+
if (this.reconnectTimeout) {
|
|
120
|
+
clearTimeout(this.reconnectTimeout);
|
|
121
|
+
this.reconnectTimeout = void 0;
|
|
122
|
+
}
|
|
123
|
+
if (this.ws) {
|
|
124
|
+
this.ws.close();
|
|
125
|
+
this.ws = void 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Health check - returns true if WebSocket is connected
|
|
130
|
+
*/
|
|
131
|
+
async healthCheck() {
|
|
132
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
export {
|
|
136
|
+
WebSocketTrigger
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Trigger, TriggerHandler, TriggerEvent } from \"@stepflowjs/core\";\nimport WebSocket from \"ws\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface WebSocketTriggerConfig {\n /** WebSocket server URL to connect to */\n url: string;\n /** WebSocket sub-protocols */\n protocols?: string[];\n /** Custom headers for connection */\n headers?: Record<string, string>;\n /** Auto-reconnect on disconnect */\n reconnect?: boolean;\n /** Reconnect interval in milliseconds */\n reconnectInterval?: number;\n /** Maximum reconnect attempts */\n maxReconnectAttempts?: number;\n /** Message parsing type */\n messageType?: \"json\" | \"text\";\n}\n\n// ============================================================================\n// WebSocketTrigger Implementation\n// ============================================================================\n\n/**\n * WebSocket trigger for Stepflow workflows\n *\n * Connects to a WebSocket server and triggers workflows on incoming messages.\n * Supports auto-reconnect with configurable retry logic.\n *\n * @example\n * ```typescript\n * const trigger = new WebSocketTrigger({\n * url: 'wss://api.example.com/events',\n * messageType: 'json',\n * reconnect: true,\n * reconnectInterval: 5000,\n * maxReconnectAttempts: 10,\n * });\n *\n * await trigger.start(async (event) => {\n * await stepflow.trigger('process-event', event.data);\n * });\n * ```\n */\nexport class WebSocketTrigger implements Trigger<WebSocketTriggerConfig> {\n readonly type = \"websocket\";\n private ws?: WebSocket;\n private handler?: TriggerHandler;\n private reconnectAttempts = 0;\n private shouldReconnect = true;\n private reconnectTimeout?: NodeJS.Timeout;\n\n constructor(readonly config: WebSocketTriggerConfig) {}\n\n /**\n * Start the trigger with a handler function\n * @param handler Function to call when WebSocket messages are received\n */\n async start(handler: TriggerHandler): Promise<void> {\n this.handler = handler;\n this.shouldReconnect = true;\n await this.connect();\n }\n\n /**\n * Connect to the WebSocket server\n * @private\n */\n private async connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n // Create WebSocket connection\n const options: WebSocket.ClientOptions = {};\n if (this.config.headers) {\n options.headers = this.config.headers;\n }\n\n this.ws = new WebSocket(\n this.config.url,\n this.config.protocols,\n options,\n );\n\n // Handle connection open\n this.ws.on(\"open\", () => {\n this.reconnectAttempts = 0;\n resolve();\n });\n\n // Handle incoming messages\n this.ws.on(\"message\", async (rawData: WebSocket.RawData) => {\n try {\n let data: unknown;\n\n // Parse message based on messageType\n const messageStr = rawData.toString();\n if (this.config.messageType === \"text\") {\n data = messageStr;\n } else {\n // Default to JSON parsing\n try {\n data = JSON.parse(messageStr);\n } catch (error) {\n console.error(\n \"Failed to parse WebSocket message as JSON:\",\n error,\n );\n data = messageStr;\n }\n }\n\n // Create trigger event\n const triggerEvent: TriggerEvent = {\n id: crypto.randomUUID(),\n type: this.type,\n source: this.config.url,\n data,\n metadata: {\n messageType: this.config.messageType || \"json\",\n },\n timestamp: new Date(),\n };\n\n // Invoke handler\n await this.handler?.(triggerEvent);\n } catch (error) {\n console.error(\"Error handling WebSocket message:\", error);\n }\n });\n\n // Handle connection close\n this.ws.on(\"close\", () => {\n if (this.shouldReconnect && this.config.reconnect !== false) {\n this.attemptReconnect();\n }\n });\n\n // Handle errors\n this.ws.on(\"error\", (error) => {\n console.error(\"WebSocket error:\", error);\n // If connection fails during initial connect, reject the promise\n if (this.reconnectAttempts === 0) {\n reject(error);\n }\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Attempt to reconnect to the WebSocket server\n * @private\n */\n private attemptReconnect(): void {\n const maxAttempts = this.config.maxReconnectAttempts ?? 10;\n const interval = this.config.reconnectInterval ?? 5000;\n\n if (this.reconnectAttempts < maxAttempts) {\n this.reconnectAttempts++;\n console.log(\n `WebSocket reconnect attempt ${this.reconnectAttempts}/${maxAttempts}`,\n );\n\n this.reconnectTimeout = setTimeout(() => {\n this.connect().catch((error) => {\n console.error(\"WebSocket reconnect failed:\", error);\n });\n }, interval);\n } else {\n console.error(\n `WebSocket max reconnect attempts (${maxAttempts}) reached`,\n );\n }\n }\n\n /**\n * Stop the trigger and close the WebSocket connection\n */\n async stop(): Promise<void> {\n this.shouldReconnect = false;\n this.handler = undefined;\n\n // Clear any pending reconnect timeout\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = undefined;\n }\n\n // Close WebSocket connection\n if (this.ws) {\n this.ws.close();\n this.ws = undefined;\n }\n }\n\n /**\n * Health check - returns true if WebSocket is connected\n */\n async healthCheck(): Promise<boolean> {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stepflowjs/trigger-websocket",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "WebSocket trigger for Stepflow with auto-reconnect",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"ws": "^8.18.0",
|
|
20
|
+
"@stepflowjs/core": "0.0.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/ws": "^8.5.0",
|
|
24
|
+
"tsup": "^8.5.1",
|
|
25
|
+
"vitest": "^4.0.17"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"typescript": "^5.0.0"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"author": "Stepflow Contributors",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://stepflow-production.up.railway.app",
|
|
35
|
+
"directory": "packages/triggers/websocket"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://stepflow-production.up.railway.app",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://stepflow-production.up.railway.app"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"stepflow",
|
|
43
|
+
"trigger",
|
|
44
|
+
"websocket",
|
|
45
|
+
"realtime",
|
|
46
|
+
"workflow",
|
|
47
|
+
"orchestration"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup",
|
|
54
|
+
"dev": "tsup --watch",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"test": "vitest",
|
|
57
|
+
"clean": "rm -rf dist"
|
|
58
|
+
}
|
|
59
|
+
}
|