@scriptdb/browser-client 1.1.0 → 1.1.2
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 +1 -1
- package/dist/index.d.mts +141 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +162 -36
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +314 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +2 -5
package/README.md
CHANGED
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-compatible ScriptDB Client
|
|
3
|
+
*
|
|
4
|
+
* This is a lightweight WebSocket client that connects to the ScriptDB WebSocket proxy.
|
|
5
|
+
* The proxy handles all communication with the ScriptDB server using @scriptdb/client.
|
|
6
|
+
*/
|
|
7
|
+
type Action = 'script-code' | 'save-db' | 'remove-db' | 'rename-db' | 'login' | 'logout' | 'get-info' | 'list-dbs' | 'create-db' | 'update-db' | 'get-db' | 'shell-command';
|
|
8
|
+
interface Logger {
|
|
9
|
+
debug?: (...args: any[]) => void;
|
|
10
|
+
info?: (...args: any[]) => void;
|
|
11
|
+
warn?: (...args: any[]) => void;
|
|
12
|
+
error?: (...args: any[]) => void;
|
|
13
|
+
}
|
|
14
|
+
interface ClientOptions {
|
|
15
|
+
host?: string;
|
|
16
|
+
port?: number;
|
|
17
|
+
username?: string;
|
|
18
|
+
password?: string;
|
|
19
|
+
logger?: Logger;
|
|
20
|
+
requestTimeout?: number;
|
|
21
|
+
secure?: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface Message {
|
|
24
|
+
id?: number;
|
|
25
|
+
action?: Action;
|
|
26
|
+
message?: string;
|
|
27
|
+
data?: any;
|
|
28
|
+
}
|
|
29
|
+
declare class BrowserClient {
|
|
30
|
+
private logger;
|
|
31
|
+
private host;
|
|
32
|
+
private port;
|
|
33
|
+
private username;
|
|
34
|
+
private password;
|
|
35
|
+
private requestTimeout;
|
|
36
|
+
private secure;
|
|
37
|
+
private ws;
|
|
38
|
+
private _connected;
|
|
39
|
+
private _nextId;
|
|
40
|
+
private _pending;
|
|
41
|
+
constructor(options: ClientOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Check if connected
|
|
44
|
+
*/
|
|
45
|
+
get connected(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Connect to WebSocket proxy and authenticate
|
|
48
|
+
*/
|
|
49
|
+
connect(): Promise<BrowserClient>;
|
|
50
|
+
/**
|
|
51
|
+
* Setup message listeners
|
|
52
|
+
*/
|
|
53
|
+
private _setupListeners;
|
|
54
|
+
/**
|
|
55
|
+
* Handle incoming message
|
|
56
|
+
*/
|
|
57
|
+
private _handleMessage;
|
|
58
|
+
/**
|
|
59
|
+
* Send request to proxy (internal helper)
|
|
60
|
+
*/
|
|
61
|
+
private _sendRequest;
|
|
62
|
+
/**
|
|
63
|
+
* Cleanup on disconnect
|
|
64
|
+
*/
|
|
65
|
+
private _cleanup;
|
|
66
|
+
/**
|
|
67
|
+
* Close connection
|
|
68
|
+
*/
|
|
69
|
+
close(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Disconnect (alias for close)
|
|
72
|
+
*/
|
|
73
|
+
disconnect(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Send request helper (for public API methods)
|
|
76
|
+
*/
|
|
77
|
+
sendRequest(action: Action, data?: any): Promise<any>;
|
|
78
|
+
/**
|
|
79
|
+
* Execute a command. Supports concurrent requests via request id mapping.
|
|
80
|
+
* Returns a Promise resolved with response.data or rejected on ERROR/timeout.
|
|
81
|
+
*/
|
|
82
|
+
execute(message: Message): Promise<any>;
|
|
83
|
+
/**
|
|
84
|
+
* Login with username and password
|
|
85
|
+
*/
|
|
86
|
+
login(username: string, password: string): Promise<any>;
|
|
87
|
+
/**
|
|
88
|
+
* Logout from server
|
|
89
|
+
*/
|
|
90
|
+
logout(): Promise<any>;
|
|
91
|
+
/**
|
|
92
|
+
* List all databases
|
|
93
|
+
*/
|
|
94
|
+
listDatabases(): Promise<any>;
|
|
95
|
+
/**
|
|
96
|
+
* Create a new database
|
|
97
|
+
*/
|
|
98
|
+
createDatabase(name: string): Promise<any>;
|
|
99
|
+
/**
|
|
100
|
+
* Remove a database
|
|
101
|
+
*/
|
|
102
|
+
removeDatabase(name: string): Promise<any>;
|
|
103
|
+
/**
|
|
104
|
+
* Rename a database
|
|
105
|
+
*/
|
|
106
|
+
renameDatabase(oldName: string, newName: string): Promise<any>;
|
|
107
|
+
/**
|
|
108
|
+
* Execute code in a database
|
|
109
|
+
*/
|
|
110
|
+
run(code: string | Function, databaseName: string): Promise<any>;
|
|
111
|
+
/**
|
|
112
|
+
* Save a database to disk
|
|
113
|
+
*/
|
|
114
|
+
saveDatabase(databaseName: string): Promise<any>;
|
|
115
|
+
/**
|
|
116
|
+
* Update database metadata
|
|
117
|
+
*/
|
|
118
|
+
updateDatabase(databaseName: string, data: any): Promise<any>;
|
|
119
|
+
/**
|
|
120
|
+
* Get database content
|
|
121
|
+
*/
|
|
122
|
+
getDatabase(name: string): Promise<{
|
|
123
|
+
success: boolean;
|
|
124
|
+
databaseName: string;
|
|
125
|
+
content: string;
|
|
126
|
+
path?: string;
|
|
127
|
+
}>;
|
|
128
|
+
/**
|
|
129
|
+
* Get server information
|
|
130
|
+
*/
|
|
131
|
+
getInfo(): Promise<any>;
|
|
132
|
+
/**
|
|
133
|
+
* Execute shell command on server
|
|
134
|
+
*/
|
|
135
|
+
executeShell(command: string): Promise<{
|
|
136
|
+
stdout: string;
|
|
137
|
+
stderr: string;
|
|
138
|
+
}>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { BrowserClient, BrowserClient as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-compatible ScriptDB Client
|
|
3
|
+
*
|
|
4
|
+
* This is a lightweight WebSocket client that connects to the ScriptDB WebSocket proxy.
|
|
5
|
+
* The proxy handles all communication with the ScriptDB server using @scriptdb/client.
|
|
6
|
+
*/
|
|
7
|
+
type Action = 'script-code' | 'save-db' | 'remove-db' | 'rename-db' | 'login' | 'logout' | 'get-info' | 'list-dbs' | 'create-db' | 'update-db' | 'get-db' | 'shell-command';
|
|
8
|
+
interface Logger {
|
|
9
|
+
debug?: (...args: any[]) => void;
|
|
10
|
+
info?: (...args: any[]) => void;
|
|
11
|
+
warn?: (...args: any[]) => void;
|
|
12
|
+
error?: (...args: any[]) => void;
|
|
13
|
+
}
|
|
14
|
+
interface ClientOptions {
|
|
15
|
+
host?: string;
|
|
16
|
+
port?: number;
|
|
17
|
+
username?: string;
|
|
18
|
+
password?: string;
|
|
19
|
+
logger?: Logger;
|
|
20
|
+
requestTimeout?: number;
|
|
21
|
+
secure?: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface Message {
|
|
24
|
+
id?: number;
|
|
25
|
+
action?: Action;
|
|
26
|
+
message?: string;
|
|
27
|
+
data?: any;
|
|
28
|
+
}
|
|
29
|
+
declare class BrowserClient {
|
|
30
|
+
private logger;
|
|
31
|
+
private host;
|
|
32
|
+
private port;
|
|
33
|
+
private username;
|
|
34
|
+
private password;
|
|
35
|
+
private requestTimeout;
|
|
36
|
+
private secure;
|
|
37
|
+
private ws;
|
|
38
|
+
private _connected;
|
|
39
|
+
private _nextId;
|
|
40
|
+
private _pending;
|
|
41
|
+
constructor(options: ClientOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Check if connected
|
|
44
|
+
*/
|
|
45
|
+
get connected(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Connect to WebSocket proxy and authenticate
|
|
48
|
+
*/
|
|
49
|
+
connect(): Promise<BrowserClient>;
|
|
50
|
+
/**
|
|
51
|
+
* Setup message listeners
|
|
52
|
+
*/
|
|
53
|
+
private _setupListeners;
|
|
54
|
+
/**
|
|
55
|
+
* Handle incoming message
|
|
56
|
+
*/
|
|
57
|
+
private _handleMessage;
|
|
58
|
+
/**
|
|
59
|
+
* Send request to proxy (internal helper)
|
|
60
|
+
*/
|
|
61
|
+
private _sendRequest;
|
|
62
|
+
/**
|
|
63
|
+
* Cleanup on disconnect
|
|
64
|
+
*/
|
|
65
|
+
private _cleanup;
|
|
66
|
+
/**
|
|
67
|
+
* Close connection
|
|
68
|
+
*/
|
|
69
|
+
close(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Disconnect (alias for close)
|
|
72
|
+
*/
|
|
73
|
+
disconnect(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Send request helper (for public API methods)
|
|
76
|
+
*/
|
|
77
|
+
sendRequest(action: Action, data?: any): Promise<any>;
|
|
78
|
+
/**
|
|
79
|
+
* Execute a command. Supports concurrent requests via request id mapping.
|
|
80
|
+
* Returns a Promise resolved with response.data or rejected on ERROR/timeout.
|
|
81
|
+
*/
|
|
82
|
+
execute(message: Message): Promise<any>;
|
|
83
|
+
/**
|
|
84
|
+
* Login with username and password
|
|
85
|
+
*/
|
|
86
|
+
login(username: string, password: string): Promise<any>;
|
|
87
|
+
/**
|
|
88
|
+
* Logout from server
|
|
89
|
+
*/
|
|
90
|
+
logout(): Promise<any>;
|
|
91
|
+
/**
|
|
92
|
+
* List all databases
|
|
93
|
+
*/
|
|
94
|
+
listDatabases(): Promise<any>;
|
|
95
|
+
/**
|
|
96
|
+
* Create a new database
|
|
97
|
+
*/
|
|
98
|
+
createDatabase(name: string): Promise<any>;
|
|
99
|
+
/**
|
|
100
|
+
* Remove a database
|
|
101
|
+
*/
|
|
102
|
+
removeDatabase(name: string): Promise<any>;
|
|
103
|
+
/**
|
|
104
|
+
* Rename a database
|
|
105
|
+
*/
|
|
106
|
+
renameDatabase(oldName: string, newName: string): Promise<any>;
|
|
107
|
+
/**
|
|
108
|
+
* Execute code in a database
|
|
109
|
+
*/
|
|
110
|
+
run(code: string | Function, databaseName: string): Promise<any>;
|
|
111
|
+
/**
|
|
112
|
+
* Save a database to disk
|
|
113
|
+
*/
|
|
114
|
+
saveDatabase(databaseName: string): Promise<any>;
|
|
115
|
+
/**
|
|
116
|
+
* Update database metadata
|
|
117
|
+
*/
|
|
118
|
+
updateDatabase(databaseName: string, data: any): Promise<any>;
|
|
119
|
+
/**
|
|
120
|
+
* Get database content
|
|
121
|
+
*/
|
|
122
|
+
getDatabase(name: string): Promise<{
|
|
123
|
+
success: boolean;
|
|
124
|
+
databaseName: string;
|
|
125
|
+
content: string;
|
|
126
|
+
path?: string;
|
|
127
|
+
}>;
|
|
128
|
+
/**
|
|
129
|
+
* Get server information
|
|
130
|
+
*/
|
|
131
|
+
getInfo(): Promise<any>;
|
|
132
|
+
/**
|
|
133
|
+
* Execute shell command on server
|
|
134
|
+
*/
|
|
135
|
+
executeShell(command: string): Promise<{
|
|
136
|
+
stdout: string;
|
|
137
|
+
stderr: string;
|
|
138
|
+
}>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { BrowserClient, BrowserClient as default };
|
package/dist/index.js
CHANGED
|
@@ -1,35 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
1
20
|
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BrowserClient: () => BrowserClient,
|
|
24
|
+
default: () => index_default
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
2
27
|
var noopLogger = {
|
|
3
|
-
debug: () => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
28
|
+
debug: () => {
|
|
29
|
+
},
|
|
30
|
+
info: () => {
|
|
31
|
+
},
|
|
32
|
+
warn: () => {
|
|
33
|
+
},
|
|
34
|
+
error: () => {
|
|
35
|
+
}
|
|
7
36
|
};
|
|
8
|
-
|
|
9
|
-
class BrowserClient {
|
|
10
|
-
logger;
|
|
11
|
-
host;
|
|
12
|
-
port;
|
|
13
|
-
username;
|
|
14
|
-
password;
|
|
15
|
-
requestTimeout;
|
|
16
|
-
secure;
|
|
17
|
-
ws = null;
|
|
18
|
-
_connected = false;
|
|
19
|
-
_nextId = 1;
|
|
20
|
-
_pending = new Map;
|
|
37
|
+
var BrowserClient = class {
|
|
21
38
|
constructor(options) {
|
|
39
|
+
this.ws = null;
|
|
40
|
+
this._connected = false;
|
|
41
|
+
this._nextId = 1;
|
|
42
|
+
this._pending = /* @__PURE__ */ new Map();
|
|
22
43
|
this.logger = options.logger || noopLogger;
|
|
23
44
|
this.host = options.host || "localhost";
|
|
24
45
|
this.port = options.port || 1234;
|
|
25
46
|
this.username = options.username || null;
|
|
26
47
|
this.password = options.password || null;
|
|
27
|
-
this.requestTimeout = Number.isFinite(options.requestTimeout) ? options.requestTimeout :
|
|
28
|
-
this.secure = options.secure !==
|
|
48
|
+
this.requestTimeout = Number.isFinite(options.requestTimeout) ? options.requestTimeout : 12e4;
|
|
49
|
+
this.secure = options.secure !== void 0 ? options.secure : false;
|
|
29
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if connected
|
|
53
|
+
*/
|
|
30
54
|
get connected() {
|
|
31
55
|
return this._connected;
|
|
32
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Connect to WebSocket proxy and authenticate
|
|
59
|
+
*/
|
|
33
60
|
async connect() {
|
|
34
61
|
return new Promise((resolve, reject) => {
|
|
35
62
|
const wsPort = this.port + 1;
|
|
@@ -75,9 +102,11 @@ class BrowserClient {
|
|
|
75
102
|
};
|
|
76
103
|
});
|
|
77
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Setup message listeners
|
|
107
|
+
*/
|
|
78
108
|
_setupListeners() {
|
|
79
|
-
if (!this.ws)
|
|
80
|
-
return;
|
|
109
|
+
if (!this.ws) return;
|
|
81
110
|
this.ws.onmessage = (event) => {
|
|
82
111
|
console.log("WebSocket onmessage fired - browserClient.ts", event.data);
|
|
83
112
|
try {
|
|
@@ -90,6 +119,9 @@ class BrowserClient {
|
|
|
90
119
|
}
|
|
91
120
|
};
|
|
92
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Handle incoming message
|
|
124
|
+
*/
|
|
93
125
|
_handleMessage(msg) {
|
|
94
126
|
console.log("_handleMessage called with:", msg);
|
|
95
127
|
if (!msg || typeof msg !== "object") {
|
|
@@ -107,8 +139,7 @@ class BrowserClient {
|
|
|
107
139
|
return;
|
|
108
140
|
}
|
|
109
141
|
const { resolve, reject, timer } = pending;
|
|
110
|
-
if (timer)
|
|
111
|
-
clearTimeout(timer);
|
|
142
|
+
if (timer) clearTimeout(timer);
|
|
112
143
|
this._pending.delete(msg.id);
|
|
113
144
|
console.log("Resolving request with message:", msg.message, "data:", msg.data);
|
|
114
145
|
if (msg.message === "ERROR" || msg.message === "AUTH FAILED" || msg.message === "AUTH FAIL") {
|
|
@@ -123,7 +154,10 @@ class BrowserClient {
|
|
|
123
154
|
}
|
|
124
155
|
console.log("Message has no id, ignoring");
|
|
125
156
|
}
|
|
126
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Send request to proxy (internal helper)
|
|
159
|
+
*/
|
|
160
|
+
async _sendRequest(action, data = {}) {
|
|
127
161
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
128
162
|
throw new Error("Not connected");
|
|
129
163
|
}
|
|
@@ -143,71 +177,163 @@ class BrowserClient {
|
|
|
143
177
|
try {
|
|
144
178
|
this.ws?.send(JSON.stringify(request));
|
|
145
179
|
} catch (e) {
|
|
146
|
-
if (timer)
|
|
147
|
-
clearTimeout(timer);
|
|
180
|
+
if (timer) clearTimeout(timer);
|
|
148
181
|
this._pending.delete(id);
|
|
149
182
|
reject(e);
|
|
150
183
|
}
|
|
151
184
|
});
|
|
152
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Cleanup on disconnect
|
|
188
|
+
*/
|
|
153
189
|
_cleanup() {
|
|
154
190
|
this._pending.forEach((pending) => {
|
|
155
|
-
if (pending.timer)
|
|
156
|
-
clearTimeout(pending.timer);
|
|
191
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
157
192
|
try {
|
|
158
193
|
pending.reject(new Error("Disconnected"));
|
|
159
|
-
} catch (e) {
|
|
194
|
+
} catch (e) {
|
|
195
|
+
}
|
|
160
196
|
});
|
|
161
197
|
this._pending.clear();
|
|
162
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Close connection
|
|
201
|
+
*/
|
|
163
202
|
close() {
|
|
164
203
|
if (this.ws) {
|
|
165
204
|
try {
|
|
166
205
|
this.ws.close();
|
|
167
|
-
} catch (e) {
|
|
206
|
+
} catch (e) {
|
|
207
|
+
}
|
|
168
208
|
this.ws = null;
|
|
169
209
|
}
|
|
170
210
|
this._cleanup();
|
|
171
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Disconnect (alias for close)
|
|
214
|
+
*/
|
|
172
215
|
async disconnect() {
|
|
173
216
|
this.close();
|
|
174
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Send request helper (for public API methods)
|
|
220
|
+
*/
|
|
221
|
+
async sendRequest(action, data = {}) {
|
|
222
|
+
return this.execute({ action, data });
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Execute a command. Supports concurrent requests via request id mapping.
|
|
226
|
+
* Returns a Promise resolved with response.data or rejected on ERROR/timeout.
|
|
227
|
+
*/
|
|
228
|
+
async execute(message) {
|
|
229
|
+
if (!message.action) {
|
|
230
|
+
throw new Error("Action is required");
|
|
231
|
+
}
|
|
232
|
+
return this._sendRequest(message.action, message.data);
|
|
233
|
+
}
|
|
234
|
+
// ========== Public API Methods ==========
|
|
235
|
+
/**
|
|
236
|
+
* Login with username and password
|
|
237
|
+
*/
|
|
175
238
|
async login(username, password) {
|
|
176
239
|
return this.sendRequest("login", { username, password });
|
|
177
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Logout from server
|
|
243
|
+
*/
|
|
178
244
|
async logout() {
|
|
179
245
|
return this.sendRequest("logout", {});
|
|
180
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* List all databases
|
|
249
|
+
*/
|
|
181
250
|
async listDatabases() {
|
|
182
251
|
return this.sendRequest("list-dbs", {});
|
|
183
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Create a new database
|
|
255
|
+
*/
|
|
184
256
|
async createDatabase(name) {
|
|
185
257
|
return this.sendRequest("create-db", { databaseName: name });
|
|
186
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Remove a database
|
|
261
|
+
*/
|
|
187
262
|
async removeDatabase(name) {
|
|
188
263
|
return this.sendRequest("remove-db", { databaseName: name });
|
|
189
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Rename a database
|
|
267
|
+
*/
|
|
190
268
|
async renameDatabase(oldName, newName) {
|
|
191
269
|
return this.sendRequest("rename-db", { databaseName: oldName, newName });
|
|
192
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Execute code in a database
|
|
273
|
+
*/
|
|
193
274
|
async run(code, databaseName) {
|
|
194
|
-
|
|
275
|
+
let stringCode;
|
|
276
|
+
if (typeof code === "function") {
|
|
277
|
+
const funcStr = code.toString();
|
|
278
|
+
const arrowMatch = funcStr.match(/^[\s]*\(?\s*\)?\s*=>\s*{?/);
|
|
279
|
+
const functionMatch = funcStr.match(/^[\s]*function\s*\(?[\w\s]*\)?\s*{/);
|
|
280
|
+
const match = arrowMatch || functionMatch;
|
|
281
|
+
const start = match ? match[0].length : 0;
|
|
282
|
+
const end = funcStr.lastIndexOf("}");
|
|
283
|
+
stringCode = funcStr.substring(start, end);
|
|
284
|
+
stringCode = stringCode.replace(/^[\s\r\n]+/, "").replace(/[\s\r\n]+$/, "");
|
|
285
|
+
stringCode = stringCode.replace(
|
|
286
|
+
/import\s*\(\s*([^)]+?)\s*\)\s*\.from\s*\(\s*(['"])([^'"]+)\2\s*\)/g,
|
|
287
|
+
(_, importArg, quote, modulePath) => {
|
|
288
|
+
const trimmed = importArg.trim();
|
|
289
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
290
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
291
|
+
return `import { ${inner} } from ${quote}${modulePath}${quote}`;
|
|
292
|
+
} else {
|
|
293
|
+
return `import ${trimmed} from ${quote}${modulePath}${quote}`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
stringCode = stringCode.split("\n").map((line) => line.trim()).join("\n").trim();
|
|
298
|
+
} else {
|
|
299
|
+
stringCode = code;
|
|
300
|
+
}
|
|
301
|
+
return this.sendRequest("script-code", { code: stringCode, databaseName });
|
|
195
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Save a database to disk
|
|
305
|
+
*/
|
|
196
306
|
async saveDatabase(databaseName) {
|
|
197
307
|
return this.sendRequest("save-db", { databaseName });
|
|
198
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Update database metadata
|
|
311
|
+
*/
|
|
199
312
|
async updateDatabase(databaseName, data) {
|
|
200
313
|
return this.sendRequest("update-db", { databaseName, ...data });
|
|
201
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Get database content
|
|
317
|
+
*/
|
|
318
|
+
async getDatabase(name) {
|
|
319
|
+
return this.sendRequest("get-db", { databaseName: name });
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get server information
|
|
323
|
+
*/
|
|
202
324
|
async getInfo() {
|
|
203
325
|
return this.sendRequest("get-info", {});
|
|
204
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Execute shell command on server
|
|
329
|
+
*/
|
|
205
330
|
async executeShell(command) {
|
|
206
331
|
return this.sendRequest("shell-command", { command });
|
|
207
332
|
}
|
|
208
|
-
}
|
|
209
|
-
var src_default = BrowserClient;
|
|
210
|
-
export {
|
|
211
|
-
src_default as default,
|
|
212
|
-
BrowserClient
|
|
213
333
|
};
|
|
334
|
+
var index_default = BrowserClient;
|
|
335
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
336
|
+
0 && (module.exports = {
|
|
337
|
+
BrowserClient
|
|
338
|
+
});
|
|
339
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Browser-compatible ScriptDB Client\n * \n * This is a lightweight WebSocket client that connects to the ScriptDB WebSocket proxy.\n * The proxy handles all communication with the ScriptDB server using @scriptdb/client.\n */\n\ntype Action = 'script-code' | 'save-db' | 'remove-db' | 'rename-db' | 'login' | 'logout' | 'get-info' | 'list-dbs' | 'create-db' | 'update-db' | 'get-db' | 'shell-command';\n\ninterface Logger {\n debug?: (...args: any[]) => void;\n info?: (...args: any[]) => void;\n warn?: (...args: any[]) => void;\n error?: (...args: any[]) => void;\n}\n\ninterface ClientOptions {\n host?: string;\n port?: number;\n username?: string;\n password?: string;\n logger?: Logger;\n requestTimeout?: number;\n secure?: boolean;\n}\n\ninterface PendingRequest {\n resolve: (value: any) => void;\n reject: (error: Error) => void;\n timer: number | null;\n}\n\ninterface Message {\n id?: number;\n action?: Action;\n message?: string;\n data?: any;\n}\n\nconst noopLogger: Logger = {\n debug: () => { },\n info: () => { },\n warn: () => { },\n error: () => { },\n};\n\nexport class BrowserClient {\n private logger: Logger;\n private host: string;\n private port: number;\n private username: string | null;\n private password: string | null;\n private requestTimeout: number;\n private secure: boolean;\n\n private ws: WebSocket | null = null;\n private _connected: boolean = false;\n private _nextId: number = 1;\n private _pending: Map<number, PendingRequest> = new Map();\n\n constructor(options: ClientOptions) {\n this.logger = options.logger || noopLogger;\n this.host = options.host || 'localhost';\n this.port = options.port || 1234;\n this.username = options.username || null;\n this.password = options.password || null;\n this.requestTimeout = Number.isFinite(options.requestTimeout) ? options.requestTimeout! : 120000; // 2 minutes default\n this.secure = options.secure !== undefined ? options.secure : false;\n }\n\n /**\n * Check if connected\n */\n get connected(): boolean {\n return this._connected;\n }\n\n /**\n * Connect to WebSocket proxy and authenticate\n */\n async connect(): Promise<BrowserClient> {\n return new Promise((resolve, reject) => {\n const wsPort = this.port + 1;\n const wsUrl = `ws://${this.host}:${wsPort}`;\n this.logger.info?.('Connecting to', wsUrl);\n\n try {\n this.ws = new WebSocket(wsUrl);\n } catch (e) {\n const error = e as Error;\n this.logger.error?.(\"Connection failed\", error.message);\n return reject(error);\n }\n\n this.ws.onopen = async () => {\n console.log('WebSocket onopen - browserClient.ts');\n this.logger.info?.('WebSocket connected');\n this._connected = true;\n this._setupListeners();\n\n // Always send login request (even with empty credentials for anonymous access)\n try {\n console.log('Sending login request with credentials:', { username: this.username || '', hasPassword: !!this.password, secure: this.secure });\n await this.sendRequest('login', {\n username: this.username || '',\n password: this.password || '',\n secure: this.secure\n });\n console.log('Authenticated successfully');\n this.logger.info?.('Authenticated successfully');\n resolve(this);\n } catch (err) {\n console.error('Login failed:', err);\n reject(err);\n }\n };\n\n this.ws.onerror = (error) => {\n console.error('WebSocket onerror:', error);\n this.logger.error?.('WebSocket error:', error);\n reject(new Error('WebSocket connection failed'));\n };\n\n this.ws.onclose = () => {\n console.log('WebSocket onclose');\n this.logger.info?.('WebSocket disconnected');\n this._connected = false;\n this._cleanup();\n };\n });\n }\n\n /**\n * Setup message listeners\n */\n private _setupListeners() {\n if (!this.ws) return;\n\n this.ws.onmessage = (event) => {\n console.log('WebSocket onmessage fired - browserClient.ts', event.data);\n try {\n const msg = JSON.parse(event.data) as Message;\n console.log('Parsed message:', msg);\n this._handleMessage(msg);\n } catch (error) {\n console.error('Failed to parse message:', error, event.data);\n this.logger.error?.('Failed to parse message:', error);\n }\n };\n }\n\n /**\n * Handle incoming message\n */\n private _handleMessage(msg: Message) {\n console.log('_handleMessage called with:', msg);\n if (!msg || typeof msg !== \"object\") {\n console.log('Invalid message - not an object');\n return;\n }\n\n this.logger.debug?.('Received message:', msg);\n\n // Handle response with id\n if (typeof msg.id !== \"undefined\") {\n console.log('Message has id:', msg.id);\n const pending = this._pending.get(msg.id);\n console.log('Pending request found:', !!pending, 'total pending:', this._pending.size);\n if (!pending) {\n this.logger.debug?.(\"No pending request for id\", msg.id);\n console.log(\"No pending request for id\", msg.id);\n return;\n }\n\n const { resolve, reject, timer } = pending;\n if (timer) clearTimeout(timer);\n this._pending.delete(msg.id);\n\n console.log('Resolving request with message:', msg.message, 'data:', msg.data);\n\n // Check for errors\n if (msg.message === \"ERROR\" || msg.message === \"AUTH FAILED\" || msg.message === \"AUTH FAIL\") {\n return reject(new Error(typeof msg.data === 'string' ? msg.data : \"Request failed\"));\n }\n\n // Success - AUTH OK, OK, or SUCCESS\n if (msg.message === \"AUTH OK\" || msg.message === \"OK\" || msg.message === \"SUCCESS\") {\n console.log('Calling resolve with data:', msg.data);\n return resolve(msg.data);\n }\n\n // Default to resolve with data\n console.log('Default resolve with data:', msg.data);\n return resolve(msg.data);\n }\n console.log('Message has no id, ignoring');\n }\n\n /**\n * Send request to proxy (internal helper)\n */\n private async _sendRequest(action: Action, data: any = {}): Promise<any> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Not connected');\n }\n\n const id = this._nextId++;\n const request = { id, action, data };\n\n return new Promise((resolve, reject) => {\n let timer: number | null = null;\n if (this.requestTimeout > 0) {\n timer = window.setTimeout(() => {\n if (this._pending.has(id)) {\n this._pending.delete(id);\n reject(new Error(\"Request timeout\"));\n }\n }, this.requestTimeout);\n }\n\n this._pending.set(id, { resolve, reject, timer });\n\n try {\n this.ws?.send(JSON.stringify(request));\n } catch (e) {\n if (timer) clearTimeout(timer);\n this._pending.delete(id);\n reject(e);\n }\n });\n }\n\n /**\n * Cleanup on disconnect\n */\n private _cleanup() {\n this._pending.forEach((pending) => {\n if (pending.timer) clearTimeout(pending.timer);\n try {\n pending.reject(new Error(\"Disconnected\"));\n } catch (e) { }\n });\n this._pending.clear();\n }\n\n /**\n * Close connection\n */\n close() {\n if (this.ws) {\n try {\n this.ws.close();\n } catch (e) { }\n this.ws = null;\n }\n this._cleanup();\n }\n\n /**\n * Disconnect (alias for close)\n */\n async disconnect(): Promise<void> {\n this.close();\n }\n\n /**\n * Send request helper (for public API methods)\n */\n async sendRequest(action: Action, data: any = {}): Promise<any> {\n return this.execute({ action, data });\n }\n\n /**\n * Execute a command. Supports concurrent requests via request id mapping.\n * Returns a Promise resolved with response.data or rejected on ERROR/timeout.\n */\n async execute(message: Message): Promise<any> {\n if (!message.action) {\n throw new Error('Action is required');\n }\n return this._sendRequest(message.action, message.data);\n }\n\n // ========== Public API Methods ==========\n\n /**\n * Login with username and password\n */\n async login(username: string, password: string): Promise<any> {\n return this.sendRequest('login', { username, password });\n }\n\n /**\n * Logout from server\n */\n async logout(): Promise<any> {\n return this.sendRequest('logout', {});\n }\n\n /**\n * List all databases\n */\n async listDatabases(): Promise<any> {\n return this.sendRequest('list-dbs', {});\n }\n\n /**\n * Create a new database\n */\n async createDatabase(name: string): Promise<any> {\n return this.sendRequest('create-db', { databaseName: name });\n }\n\n /**\n * Remove a database\n */\n async removeDatabase(name: string): Promise<any> {\n return this.sendRequest('remove-db', { databaseName: name });\n }\n\n /**\n * Rename a database\n */\n async renameDatabase(oldName: string, newName: string): Promise<any> {\n return this.sendRequest('rename-db', { databaseName: oldName, newName });\n }\n\n /**\n * Execute code in a database\n */\n async run(code: string | Function, databaseName: string): Promise<any> {\n let stringCode: string;\n if (typeof code === 'function') {\n const funcStr = code.toString();\n // ตัด arrow function หรือ function keyword และ opening brace ออก\n const arrowMatch = funcStr.match(/^[\\s]*\\(?\\s*\\)?\\s*=>\\s*{?/);\n const functionMatch = funcStr.match(/^[\\s]*function\\s*\\(?[\\w\\s]*\\)?\\s*{/);\n const match = arrowMatch || functionMatch;\n const start = match ? match[0].length : 0;\n const end = funcStr.lastIndexOf('}');\n stringCode = funcStr.substring(start, end);\n // Trim leading newline, spaces, and trailing\n stringCode = stringCode.replace(/^[\\s\\r\\n]+/, '').replace(/[\\s\\r\\n]+$/, '');\n\n // Transform import(aa).from(\"module\") to import aa from \"module\"\n stringCode = stringCode.replace(\n /import\\s*\\(\\s*([^)]+?)\\s*\\)\\s*\\.from\\s*\\(\\s*(['\"])([^'\"]+)\\2\\s*\\)/g,\n (_, importArg, quote, modulePath) => {\n // Check if importArg is wrapped in braces (destructuring)\n const trimmed = importArg.trim();\n if (trimmed.startsWith('{') && trimmed.endsWith('}')) {\n // Destructuring: import({bb}) -> import { bb }\n const inner = trimmed.slice(1, -1).trim();\n return `import { ${inner} } from ${quote}${modulePath}${quote}`;\n } else {\n // Default: import(aa) -> import aa\n return `import ${trimmed} from ${quote}${modulePath}${quote}`;\n }\n }\n );\n\n // Trim leading whitespace from each line\n stringCode = stringCode.split('\\n').map(line => line.trim()).join('\\n').trim();\n } else {\n stringCode = code;\n }\n\n return this.sendRequest('script-code', { code: stringCode, databaseName });\n }\n\n /**\n * Save a database to disk\n */\n async saveDatabase(databaseName: string): Promise<any> {\n return this.sendRequest('save-db', { databaseName });\n }\n\n /**\n * Update database metadata\n */\n async updateDatabase(databaseName: string, data: any): Promise<any> {\n return this.sendRequest('update-db', { databaseName, ...data });\n }\n\n /**\n * Get database content\n */\n async getDatabase(name: string): Promise<{ success: boolean; databaseName: string; content: string; path?: string }> {\n return this.sendRequest('get-db', { databaseName: name });\n }\n\n /**\n * Get server information\n */\n async getInfo(): Promise<any> {\n return this.sendRequest('get-info', {});\n }\n\n /**\n * Execute shell command on server\n */\n async executeShell(command: string): Promise<{ stdout: string; stderr: string }> {\n return this.sendRequest('shell-command', { command });\n }\n}\n\nexport default BrowserClient;\n\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var noopLogger = {
|
|
3
|
+
debug: () => {
|
|
4
|
+
},
|
|
5
|
+
info: () => {
|
|
6
|
+
},
|
|
7
|
+
warn: () => {
|
|
8
|
+
},
|
|
9
|
+
error: () => {
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var BrowserClient = class {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.ws = null;
|
|
15
|
+
this._connected = false;
|
|
16
|
+
this._nextId = 1;
|
|
17
|
+
this._pending = /* @__PURE__ */ new Map();
|
|
18
|
+
this.logger = options.logger || noopLogger;
|
|
19
|
+
this.host = options.host || "localhost";
|
|
20
|
+
this.port = options.port || 1234;
|
|
21
|
+
this.username = options.username || null;
|
|
22
|
+
this.password = options.password || null;
|
|
23
|
+
this.requestTimeout = Number.isFinite(options.requestTimeout) ? options.requestTimeout : 12e4;
|
|
24
|
+
this.secure = options.secure !== void 0 ? options.secure : false;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check if connected
|
|
28
|
+
*/
|
|
29
|
+
get connected() {
|
|
30
|
+
return this._connected;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Connect to WebSocket proxy and authenticate
|
|
34
|
+
*/
|
|
35
|
+
async connect() {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const wsPort = this.port + 1;
|
|
38
|
+
const wsUrl = `ws://${this.host}:${wsPort}`;
|
|
39
|
+
this.logger.info?.("Connecting to", wsUrl);
|
|
40
|
+
try {
|
|
41
|
+
this.ws = new WebSocket(wsUrl);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
const error = e;
|
|
44
|
+
this.logger.error?.("Connection failed", error.message);
|
|
45
|
+
return reject(error);
|
|
46
|
+
}
|
|
47
|
+
this.ws.onopen = async () => {
|
|
48
|
+
console.log("WebSocket onopen - browserClient.ts");
|
|
49
|
+
this.logger.info?.("WebSocket connected");
|
|
50
|
+
this._connected = true;
|
|
51
|
+
this._setupListeners();
|
|
52
|
+
try {
|
|
53
|
+
console.log("Sending login request with credentials:", { username: this.username || "", hasPassword: !!this.password, secure: this.secure });
|
|
54
|
+
await this.sendRequest("login", {
|
|
55
|
+
username: this.username || "",
|
|
56
|
+
password: this.password || "",
|
|
57
|
+
secure: this.secure
|
|
58
|
+
});
|
|
59
|
+
console.log("Authenticated successfully");
|
|
60
|
+
this.logger.info?.("Authenticated successfully");
|
|
61
|
+
resolve(this);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error("Login failed:", err);
|
|
64
|
+
reject(err);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
this.ws.onerror = (error) => {
|
|
68
|
+
console.error("WebSocket onerror:", error);
|
|
69
|
+
this.logger.error?.("WebSocket error:", error);
|
|
70
|
+
reject(new Error("WebSocket connection failed"));
|
|
71
|
+
};
|
|
72
|
+
this.ws.onclose = () => {
|
|
73
|
+
console.log("WebSocket onclose");
|
|
74
|
+
this.logger.info?.("WebSocket disconnected");
|
|
75
|
+
this._connected = false;
|
|
76
|
+
this._cleanup();
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Setup message listeners
|
|
82
|
+
*/
|
|
83
|
+
_setupListeners() {
|
|
84
|
+
if (!this.ws) return;
|
|
85
|
+
this.ws.onmessage = (event) => {
|
|
86
|
+
console.log("WebSocket onmessage fired - browserClient.ts", event.data);
|
|
87
|
+
try {
|
|
88
|
+
const msg = JSON.parse(event.data);
|
|
89
|
+
console.log("Parsed message:", msg);
|
|
90
|
+
this._handleMessage(msg);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error("Failed to parse message:", error, event.data);
|
|
93
|
+
this.logger.error?.("Failed to parse message:", error);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Handle incoming message
|
|
99
|
+
*/
|
|
100
|
+
_handleMessage(msg) {
|
|
101
|
+
console.log("_handleMessage called with:", msg);
|
|
102
|
+
if (!msg || typeof msg !== "object") {
|
|
103
|
+
console.log("Invalid message - not an object");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.logger.debug?.("Received message:", msg);
|
|
107
|
+
if (typeof msg.id !== "undefined") {
|
|
108
|
+
console.log("Message has id:", msg.id);
|
|
109
|
+
const pending = this._pending.get(msg.id);
|
|
110
|
+
console.log("Pending request found:", !!pending, "total pending:", this._pending.size);
|
|
111
|
+
if (!pending) {
|
|
112
|
+
this.logger.debug?.("No pending request for id", msg.id);
|
|
113
|
+
console.log("No pending request for id", msg.id);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const { resolve, reject, timer } = pending;
|
|
117
|
+
if (timer) clearTimeout(timer);
|
|
118
|
+
this._pending.delete(msg.id);
|
|
119
|
+
console.log("Resolving request with message:", msg.message, "data:", msg.data);
|
|
120
|
+
if (msg.message === "ERROR" || msg.message === "AUTH FAILED" || msg.message === "AUTH FAIL") {
|
|
121
|
+
return reject(new Error(typeof msg.data === "string" ? msg.data : "Request failed"));
|
|
122
|
+
}
|
|
123
|
+
if (msg.message === "AUTH OK" || msg.message === "OK" || msg.message === "SUCCESS") {
|
|
124
|
+
console.log("Calling resolve with data:", msg.data);
|
|
125
|
+
return resolve(msg.data);
|
|
126
|
+
}
|
|
127
|
+
console.log("Default resolve with data:", msg.data);
|
|
128
|
+
return resolve(msg.data);
|
|
129
|
+
}
|
|
130
|
+
console.log("Message has no id, ignoring");
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Send request to proxy (internal helper)
|
|
134
|
+
*/
|
|
135
|
+
async _sendRequest(action, data = {}) {
|
|
136
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
137
|
+
throw new Error("Not connected");
|
|
138
|
+
}
|
|
139
|
+
const id = this._nextId++;
|
|
140
|
+
const request = { id, action, data };
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
let timer = null;
|
|
143
|
+
if (this.requestTimeout > 0) {
|
|
144
|
+
timer = window.setTimeout(() => {
|
|
145
|
+
if (this._pending.has(id)) {
|
|
146
|
+
this._pending.delete(id);
|
|
147
|
+
reject(new Error("Request timeout"));
|
|
148
|
+
}
|
|
149
|
+
}, this.requestTimeout);
|
|
150
|
+
}
|
|
151
|
+
this._pending.set(id, { resolve, reject, timer });
|
|
152
|
+
try {
|
|
153
|
+
this.ws?.send(JSON.stringify(request));
|
|
154
|
+
} catch (e) {
|
|
155
|
+
if (timer) clearTimeout(timer);
|
|
156
|
+
this._pending.delete(id);
|
|
157
|
+
reject(e);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Cleanup on disconnect
|
|
163
|
+
*/
|
|
164
|
+
_cleanup() {
|
|
165
|
+
this._pending.forEach((pending) => {
|
|
166
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
167
|
+
try {
|
|
168
|
+
pending.reject(new Error("Disconnected"));
|
|
169
|
+
} catch (e) {
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
this._pending.clear();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Close connection
|
|
176
|
+
*/
|
|
177
|
+
close() {
|
|
178
|
+
if (this.ws) {
|
|
179
|
+
try {
|
|
180
|
+
this.ws.close();
|
|
181
|
+
} catch (e) {
|
|
182
|
+
}
|
|
183
|
+
this.ws = null;
|
|
184
|
+
}
|
|
185
|
+
this._cleanup();
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Disconnect (alias for close)
|
|
189
|
+
*/
|
|
190
|
+
async disconnect() {
|
|
191
|
+
this.close();
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Send request helper (for public API methods)
|
|
195
|
+
*/
|
|
196
|
+
async sendRequest(action, data = {}) {
|
|
197
|
+
return this.execute({ action, data });
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Execute a command. Supports concurrent requests via request id mapping.
|
|
201
|
+
* Returns a Promise resolved with response.data or rejected on ERROR/timeout.
|
|
202
|
+
*/
|
|
203
|
+
async execute(message) {
|
|
204
|
+
if (!message.action) {
|
|
205
|
+
throw new Error("Action is required");
|
|
206
|
+
}
|
|
207
|
+
return this._sendRequest(message.action, message.data);
|
|
208
|
+
}
|
|
209
|
+
// ========== Public API Methods ==========
|
|
210
|
+
/**
|
|
211
|
+
* Login with username and password
|
|
212
|
+
*/
|
|
213
|
+
async login(username, password) {
|
|
214
|
+
return this.sendRequest("login", { username, password });
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Logout from server
|
|
218
|
+
*/
|
|
219
|
+
async logout() {
|
|
220
|
+
return this.sendRequest("logout", {});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* List all databases
|
|
224
|
+
*/
|
|
225
|
+
async listDatabases() {
|
|
226
|
+
return this.sendRequest("list-dbs", {});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Create a new database
|
|
230
|
+
*/
|
|
231
|
+
async createDatabase(name) {
|
|
232
|
+
return this.sendRequest("create-db", { databaseName: name });
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Remove a database
|
|
236
|
+
*/
|
|
237
|
+
async removeDatabase(name) {
|
|
238
|
+
return this.sendRequest("remove-db", { databaseName: name });
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Rename a database
|
|
242
|
+
*/
|
|
243
|
+
async renameDatabase(oldName, newName) {
|
|
244
|
+
return this.sendRequest("rename-db", { databaseName: oldName, newName });
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Execute code in a database
|
|
248
|
+
*/
|
|
249
|
+
async run(code, databaseName) {
|
|
250
|
+
let stringCode;
|
|
251
|
+
if (typeof code === "function") {
|
|
252
|
+
const funcStr = code.toString();
|
|
253
|
+
const arrowMatch = funcStr.match(/^[\s]*\(?\s*\)?\s*=>\s*{?/);
|
|
254
|
+
const functionMatch = funcStr.match(/^[\s]*function\s*\(?[\w\s]*\)?\s*{/);
|
|
255
|
+
const match = arrowMatch || functionMatch;
|
|
256
|
+
const start = match ? match[0].length : 0;
|
|
257
|
+
const end = funcStr.lastIndexOf("}");
|
|
258
|
+
stringCode = funcStr.substring(start, end);
|
|
259
|
+
stringCode = stringCode.replace(/^[\s\r\n]+/, "").replace(/[\s\r\n]+$/, "");
|
|
260
|
+
stringCode = stringCode.replace(
|
|
261
|
+
/import\s*\(\s*([^)]+?)\s*\)\s*\.from\s*\(\s*(['"])([^'"]+)\2\s*\)/g,
|
|
262
|
+
(_, importArg, quote, modulePath) => {
|
|
263
|
+
const trimmed = importArg.trim();
|
|
264
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
265
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
266
|
+
return `import { ${inner} } from ${quote}${modulePath}${quote}`;
|
|
267
|
+
} else {
|
|
268
|
+
return `import ${trimmed} from ${quote}${modulePath}${quote}`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
stringCode = stringCode.split("\n").map((line) => line.trim()).join("\n").trim();
|
|
273
|
+
} else {
|
|
274
|
+
stringCode = code;
|
|
275
|
+
}
|
|
276
|
+
return this.sendRequest("script-code", { code: stringCode, databaseName });
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Save a database to disk
|
|
280
|
+
*/
|
|
281
|
+
async saveDatabase(databaseName) {
|
|
282
|
+
return this.sendRequest("save-db", { databaseName });
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Update database metadata
|
|
286
|
+
*/
|
|
287
|
+
async updateDatabase(databaseName, data) {
|
|
288
|
+
return this.sendRequest("update-db", { databaseName, ...data });
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get database content
|
|
292
|
+
*/
|
|
293
|
+
async getDatabase(name) {
|
|
294
|
+
return this.sendRequest("get-db", { databaseName: name });
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get server information
|
|
298
|
+
*/
|
|
299
|
+
async getInfo() {
|
|
300
|
+
return this.sendRequest("get-info", {});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Execute shell command on server
|
|
304
|
+
*/
|
|
305
|
+
async executeShell(command) {
|
|
306
|
+
return this.sendRequest("shell-command", { command });
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
var index_default = BrowserClient;
|
|
310
|
+
export {
|
|
311
|
+
BrowserClient,
|
|
312
|
+
index_default as default
|
|
313
|
+
};
|
|
314
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Browser-compatible ScriptDB Client\n * \n * This is a lightweight WebSocket client that connects to the ScriptDB WebSocket proxy.\n * The proxy handles all communication with the ScriptDB server using @scriptdb/client.\n */\n\ntype Action = 'script-code' | 'save-db' | 'remove-db' | 'rename-db' | 'login' | 'logout' | 'get-info' | 'list-dbs' | 'create-db' | 'update-db' | 'get-db' | 'shell-command';\n\ninterface Logger {\n debug?: (...args: any[]) => void;\n info?: (...args: any[]) => void;\n warn?: (...args: any[]) => void;\n error?: (...args: any[]) => void;\n}\n\ninterface ClientOptions {\n host?: string;\n port?: number;\n username?: string;\n password?: string;\n logger?: Logger;\n requestTimeout?: number;\n secure?: boolean;\n}\n\ninterface PendingRequest {\n resolve: (value: any) => void;\n reject: (error: Error) => void;\n timer: number | null;\n}\n\ninterface Message {\n id?: number;\n action?: Action;\n message?: string;\n data?: any;\n}\n\nconst noopLogger: Logger = {\n debug: () => { },\n info: () => { },\n warn: () => { },\n error: () => { },\n};\n\nexport class BrowserClient {\n private logger: Logger;\n private host: string;\n private port: number;\n private username: string | null;\n private password: string | null;\n private requestTimeout: number;\n private secure: boolean;\n\n private ws: WebSocket | null = null;\n private _connected: boolean = false;\n private _nextId: number = 1;\n private _pending: Map<number, PendingRequest> = new Map();\n\n constructor(options: ClientOptions) {\n this.logger = options.logger || noopLogger;\n this.host = options.host || 'localhost';\n this.port = options.port || 1234;\n this.username = options.username || null;\n this.password = options.password || null;\n this.requestTimeout = Number.isFinite(options.requestTimeout) ? options.requestTimeout! : 120000; // 2 minutes default\n this.secure = options.secure !== undefined ? options.secure : false;\n }\n\n /**\n * Check if connected\n */\n get connected(): boolean {\n return this._connected;\n }\n\n /**\n * Connect to WebSocket proxy and authenticate\n */\n async connect(): Promise<BrowserClient> {\n return new Promise((resolve, reject) => {\n const wsPort = this.port + 1;\n const wsUrl = `ws://${this.host}:${wsPort}`;\n this.logger.info?.('Connecting to', wsUrl);\n\n try {\n this.ws = new WebSocket(wsUrl);\n } catch (e) {\n const error = e as Error;\n this.logger.error?.(\"Connection failed\", error.message);\n return reject(error);\n }\n\n this.ws.onopen = async () => {\n console.log('WebSocket onopen - browserClient.ts');\n this.logger.info?.('WebSocket connected');\n this._connected = true;\n this._setupListeners();\n\n // Always send login request (even with empty credentials for anonymous access)\n try {\n console.log('Sending login request with credentials:', { username: this.username || '', hasPassword: !!this.password, secure: this.secure });\n await this.sendRequest('login', {\n username: this.username || '',\n password: this.password || '',\n secure: this.secure\n });\n console.log('Authenticated successfully');\n this.logger.info?.('Authenticated successfully');\n resolve(this);\n } catch (err) {\n console.error('Login failed:', err);\n reject(err);\n }\n };\n\n this.ws.onerror = (error) => {\n console.error('WebSocket onerror:', error);\n this.logger.error?.('WebSocket error:', error);\n reject(new Error('WebSocket connection failed'));\n };\n\n this.ws.onclose = () => {\n console.log('WebSocket onclose');\n this.logger.info?.('WebSocket disconnected');\n this._connected = false;\n this._cleanup();\n };\n });\n }\n\n /**\n * Setup message listeners\n */\n private _setupListeners() {\n if (!this.ws) return;\n\n this.ws.onmessage = (event) => {\n console.log('WebSocket onmessage fired - browserClient.ts', event.data);\n try {\n const msg = JSON.parse(event.data) as Message;\n console.log('Parsed message:', msg);\n this._handleMessage(msg);\n } catch (error) {\n console.error('Failed to parse message:', error, event.data);\n this.logger.error?.('Failed to parse message:', error);\n }\n };\n }\n\n /**\n * Handle incoming message\n */\n private _handleMessage(msg: Message) {\n console.log('_handleMessage called with:', msg);\n if (!msg || typeof msg !== \"object\") {\n console.log('Invalid message - not an object');\n return;\n }\n\n this.logger.debug?.('Received message:', msg);\n\n // Handle response with id\n if (typeof msg.id !== \"undefined\") {\n console.log('Message has id:', msg.id);\n const pending = this._pending.get(msg.id);\n console.log('Pending request found:', !!pending, 'total pending:', this._pending.size);\n if (!pending) {\n this.logger.debug?.(\"No pending request for id\", msg.id);\n console.log(\"No pending request for id\", msg.id);\n return;\n }\n\n const { resolve, reject, timer } = pending;\n if (timer) clearTimeout(timer);\n this._pending.delete(msg.id);\n\n console.log('Resolving request with message:', msg.message, 'data:', msg.data);\n\n // Check for errors\n if (msg.message === \"ERROR\" || msg.message === \"AUTH FAILED\" || msg.message === \"AUTH FAIL\") {\n return reject(new Error(typeof msg.data === 'string' ? msg.data : \"Request failed\"));\n }\n\n // Success - AUTH OK, OK, or SUCCESS\n if (msg.message === \"AUTH OK\" || msg.message === \"OK\" || msg.message === \"SUCCESS\") {\n console.log('Calling resolve with data:', msg.data);\n return resolve(msg.data);\n }\n\n // Default to resolve with data\n console.log('Default resolve with data:', msg.data);\n return resolve(msg.data);\n }\n console.log('Message has no id, ignoring');\n }\n\n /**\n * Send request to proxy (internal helper)\n */\n private async _sendRequest(action: Action, data: any = {}): Promise<any> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('Not connected');\n }\n\n const id = this._nextId++;\n const request = { id, action, data };\n\n return new Promise((resolve, reject) => {\n let timer: number | null = null;\n if (this.requestTimeout > 0) {\n timer = window.setTimeout(() => {\n if (this._pending.has(id)) {\n this._pending.delete(id);\n reject(new Error(\"Request timeout\"));\n }\n }, this.requestTimeout);\n }\n\n this._pending.set(id, { resolve, reject, timer });\n\n try {\n this.ws?.send(JSON.stringify(request));\n } catch (e) {\n if (timer) clearTimeout(timer);\n this._pending.delete(id);\n reject(e);\n }\n });\n }\n\n /**\n * Cleanup on disconnect\n */\n private _cleanup() {\n this._pending.forEach((pending) => {\n if (pending.timer) clearTimeout(pending.timer);\n try {\n pending.reject(new Error(\"Disconnected\"));\n } catch (e) { }\n });\n this._pending.clear();\n }\n\n /**\n * Close connection\n */\n close() {\n if (this.ws) {\n try {\n this.ws.close();\n } catch (e) { }\n this.ws = null;\n }\n this._cleanup();\n }\n\n /**\n * Disconnect (alias for close)\n */\n async disconnect(): Promise<void> {\n this.close();\n }\n\n /**\n * Send request helper (for public API methods)\n */\n async sendRequest(action: Action, data: any = {}): Promise<any> {\n return this.execute({ action, data });\n }\n\n /**\n * Execute a command. Supports concurrent requests via request id mapping.\n * Returns a Promise resolved with response.data or rejected on ERROR/timeout.\n */\n async execute(message: Message): Promise<any> {\n if (!message.action) {\n throw new Error('Action is required');\n }\n return this._sendRequest(message.action, message.data);\n }\n\n // ========== Public API Methods ==========\n\n /**\n * Login with username and password\n */\n async login(username: string, password: string): Promise<any> {\n return this.sendRequest('login', { username, password });\n }\n\n /**\n * Logout from server\n */\n async logout(): Promise<any> {\n return this.sendRequest('logout', {});\n }\n\n /**\n * List all databases\n */\n async listDatabases(): Promise<any> {\n return this.sendRequest('list-dbs', {});\n }\n\n /**\n * Create a new database\n */\n async createDatabase(name: string): Promise<any> {\n return this.sendRequest('create-db', { databaseName: name });\n }\n\n /**\n * Remove a database\n */\n async removeDatabase(name: string): Promise<any> {\n return this.sendRequest('remove-db', { databaseName: name });\n }\n\n /**\n * Rename a database\n */\n async renameDatabase(oldName: string, newName: string): Promise<any> {\n return this.sendRequest('rename-db', { databaseName: oldName, newName });\n }\n\n /**\n * Execute code in a database\n */\n async run(code: string | Function, databaseName: string): Promise<any> {\n let stringCode: string;\n if (typeof code === 'function') {\n const funcStr = code.toString();\n // ตัด arrow function หรือ function keyword และ opening brace ออก\n const arrowMatch = funcStr.match(/^[\\s]*\\(?\\s*\\)?\\s*=>\\s*{?/);\n const functionMatch = funcStr.match(/^[\\s]*function\\s*\\(?[\\w\\s]*\\)?\\s*{/);\n const match = arrowMatch || functionMatch;\n const start = match ? match[0].length : 0;\n const end = funcStr.lastIndexOf('}');\n stringCode = funcStr.substring(start, end);\n // Trim leading newline, spaces, and trailing\n stringCode = stringCode.replace(/^[\\s\\r\\n]+/, '').replace(/[\\s\\r\\n]+$/, '');\n\n // Transform import(aa).from(\"module\") to import aa from \"module\"\n stringCode = stringCode.replace(\n /import\\s*\\(\\s*([^)]+?)\\s*\\)\\s*\\.from\\s*\\(\\s*(['\"])([^'\"]+)\\2\\s*\\)/g,\n (_, importArg, quote, modulePath) => {\n // Check if importArg is wrapped in braces (destructuring)\n const trimmed = importArg.trim();\n if (trimmed.startsWith('{') && trimmed.endsWith('}')) {\n // Destructuring: import({bb}) -> import { bb }\n const inner = trimmed.slice(1, -1).trim();\n return `import { ${inner} } from ${quote}${modulePath}${quote}`;\n } else {\n // Default: import(aa) -> import aa\n return `import ${trimmed} from ${quote}${modulePath}${quote}`;\n }\n }\n );\n\n // Trim leading whitespace from each line\n stringCode = stringCode.split('\\n').map(line => line.trim()).join('\\n').trim();\n } else {\n stringCode = code;\n }\n\n return this.sendRequest('script-code', { code: stringCode, databaseName });\n }\n\n /**\n * Save a database to disk\n */\n async saveDatabase(databaseName: string): Promise<any> {\n return this.sendRequest('save-db', { databaseName });\n }\n\n /**\n * Update database metadata\n */\n async updateDatabase(databaseName: string, data: any): Promise<any> {\n return this.sendRequest('update-db', { databaseName, ...data });\n }\n\n /**\n * Get database content\n */\n async getDatabase(name: string): Promise<{ success: boolean; databaseName: string; content: string; path?: string }> {\n return this.sendRequest('get-db', { databaseName: name });\n }\n\n /**\n * Get server information\n */\n async getInfo(): Promise<any> {\n return this.sendRequest('get-info', {});\n }\n\n /**\n * Execute shell command on server\n */\n async executeShell(command: string): Promise<{ stdout: string; stderr: string }> {\n return this.sendRequest('shell-command', { command });\n }\n}\n\nexport default BrowserClient;\n\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scriptdb/browser-client",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Browser WebSocket client for ScriptDB",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -17,10 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"dev": "bun --watch src/index.ts",
|
|
20
|
-
"build": "
|
|
21
|
-
"build:cjs": "bun build src/index.ts --outdir dist --target browser --format cjs --outfile dist/index.js",
|
|
22
|
-
"build:types": "tsc --emitDeclarationOnly",
|
|
23
|
-
"build:all": "bun run build && bun run build:cjs && bun run build:types",
|
|
20
|
+
"build": "tsup",
|
|
24
21
|
"typecheck": "tsc --noEmit",
|
|
25
22
|
"clean": "rm -rf dist"
|
|
26
23
|
},
|