@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 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;
@@ -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
+ ```