@recursorsdk/sdk 1.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.
@@ -0,0 +1,190 @@
1
+ /**
2
+ * WebSocket Client for Recursor SDK
3
+ * Provides real-time updates via WebSocket connection
4
+ */
5
+ export class RecursorWebSocket {
6
+ constructor(baseUrl, accessToken) {
7
+ this.ws = null;
8
+ this.reconnectAttempts = 0;
9
+ this.maxReconnectAttempts = 5;
10
+ this.reconnectDelay = 1000;
11
+ this.pingInterval = null;
12
+ this.isConnecting = false;
13
+ this.shouldReconnect = true;
14
+ this.eventHandlers = new Map();
15
+ // Convert http/https to ws/wss
16
+ this.baseUrl = baseUrl.replace(/^http/, "ws");
17
+ this.accessToken = accessToken;
18
+ }
19
+ /**
20
+ * Connect to WebSocket server
21
+ */
22
+ connect() {
23
+ return new Promise((resolve, reject) => {
24
+ if (this.isConnecting || (this.ws && this.ws.readyState === WebSocket.OPEN)) {
25
+ resolve();
26
+ return;
27
+ }
28
+ this.isConnecting = true;
29
+ this.shouldReconnect = true;
30
+ const wsUrl = `${this.baseUrl}/client/ws?token=${this.accessToken}`;
31
+ try {
32
+ this.ws = new WebSocket(wsUrl);
33
+ this.ws.onopen = () => {
34
+ this.isConnecting = false;
35
+ this.reconnectAttempts = 0;
36
+ this.startPing();
37
+ resolve();
38
+ };
39
+ this.ws.onmessage = (event) => {
40
+ try {
41
+ const message = JSON.parse(event.data);
42
+ this.handleMessage(message);
43
+ }
44
+ catch (error) {
45
+ console.error("Failed to parse WebSocket message:", error);
46
+ }
47
+ };
48
+ this.ws.onclose = () => {
49
+ this.isConnecting = false;
50
+ this.stopPing();
51
+ if (this.shouldReconnect) {
52
+ this.attemptReconnect();
53
+ }
54
+ };
55
+ this.ws.onerror = (error) => {
56
+ console.error("WebSocket error:", error);
57
+ this.isConnecting = false;
58
+ reject(error);
59
+ };
60
+ }
61
+ catch (error) {
62
+ this.isConnecting = false;
63
+ reject(error);
64
+ }
65
+ });
66
+ }
67
+ /**
68
+ * Attempt to reconnect with exponential backoff
69
+ */
70
+ attemptReconnect() {
71
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
72
+ console.error("Max reconnection attempts reached");
73
+ return;
74
+ }
75
+ this.reconnectAttempts++;
76
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
77
+ setTimeout(() => {
78
+ this.connect().catch(console.error);
79
+ }, delay);
80
+ }
81
+ /**
82
+ * Start ping interval to keep connection alive
83
+ */
84
+ startPing() {
85
+ this.stopPing();
86
+ this.pingInterval = setInterval(() => {
87
+ if (this.ws?.readyState === WebSocket.OPEN) {
88
+ this.send({ type: "ping" });
89
+ }
90
+ }, 30000); // Ping every 30 seconds
91
+ }
92
+ /**
93
+ * Stop ping interval
94
+ */
95
+ stopPing() {
96
+ if (this.pingInterval) {
97
+ clearInterval(this.pingInterval);
98
+ this.pingInterval = null;
99
+ }
100
+ }
101
+ /**
102
+ * Send message to server
103
+ */
104
+ send(message) {
105
+ if (this.ws?.readyState === WebSocket.OPEN) {
106
+ this.ws.send(JSON.stringify(message));
107
+ }
108
+ }
109
+ /**
110
+ * Subscribe to event type
111
+ */
112
+ on(event, handler) {
113
+ if (!this.eventHandlers.has(event)) {
114
+ this.eventHandlers.set(event, new Set());
115
+ }
116
+ this.eventHandlers.get(event).add(handler);
117
+ }
118
+ /**
119
+ * Unsubscribe from event type
120
+ */
121
+ off(event, handler) {
122
+ this.eventHandlers.get(event)?.delete(handler);
123
+ }
124
+ /**
125
+ * Handle incoming message
126
+ */
127
+ handleMessage(message) {
128
+ // Handle connection confirmation
129
+ if (message.type === "connected") {
130
+ // Connection confirmed, emit connected event
131
+ this.emit("connected", message);
132
+ return;
133
+ }
134
+ // Handle pong response
135
+ if (message.type === "pong") {
136
+ return;
137
+ }
138
+ // Emit event based on message type
139
+ if (message.type) {
140
+ this.emit(message.type, message.data || message);
141
+ }
142
+ }
143
+ /**
144
+ * Emit event to handlers
145
+ */
146
+ emit(event, data) {
147
+ const handlers = this.eventHandlers.get(event);
148
+ if (!handlers || handlers.size === 0)
149
+ return;
150
+ handlers.forEach((handler) => {
151
+ try {
152
+ handler(data);
153
+ }
154
+ catch (error) {
155
+ console.error(`Error in WebSocket handler for ${event}:`, error);
156
+ }
157
+ });
158
+ }
159
+ /**
160
+ * Disconnect WebSocket
161
+ */
162
+ disconnect() {
163
+ this.shouldReconnect = false;
164
+ this.stopPing();
165
+ if (this.ws) {
166
+ this.ws.close();
167
+ this.ws = null;
168
+ }
169
+ this.isConnecting = false;
170
+ this.reconnectAttempts = 0;
171
+ this.eventHandlers.clear();
172
+ }
173
+ /**
174
+ * Check if WebSocket is connected
175
+ */
176
+ isConnected() {
177
+ return this.ws?.readyState === WebSocket.OPEN;
178
+ }
179
+ /**
180
+ * Update access token (useful after token refresh)
181
+ */
182
+ updateToken(newToken) {
183
+ this.accessToken = newToken;
184
+ // Reconnect with new token
185
+ if (this.isConnected()) {
186
+ this.disconnect();
187
+ this.connect().catch(console.error);
188
+ }
189
+ }
190
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@recursorsdk/sdk",
3
+ "version": "1.0.0",
4
+ "description": "Recursor SDK for Node.js",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "sideEffects": false,
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json && node scripts/fix-esm-imports.js",
21
+ "clean": "rm -rf dist",
22
+ "test": "npm run build && node --test test/*.test.ts test/*.test.js",
23
+ "prepare": "npm run build",
24
+ "prepublishOnly": "npm run clean && npm run build"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^5.6.3",
31
+ "@types/node": "^20.11.30"
32
+ }
33
+ }