@jay-framework/editor-client 0.6.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/dist/index.cjs +247 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +247 -0
- package/package.json +46 -0
- package/readme.md +181 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
|
+
var __publicField = (obj, key, value) => {
|
|
5
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
6
|
+
return value;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
9
|
+
const socket_ioClient = require("socket.io-client");
|
|
10
|
+
const uuid = require("uuid");
|
|
11
|
+
const editorProtocol = require("@jay-framework/editor-protocol");
|
|
12
|
+
class ConnectionManager {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
__publicField(this, "socket", null);
|
|
15
|
+
__publicField(this, "connectionState", "disconnected");
|
|
16
|
+
__publicField(this, "portRange");
|
|
17
|
+
__publicField(this, "scanTimeout");
|
|
18
|
+
__publicField(this, "retryAttempts");
|
|
19
|
+
__publicField(this, "editorId");
|
|
20
|
+
__publicField(this, "autoReconnect");
|
|
21
|
+
__publicField(this, "reconnectDelay");
|
|
22
|
+
__publicField(this, "maxReconnectAttempts");
|
|
23
|
+
__publicField(this, "reconnectAttempts", 0);
|
|
24
|
+
__publicField(this, "reconnectTimer", null);
|
|
25
|
+
__publicField(this, "isManualDisconnect", false);
|
|
26
|
+
__publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
|
|
27
|
+
this.portRange = options.portRange || [3101, 3200];
|
|
28
|
+
this.scanTimeout = options.scanTimeout || 5e3;
|
|
29
|
+
this.retryAttempts = options.retryAttempts || 3;
|
|
30
|
+
this.editorId = options.editorId || uuid.v4();
|
|
31
|
+
this.autoReconnect = options.autoReconnect ?? true;
|
|
32
|
+
this.reconnectDelay = options.reconnectDelay ?? 1e3;
|
|
33
|
+
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
|
34
|
+
}
|
|
35
|
+
async connect() {
|
|
36
|
+
if (this.connectionState === "connected") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.isManualDisconnect = false;
|
|
40
|
+
this.reconnectAttempts = 0;
|
|
41
|
+
this.connectionState = "connecting";
|
|
42
|
+
try {
|
|
43
|
+
const serverPort = await this.discoverServer();
|
|
44
|
+
await this.establishConnection(serverPort);
|
|
45
|
+
this.connectionState = "connected";
|
|
46
|
+
} catch (error) {
|
|
47
|
+
this.connectionState = "error";
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async disconnect() {
|
|
52
|
+
this.isManualDisconnect = true;
|
|
53
|
+
if (this.reconnectTimer) {
|
|
54
|
+
clearTimeout(this.reconnectTimer);
|
|
55
|
+
this.reconnectTimer = null;
|
|
56
|
+
}
|
|
57
|
+
if (this.socket) {
|
|
58
|
+
this.socket.disconnect();
|
|
59
|
+
this.socket = null;
|
|
60
|
+
}
|
|
61
|
+
this.connectionState = "disconnected";
|
|
62
|
+
}
|
|
63
|
+
getConnectionState() {
|
|
64
|
+
return this.connectionState;
|
|
65
|
+
}
|
|
66
|
+
async sendMessage(message) {
|
|
67
|
+
if (this.connectionState !== "connected") {
|
|
68
|
+
throw new Error("Not connected to editor server");
|
|
69
|
+
}
|
|
70
|
+
if (!this.socket) {
|
|
71
|
+
throw new Error("Not connected to editor server - Socket not available");
|
|
72
|
+
}
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const protocolMessage = editorProtocol.createProtocolMessage(message);
|
|
75
|
+
this.pendingRequests.set(protocolMessage.id, { resolve, reject });
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
if (this.pendingRequests.has(protocolMessage.id)) {
|
|
78
|
+
this.pendingRequests.delete(protocolMessage.id);
|
|
79
|
+
reject(new Error("Request timeout"));
|
|
80
|
+
}
|
|
81
|
+
}, this.scanTimeout);
|
|
82
|
+
this.socket.emit("protocol-message", protocolMessage);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
onConnectionStateChange(callback) {
|
|
86
|
+
const checkState = () => {
|
|
87
|
+
const currentState = this.getConnectionState();
|
|
88
|
+
callback(currentState);
|
|
89
|
+
if (currentState === "disconnected" && this.autoReconnect && !this.isManualDisconnect) {
|
|
90
|
+
this.attemptReconnect();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
setInterval(checkState, 1e3);
|
|
94
|
+
}
|
|
95
|
+
async discoverServer() {
|
|
96
|
+
const [startPort, endPort] = this.portRange;
|
|
97
|
+
const ports = Array.from({ length: endPort - startPort + 1 }, (_, i) => startPort + i);
|
|
98
|
+
for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
|
|
99
|
+
for (const port of ports) {
|
|
100
|
+
try {
|
|
101
|
+
const response = await this.checkPort(port);
|
|
102
|
+
if (response) {
|
|
103
|
+
return port;
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (attempt < this.retryAttempts - 1) {
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`No editor server found in port range ${startPort}-${endPort}`);
|
|
113
|
+
}
|
|
114
|
+
async checkPort(port) {
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
const timeout = setTimeout(() => {
|
|
117
|
+
reject(new Error(`Port ${port} timeout`));
|
|
118
|
+
}, this.scanTimeout);
|
|
119
|
+
const controller = new AbortController();
|
|
120
|
+
fetch(`http://localhost:${port}/editor-connect?id=${this.editorId}`, {
|
|
121
|
+
signal: controller.signal
|
|
122
|
+
}).then((response) => {
|
|
123
|
+
clearTimeout(timeout);
|
|
124
|
+
if (response.ok) {
|
|
125
|
+
return response.json();
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`HTTP ${response.status}`);
|
|
128
|
+
}).then((data) => {
|
|
129
|
+
if (data.status === "init" || data.id === this.editorId) {
|
|
130
|
+
resolve(data);
|
|
131
|
+
} else {
|
|
132
|
+
resolve(null);
|
|
133
|
+
}
|
|
134
|
+
}).catch((error) => {
|
|
135
|
+
clearTimeout(timeout);
|
|
136
|
+
reject(error);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async establishConnection(port) {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
this.socket = socket_ioClient.io(`http://localhost:${port}`, {
|
|
143
|
+
timeout: this.scanTimeout,
|
|
144
|
+
forceNew: true
|
|
145
|
+
});
|
|
146
|
+
this.socket.on("connect", () => {
|
|
147
|
+
console.log("Connected to editor server");
|
|
148
|
+
this.setupSocketHandlers();
|
|
149
|
+
resolve();
|
|
150
|
+
});
|
|
151
|
+
this.socket.on("connect_error", (error) => {
|
|
152
|
+
console.error("Connection error:", error);
|
|
153
|
+
reject(error);
|
|
154
|
+
});
|
|
155
|
+
this.socket.on("disconnect", () => {
|
|
156
|
+
console.log("Disconnected from editor server");
|
|
157
|
+
this.connectionState = "disconnected";
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
setupSocketHandlers() {
|
|
162
|
+
if (!this.socket)
|
|
163
|
+
return;
|
|
164
|
+
this.socket.on("protocol-response", (response) => {
|
|
165
|
+
const pendingRequest = this.pendingRequests.get(response.id);
|
|
166
|
+
if (pendingRequest) {
|
|
167
|
+
this.pendingRequests.delete(response.id);
|
|
168
|
+
if (response.payload.success) {
|
|
169
|
+
pendingRequest.resolve(response.payload);
|
|
170
|
+
} else {
|
|
171
|
+
pendingRequest.reject(new Error(response.payload.error || "Unknown error"));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async attemptReconnect() {
|
|
177
|
+
if (this.isManualDisconnect || this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.reconnectAttempts++;
|
|
181
|
+
console.log(
|
|
182
|
+
`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`
|
|
183
|
+
);
|
|
184
|
+
try {
|
|
185
|
+
await this.connect();
|
|
186
|
+
this.reconnectAttempts = 0;
|
|
187
|
+
console.log("Reconnected successfully");
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error("Reconnection failed:", error);
|
|
190
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
191
|
+
this.reconnectTimer = setTimeout(() => {
|
|
192
|
+
this.attemptReconnect();
|
|
193
|
+
}, this.reconnectDelay * this.reconnectAttempts);
|
|
194
|
+
} else {
|
|
195
|
+
console.error("Max reconnection attempts reached");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function createConnectionManager(options) {
|
|
201
|
+
return new ConnectionManager(options);
|
|
202
|
+
}
|
|
203
|
+
class EditorClient {
|
|
204
|
+
constructor(connectionManager) {
|
|
205
|
+
__publicField(this, "connectionManager");
|
|
206
|
+
this.connectionManager = connectionManager;
|
|
207
|
+
}
|
|
208
|
+
// Connection management - delegate to ConnectionManager
|
|
209
|
+
async connect() {
|
|
210
|
+
return this.connectionManager.connect();
|
|
211
|
+
}
|
|
212
|
+
async disconnect() {
|
|
213
|
+
return this.connectionManager.disconnect();
|
|
214
|
+
}
|
|
215
|
+
getConnectionState() {
|
|
216
|
+
return this.connectionManager.getConnectionState();
|
|
217
|
+
}
|
|
218
|
+
onConnectionStateChange(callback) {
|
|
219
|
+
return this.connectionManager.onConnectionStateChange(callback);
|
|
220
|
+
}
|
|
221
|
+
// EditorProtocol implementation - delegate to ConnectionManager
|
|
222
|
+
async publish(params) {
|
|
223
|
+
return this.connectionManager.sendMessage(params);
|
|
224
|
+
}
|
|
225
|
+
async saveImage(params) {
|
|
226
|
+
return this.connectionManager.sendMessage(params);
|
|
227
|
+
}
|
|
228
|
+
async hasImage(params) {
|
|
229
|
+
return this.connectionManager.sendMessage(params);
|
|
230
|
+
}
|
|
231
|
+
// Get access to the underlying connection manager if needed
|
|
232
|
+
getConnectionManager() {
|
|
233
|
+
return this.connectionManager;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function createEditorClient(options) {
|
|
237
|
+
const connectionManager = new ConnectionManager(options);
|
|
238
|
+
return new EditorClient(connectionManager);
|
|
239
|
+
}
|
|
240
|
+
function createEditorClientWithConnectionManager(connectionManager) {
|
|
241
|
+
return new EditorClient(connectionManager);
|
|
242
|
+
}
|
|
243
|
+
exports.ConnectionManager = ConnectionManager;
|
|
244
|
+
exports.EditorClient = EditorClient;
|
|
245
|
+
exports.createConnectionManager = createConnectionManager;
|
|
246
|
+
exports.createEditorClient = createEditorClient;
|
|
247
|
+
exports.createEditorClientWithConnectionManager = createEditorClientWithConnectionManager;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as _jay_framework_editor_protocol from '@jay-framework/editor-protocol';
|
|
2
|
+
import { ConnectionState, PublishMessage, SaveImageMessage, HasImageMessage, PublishResponse, SaveImageResponse, HasImageResponse, EditorProtocol } from '@jay-framework/editor-protocol';
|
|
3
|
+
|
|
4
|
+
interface ConnectionManagerOptions {
|
|
5
|
+
portRange?: [number, number];
|
|
6
|
+
scanTimeout?: number;
|
|
7
|
+
retryAttempts?: number;
|
|
8
|
+
editorId?: string;
|
|
9
|
+
autoReconnect?: boolean;
|
|
10
|
+
reconnectDelay?: number;
|
|
11
|
+
maxReconnectAttempts?: number;
|
|
12
|
+
}
|
|
13
|
+
declare class ConnectionManager {
|
|
14
|
+
private socket;
|
|
15
|
+
private connectionState;
|
|
16
|
+
private portRange;
|
|
17
|
+
private scanTimeout;
|
|
18
|
+
private retryAttempts;
|
|
19
|
+
private editorId;
|
|
20
|
+
private autoReconnect;
|
|
21
|
+
private reconnectDelay;
|
|
22
|
+
private maxReconnectAttempts;
|
|
23
|
+
private reconnectAttempts;
|
|
24
|
+
private reconnectTimer;
|
|
25
|
+
private isManualDisconnect;
|
|
26
|
+
private pendingRequests;
|
|
27
|
+
constructor(options?: ConnectionManagerOptions);
|
|
28
|
+
connect(): Promise<void>;
|
|
29
|
+
disconnect(): Promise<void>;
|
|
30
|
+
getConnectionState(): ConnectionState;
|
|
31
|
+
sendMessage<T extends PublishMessage | SaveImageMessage | HasImageMessage>(message: T): Promise<T extends PublishMessage ? PublishResponse : T extends SaveImageMessage ? SaveImageResponse : T extends HasImageMessage ? HasImageResponse : never>;
|
|
32
|
+
onConnectionStateChange(callback: (state: ConnectionState) => void): void;
|
|
33
|
+
private discoverServer;
|
|
34
|
+
private checkPort;
|
|
35
|
+
private establishConnection;
|
|
36
|
+
private setupSocketHandlers;
|
|
37
|
+
private attemptReconnect;
|
|
38
|
+
}
|
|
39
|
+
declare function createConnectionManager(options?: ConnectionManagerOptions): ConnectionManager;
|
|
40
|
+
|
|
41
|
+
interface EditorClientOptions extends ConnectionManagerOptions {
|
|
42
|
+
}
|
|
43
|
+
declare class EditorClient implements EditorProtocol {
|
|
44
|
+
private connectionManager;
|
|
45
|
+
constructor(connectionManager: ConnectionManager);
|
|
46
|
+
connect(): Promise<void>;
|
|
47
|
+
disconnect(): Promise<void>;
|
|
48
|
+
getConnectionState(): _jay_framework_editor_protocol.ConnectionState;
|
|
49
|
+
onConnectionStateChange(callback: (state: any) => void): void;
|
|
50
|
+
publish(params: PublishMessage): Promise<PublishResponse>;
|
|
51
|
+
saveImage(params: SaveImageMessage): Promise<SaveImageResponse>;
|
|
52
|
+
hasImage(params: HasImageMessage): Promise<HasImageResponse>;
|
|
53
|
+
getConnectionManager(): ConnectionManager;
|
|
54
|
+
}
|
|
55
|
+
declare function createEditorClient(options?: EditorClientOptions): EditorClient;
|
|
56
|
+
declare function createEditorClientWithConnectionManager(connectionManager: ConnectionManager): EditorClient;
|
|
57
|
+
|
|
58
|
+
export { ConnectionManager, type ConnectionManagerOptions, EditorClient, type EditorClientOptions, createConnectionManager, createEditorClient, createEditorClientWithConnectionManager };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => {
|
|
4
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
|
+
return value;
|
|
6
|
+
};
|
|
7
|
+
import { io } from "socket.io-client";
|
|
8
|
+
import { v4 } from "uuid";
|
|
9
|
+
import { createProtocolMessage } from "@jay-framework/editor-protocol";
|
|
10
|
+
class ConnectionManager {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
__publicField(this, "socket", null);
|
|
13
|
+
__publicField(this, "connectionState", "disconnected");
|
|
14
|
+
__publicField(this, "portRange");
|
|
15
|
+
__publicField(this, "scanTimeout");
|
|
16
|
+
__publicField(this, "retryAttempts");
|
|
17
|
+
__publicField(this, "editorId");
|
|
18
|
+
__publicField(this, "autoReconnect");
|
|
19
|
+
__publicField(this, "reconnectDelay");
|
|
20
|
+
__publicField(this, "maxReconnectAttempts");
|
|
21
|
+
__publicField(this, "reconnectAttempts", 0);
|
|
22
|
+
__publicField(this, "reconnectTimer", null);
|
|
23
|
+
__publicField(this, "isManualDisconnect", false);
|
|
24
|
+
__publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
|
|
25
|
+
this.portRange = options.portRange || [3101, 3200];
|
|
26
|
+
this.scanTimeout = options.scanTimeout || 5e3;
|
|
27
|
+
this.retryAttempts = options.retryAttempts || 3;
|
|
28
|
+
this.editorId = options.editorId || v4();
|
|
29
|
+
this.autoReconnect = options.autoReconnect ?? true;
|
|
30
|
+
this.reconnectDelay = options.reconnectDelay ?? 1e3;
|
|
31
|
+
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
|
32
|
+
}
|
|
33
|
+
async connect() {
|
|
34
|
+
if (this.connectionState === "connected") {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.isManualDisconnect = false;
|
|
38
|
+
this.reconnectAttempts = 0;
|
|
39
|
+
this.connectionState = "connecting";
|
|
40
|
+
try {
|
|
41
|
+
const serverPort = await this.discoverServer();
|
|
42
|
+
await this.establishConnection(serverPort);
|
|
43
|
+
this.connectionState = "connected";
|
|
44
|
+
} catch (error) {
|
|
45
|
+
this.connectionState = "error";
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async disconnect() {
|
|
50
|
+
this.isManualDisconnect = true;
|
|
51
|
+
if (this.reconnectTimer) {
|
|
52
|
+
clearTimeout(this.reconnectTimer);
|
|
53
|
+
this.reconnectTimer = null;
|
|
54
|
+
}
|
|
55
|
+
if (this.socket) {
|
|
56
|
+
this.socket.disconnect();
|
|
57
|
+
this.socket = null;
|
|
58
|
+
}
|
|
59
|
+
this.connectionState = "disconnected";
|
|
60
|
+
}
|
|
61
|
+
getConnectionState() {
|
|
62
|
+
return this.connectionState;
|
|
63
|
+
}
|
|
64
|
+
async sendMessage(message) {
|
|
65
|
+
if (this.connectionState !== "connected") {
|
|
66
|
+
throw new Error("Not connected to editor server");
|
|
67
|
+
}
|
|
68
|
+
if (!this.socket) {
|
|
69
|
+
throw new Error("Not connected to editor server - Socket not available");
|
|
70
|
+
}
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const protocolMessage = createProtocolMessage(message);
|
|
73
|
+
this.pendingRequests.set(protocolMessage.id, { resolve, reject });
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
if (this.pendingRequests.has(protocolMessage.id)) {
|
|
76
|
+
this.pendingRequests.delete(protocolMessage.id);
|
|
77
|
+
reject(new Error("Request timeout"));
|
|
78
|
+
}
|
|
79
|
+
}, this.scanTimeout);
|
|
80
|
+
this.socket.emit("protocol-message", protocolMessage);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
onConnectionStateChange(callback) {
|
|
84
|
+
const checkState = () => {
|
|
85
|
+
const currentState = this.getConnectionState();
|
|
86
|
+
callback(currentState);
|
|
87
|
+
if (currentState === "disconnected" && this.autoReconnect && !this.isManualDisconnect) {
|
|
88
|
+
this.attemptReconnect();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
setInterval(checkState, 1e3);
|
|
92
|
+
}
|
|
93
|
+
async discoverServer() {
|
|
94
|
+
const [startPort, endPort] = this.portRange;
|
|
95
|
+
const ports = Array.from({ length: endPort - startPort + 1 }, (_, i) => startPort + i);
|
|
96
|
+
for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
|
|
97
|
+
for (const port of ports) {
|
|
98
|
+
try {
|
|
99
|
+
const response = await this.checkPort(port);
|
|
100
|
+
if (response) {
|
|
101
|
+
return port;
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (attempt < this.retryAttempts - 1) {
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw new Error(`No editor server found in port range ${startPort}-${endPort}`);
|
|
111
|
+
}
|
|
112
|
+
async checkPort(port) {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const timeout = setTimeout(() => {
|
|
115
|
+
reject(new Error(`Port ${port} timeout`));
|
|
116
|
+
}, this.scanTimeout);
|
|
117
|
+
const controller = new AbortController();
|
|
118
|
+
fetch(`http://localhost:${port}/editor-connect?id=${this.editorId}`, {
|
|
119
|
+
signal: controller.signal
|
|
120
|
+
}).then((response) => {
|
|
121
|
+
clearTimeout(timeout);
|
|
122
|
+
if (response.ok) {
|
|
123
|
+
return response.json();
|
|
124
|
+
}
|
|
125
|
+
throw new Error(`HTTP ${response.status}`);
|
|
126
|
+
}).then((data) => {
|
|
127
|
+
if (data.status === "init" || data.id === this.editorId) {
|
|
128
|
+
resolve(data);
|
|
129
|
+
} else {
|
|
130
|
+
resolve(null);
|
|
131
|
+
}
|
|
132
|
+
}).catch((error) => {
|
|
133
|
+
clearTimeout(timeout);
|
|
134
|
+
reject(error);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async establishConnection(port) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
this.socket = io(`http://localhost:${port}`, {
|
|
141
|
+
timeout: this.scanTimeout,
|
|
142
|
+
forceNew: true
|
|
143
|
+
});
|
|
144
|
+
this.socket.on("connect", () => {
|
|
145
|
+
console.log("Connected to editor server");
|
|
146
|
+
this.setupSocketHandlers();
|
|
147
|
+
resolve();
|
|
148
|
+
});
|
|
149
|
+
this.socket.on("connect_error", (error) => {
|
|
150
|
+
console.error("Connection error:", error);
|
|
151
|
+
reject(error);
|
|
152
|
+
});
|
|
153
|
+
this.socket.on("disconnect", () => {
|
|
154
|
+
console.log("Disconnected from editor server");
|
|
155
|
+
this.connectionState = "disconnected";
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
setupSocketHandlers() {
|
|
160
|
+
if (!this.socket)
|
|
161
|
+
return;
|
|
162
|
+
this.socket.on("protocol-response", (response) => {
|
|
163
|
+
const pendingRequest = this.pendingRequests.get(response.id);
|
|
164
|
+
if (pendingRequest) {
|
|
165
|
+
this.pendingRequests.delete(response.id);
|
|
166
|
+
if (response.payload.success) {
|
|
167
|
+
pendingRequest.resolve(response.payload);
|
|
168
|
+
} else {
|
|
169
|
+
pendingRequest.reject(new Error(response.payload.error || "Unknown error"));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async attemptReconnect() {
|
|
175
|
+
if (this.isManualDisconnect || this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this.reconnectAttempts++;
|
|
179
|
+
console.log(
|
|
180
|
+
`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`
|
|
181
|
+
);
|
|
182
|
+
try {
|
|
183
|
+
await this.connect();
|
|
184
|
+
this.reconnectAttempts = 0;
|
|
185
|
+
console.log("Reconnected successfully");
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error("Reconnection failed:", error);
|
|
188
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
189
|
+
this.reconnectTimer = setTimeout(() => {
|
|
190
|
+
this.attemptReconnect();
|
|
191
|
+
}, this.reconnectDelay * this.reconnectAttempts);
|
|
192
|
+
} else {
|
|
193
|
+
console.error("Max reconnection attempts reached");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function createConnectionManager(options) {
|
|
199
|
+
return new ConnectionManager(options);
|
|
200
|
+
}
|
|
201
|
+
class EditorClient {
|
|
202
|
+
constructor(connectionManager) {
|
|
203
|
+
__publicField(this, "connectionManager");
|
|
204
|
+
this.connectionManager = connectionManager;
|
|
205
|
+
}
|
|
206
|
+
// Connection management - delegate to ConnectionManager
|
|
207
|
+
async connect() {
|
|
208
|
+
return this.connectionManager.connect();
|
|
209
|
+
}
|
|
210
|
+
async disconnect() {
|
|
211
|
+
return this.connectionManager.disconnect();
|
|
212
|
+
}
|
|
213
|
+
getConnectionState() {
|
|
214
|
+
return this.connectionManager.getConnectionState();
|
|
215
|
+
}
|
|
216
|
+
onConnectionStateChange(callback) {
|
|
217
|
+
return this.connectionManager.onConnectionStateChange(callback);
|
|
218
|
+
}
|
|
219
|
+
// EditorProtocol implementation - delegate to ConnectionManager
|
|
220
|
+
async publish(params) {
|
|
221
|
+
return this.connectionManager.sendMessage(params);
|
|
222
|
+
}
|
|
223
|
+
async saveImage(params) {
|
|
224
|
+
return this.connectionManager.sendMessage(params);
|
|
225
|
+
}
|
|
226
|
+
async hasImage(params) {
|
|
227
|
+
return this.connectionManager.sendMessage(params);
|
|
228
|
+
}
|
|
229
|
+
// Get access to the underlying connection manager if needed
|
|
230
|
+
getConnectionManager() {
|
|
231
|
+
return this.connectionManager;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function createEditorClient(options) {
|
|
235
|
+
const connectionManager = new ConnectionManager(options);
|
|
236
|
+
return new EditorClient(connectionManager);
|
|
237
|
+
}
|
|
238
|
+
function createEditorClientWithConnectionManager(connectionManager) {
|
|
239
|
+
return new EditorClient(connectionManager);
|
|
240
|
+
}
|
|
241
|
+
export {
|
|
242
|
+
ConnectionManager,
|
|
243
|
+
EditorClient,
|
|
244
|
+
createConnectionManager,
|
|
245
|
+
createEditorClient,
|
|
246
|
+
createEditorClientWithConnectionManager
|
|
247
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jay-framework/editor-client",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"readme.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "npm run build:js && npm run build:types",
|
|
20
|
+
"build:watch": "npm run build:js -- --watch & npm run build:types -- --watch",
|
|
21
|
+
"build:js": "vite build",
|
|
22
|
+
"build:types": "tsup lib/index.ts --dts-only --format esm",
|
|
23
|
+
"build:check-types": "tsc",
|
|
24
|
+
"clean": "rimraf dist",
|
|
25
|
+
"confirm": "npm run clean && npm run build && npm run build:check-types && npm run test",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@jay-framework/editor-protocol": "^0.6.0",
|
|
31
|
+
"get-port": "^7.0.0",
|
|
32
|
+
"socket.io-client": "^4.7.4",
|
|
33
|
+
"uuid": "^9.0.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@jay-framework/dev-environment": "^0.6.0",
|
|
37
|
+
"@jay-framework/jay-cli": "^0.6.0",
|
|
38
|
+
"@types/node": "^22.15.21",
|
|
39
|
+
"@types/uuid": "^9.0.7",
|
|
40
|
+
"rimraf": "^5.0.5",
|
|
41
|
+
"tsup": "^8.0.1",
|
|
42
|
+
"typescript": "^5.3.3",
|
|
43
|
+
"vite": "^5.0.11",
|
|
44
|
+
"vitest": "^1.2.1"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# @jay-framework/editor-client
|
|
2
|
+
|
|
3
|
+
Socket.io client implementation for editor applications to connect to Jay dev servers.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package provides a Socket.io client that can be used by editor applications to connect to Jay dev servers and perform real-time operations. It includes:
|
|
8
|
+
|
|
9
|
+
- Automatic port discovery and server connection
|
|
10
|
+
- Socket.io client with automatic reconnection
|
|
11
|
+
- Protocol message handling for publish, saveImage, and hasImage operations
|
|
12
|
+
- Connection state management
|
|
13
|
+
- Type-safe protocol implementation with wrapper message structure
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Basic Usage
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createEditorClient } from '@jay-framework/editor-client';
|
|
21
|
+
|
|
22
|
+
// Create the editor client
|
|
23
|
+
const client = createEditorClient({
|
|
24
|
+
portRange: [3101, 3200],
|
|
25
|
+
scanTimeout: 5000,
|
|
26
|
+
retryAttempts: 3,
|
|
27
|
+
editorId: 'my-editor-123',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Connect to the dev server
|
|
31
|
+
await client.connect();
|
|
32
|
+
|
|
33
|
+
// Publish a jay-html file
|
|
34
|
+
const result = await client.publish({
|
|
35
|
+
type: 'publish',
|
|
36
|
+
pages: [
|
|
37
|
+
{
|
|
38
|
+
route: '/pages',
|
|
39
|
+
jayHtml: '<div>Hello World</div>',
|
|
40
|
+
name: 'home',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
console.log('Published:', result.status[0].filePath);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Advanced Usage with Custom Connection Manager
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import {
|
|
52
|
+
createEditorClientWithConnectionManager,
|
|
53
|
+
createConnectionManager,
|
|
54
|
+
} from '@jay-framework/editor-client';
|
|
55
|
+
|
|
56
|
+
// Create a custom connection manager
|
|
57
|
+
const connectionManager = createConnectionManager({
|
|
58
|
+
portRange: [3101, 3200],
|
|
59
|
+
autoReconnect: true,
|
|
60
|
+
reconnectDelay: 1000,
|
|
61
|
+
maxReconnectAttempts: 5,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Create editor client with the connection manager
|
|
65
|
+
const client = createEditorClientWithConnectionManager(connectionManager);
|
|
66
|
+
|
|
67
|
+
// Connect and handle state changes
|
|
68
|
+
await client.connect();
|
|
69
|
+
|
|
70
|
+
client.onConnectionStateChange((state) => {
|
|
71
|
+
console.log('Connection state:', state);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Use protocol methods
|
|
75
|
+
const imageResult = await client.saveImage({
|
|
76
|
+
type: 'saveImage',
|
|
77
|
+
imageId: 'my-image',
|
|
78
|
+
imageData:
|
|
79
|
+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log('Image saved:', imageResult.imageUrl);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Features
|
|
86
|
+
|
|
87
|
+
### Port Discovery
|
|
88
|
+
|
|
89
|
+
- Automatically scans port range to find available dev servers
|
|
90
|
+
- Supports both "init" mode and "configured" mode servers
|
|
91
|
+
- Configurable scan timeout and retry attempts
|
|
92
|
+
|
|
93
|
+
### Protocol Support
|
|
94
|
+
|
|
95
|
+
- **Publish**: Send jay-html files to the dev server
|
|
96
|
+
- **Save Image**: Upload base64 image data to server assets
|
|
97
|
+
- **Has Image**: Check if an image already exists on the server
|
|
98
|
+
|
|
99
|
+
### Connection Management
|
|
100
|
+
|
|
101
|
+
- Automatic reconnection with exponential backoff
|
|
102
|
+
- Connection state monitoring
|
|
103
|
+
- Request timeout handling
|
|
104
|
+
- Error handling and recovery
|
|
105
|
+
|
|
106
|
+
### Type Safety
|
|
107
|
+
|
|
108
|
+
- Full TypeScript support with wrapper message structure
|
|
109
|
+
- Protocol interface compliance with `id`, `timestamp`, and `payload` fields
|
|
110
|
+
- Type-safe request/response handling
|
|
111
|
+
|
|
112
|
+
## Architecture
|
|
113
|
+
|
|
114
|
+
The package follows a clean separation of concerns:
|
|
115
|
+
|
|
116
|
+
- **EditorClient**: High-level API that implements the `EditorProtocol` interface
|
|
117
|
+
- **ConnectionManager**: Handles all connection logic, port discovery, and message transport
|
|
118
|
+
|
|
119
|
+
### Protocol Message Structure
|
|
120
|
+
|
|
121
|
+
All messages use a wrapper structure:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
interface ProtocolMessage {
|
|
125
|
+
id: string;
|
|
126
|
+
timestamp: number;
|
|
127
|
+
payload: {
|
|
128
|
+
type: 'publish' | 'saveImage' | 'hasImage';
|
|
129
|
+
// ... message-specific fields
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Configuration Options
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
interface ConnectionManagerOptions {
|
|
138
|
+
portRange?: [number, number]; // Default: [3101, 3200]
|
|
139
|
+
scanTimeout?: number; // Default: 5000ms
|
|
140
|
+
retryAttempts?: number; // Default: 3
|
|
141
|
+
editorId?: string; // Auto-generated UUID if not provided
|
|
142
|
+
autoReconnect?: boolean; // Default: true
|
|
143
|
+
reconnectDelay?: number; // Default: 1000ms
|
|
144
|
+
maxReconnectAttempts?: number; // Default: 5
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Testing
|
|
149
|
+
|
|
150
|
+
Comprehensive test suite including:
|
|
151
|
+
|
|
152
|
+
- Unit tests for all components
|
|
153
|
+
- End-to-end tests with real Socket.io servers
|
|
154
|
+
- Multiple server and client scenarios
|
|
155
|
+
- Connection state and error handling tests
|
|
156
|
+
- Protocol message validation
|
|
157
|
+
|
|
158
|
+
## Integration with Editor Applications
|
|
159
|
+
|
|
160
|
+
This package is designed to be used by any editor application that needs to communicate with Jay dev servers, such as:
|
|
161
|
+
|
|
162
|
+
- Figma plugins
|
|
163
|
+
- Web-based editors
|
|
164
|
+
- Desktop applications
|
|
165
|
+
- Browser extensions
|
|
166
|
+
|
|
167
|
+
interface EditorClientOptions extends ConnectionManagerOptions {
|
|
168
|
+
// Additional editor-specific options can be added here
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Integration with Editor Applications
|
|
174
|
+
|
|
175
|
+
This package is designed to be used by any editor application that needs to communicate with Jay dev servers, such as:
|
|
176
|
+
|
|
177
|
+
- Figma plugins
|
|
178
|
+
- Web-based editors
|
|
179
|
+
- Desktop applications
|
|
180
|
+
- Browser extensions
|
|
181
|
+
```
|