wispjs 2.4.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,495 +0,0 @@
1
- import stripAnsi from 'strip-ansi';
2
- import { WebsocketPool } from "./pool.js"
3
- import { ConsoleMessage, FilesearchResults } from "./pool"
4
- import { GitPullData, GitPullResult } from "./pool.js"
5
- import { GitCloneData, GitCloneResult } from "./pool.js"
6
-
7
- import type { WispAPI } from "../wisp_api/index.js"
8
-
9
-
10
- /**
11
- * The Websocket information returned from the API
12
- *
13
- * @param token The token to use when authenticating with the `auth` command in the Websocket
14
- * @param url The actual URL of the Websocket
15
- *
16
- * @internal
17
- */
18
- export interface WebsocketInfo {
19
- token: string;
20
- url: string;
21
- }
22
-
23
-
24
- export type WebsocketDetailsPreprocessor = (info: WebsocketInfo) => void;
25
-
26
-
27
- export interface WispSocket {
28
- pool: WebsocketPool;
29
- logger: any;
30
- api: WispAPI;
31
- url: string | undefined;
32
- token: string | undefined;
33
- ghToken: string | undefined;
34
- consoleCallbacks: ((message: string) => void)[];
35
- detailsPreprocessor: WebsocketDetailsPreprocessor | undefined;
36
- }
37
-
38
-
39
- /**
40
- * The primary interface to the Websocket API
41
- *
42
- * @internal
43
- */
44
- export class WispSocket {
45
- constructor(logger: any, api: any, ghToken: string | undefined) {
46
- this.logger = logger
47
- this.api = api
48
- this.ghToken = ghToken
49
- this.consoleCallbacks = []
50
- }
51
-
52
-
53
- /**
54
- * Sets a callback to run on the Websocket Info before saving the details.
55
- *
56
- * @example
57
- * ```js
58
- * // Change the URL of the Websocket
59
- * wisp.socket.setWebsocketDetailsPreprocessor((info) => {
60
- * info.url = "wss://newurl.com"
61
- * })
62
- * ```
63
- *
64
- * @remarks
65
- * ℹ️ This can be used to modify the URL or token after its retrieved from the API
66
- *
67
- * @param preprocessor The callback to run when the data is received from the API
68
- *
69
- * @public
70
- */
71
- setWebsocketDetailsPreprocessor(preprocessor: WebsocketDetailsPreprocessor) {
72
- this.detailsPreprocessor = preprocessor;
73
- }
74
-
75
-
76
- /**
77
- * Creates a new Websocket Pool
78
- *
79
- * @throws Throws an error if the URL or token are not set yet
80
- * @internal
81
- */
82
- createPool() {
83
- if (!this.url || !this.token) {
84
- throw new Error("Attempted to create a pool without a URL or token")
85
- }
86
-
87
- this.pool = new WebsocketPool(this.url, this.token)
88
- }
89
-
90
-
91
- /**
92
- * Requests and saves the Websocket details from the API
93
- *
94
- * @internal
95
- */
96
- async setDetails() {
97
- try {
98
- const websocketInfo: WebsocketInfo = await this.api.Servers.GetWebsocketDetails()
99
- if (this.detailsPreprocessor) {
100
- this.detailsPreprocessor(websocketInfo);
101
- }
102
-
103
- this.url = websocketInfo.url
104
- this.token = websocketInfo.token
105
-
106
- this.logger.info("Got Websocket Details.", this.url, this.token)
107
- } catch(e) {
108
- this.logger.error(`Failed to get websocket details: ${e}`)
109
- throw(e)
110
- }
111
- }
112
-
113
-
114
- /**
115
- * Disconnects from the websocket
116
- *
117
- * @internal
118
- */
119
- async disconnect() {
120
- if (this.pool) {
121
- await this.pool.disconnect()
122
- }
123
- }
124
-
125
-
126
- /**
127
- * Verifies that the pool is created and ready to use
128
- *
129
- * @internal
130
- */
131
- async verifyPool() {
132
- if (!this.pool) {
133
- await this.setDetails()
134
- this.createPool()
135
- }
136
- }
137
-
138
-
139
- /**
140
- * Searches all file contents for the given query
141
- *
142
- * @param query The query string to search for
143
- * @param timeout How long to wait (in ms) for results before timing out
144
- *
145
- * @public
146
- */
147
- async filesearch(query: string, timeout: number = 10000): Promise<FilesearchResults> {
148
- this.logger.info("Running filesearch with: ", query)
149
- await this.verifyPool()
150
-
151
- return await this.pool.run((worker) => {
152
- const socket = worker.socket
153
- const logger = worker.logger
154
- logger.log("Running filesearch:", query)
155
-
156
- return new Promise<FilesearchResults>((resolve, reject) => {
157
- const timeoutObj = setTimeout(() => {
158
- socket.off("filesearch-results")
159
- logger.error("Rejected filesearch: 'Timeout'")
160
- reject()
161
- }, timeout)
162
-
163
- socket.once("filesearch-results", (data) => {
164
- clearTimeout(timeoutObj)
165
- resolve(data)
166
- })
167
-
168
- socket.emit("filesearch-start", query)
169
- })
170
- })
171
- }
172
-
173
-
174
- /**
175
- * Performs a git pull operation on the given directory
176
- *
177
- * @param dir The full directory path to perform a pull on
178
- * @param timeout In milliseconds, how long to wait before timing out
179
- *
180
- * @public
181
- */
182
- async gitPull(dir: string, useAuth: boolean = false, timeout: number = 10000) {
183
- await this.verifyPool()
184
-
185
- const pullResult = await this.pool.run((worker) => {
186
- const socket = worker.socket
187
- const logger = worker.logger
188
- logger.log("Running gitPull:", dir)
189
-
190
- return new Promise<GitPullResult>((resolve, reject) => {
191
- let isPrivate = false
192
- let finished: (success: boolean, output: string) => void
193
-
194
- const timeoutObj = setTimeout(() => {
195
- logger.error("Rejected gitPull: 'Timeout'")
196
- finished(false, "Timeout")
197
- }, timeout)
198
-
199
- finished = (success: boolean, output: string) => {
200
- socket.removeAllListeners("git-pull")
201
- socket.removeAllListeners("git-error")
202
- socket.removeAllListeners("git-success")
203
-
204
- const result: GitPullResult = {
205
- output: output,
206
- isPrivate: isPrivate
207
- }
208
-
209
- if (success) {
210
- resolve(result)
211
- } else {
212
- logger.error("Rejected gitPull:", dir, output)
213
- reject(output)
214
- }
215
-
216
- clearTimeout(timeoutObj)
217
- }
218
-
219
- const sendRequest = (includeAuth: boolean = false) => {
220
- const data: GitPullData = { dir: dir }
221
-
222
- if (includeAuth) {
223
- if (!this.ghToken) {
224
- logger.error("No GitHub token set, can't authenticate")
225
- return finished(false, "Authentication is required, but no GitHub token was set. Can't pull!")
226
- }
227
-
228
- isPrivate = true
229
- data.authkey = this.ghToken
230
- }
231
-
232
- socket.emit("git-pull", data)
233
- }
234
-
235
- socket.once("git-pull", (data) => {
236
- logger.log(`Updating ${data}`)
237
- })
238
-
239
- socket.once("git-success", (commit) => {
240
- logger.log(`Addon updated to ${commit}`)
241
-
242
- if (!commit) {
243
- logger.log("No commit given!")
244
- }
245
-
246
- finished(true, commit || "")
247
- })
248
-
249
- socket.on("git-error", (message) => {
250
- if (message === "Remote authentication required but no callback set") {
251
- logger.log(`Remote authentication required, trying again with authkey: ${dir}`)
252
- sendRequest(true)
253
- } else {
254
- logger.log(`Error updating addon: ${message}`)
255
- finished(false, message)
256
- }
257
- })
258
-
259
- sendRequest(useAuth)
260
- })
261
- })
262
-
263
- return pullResult
264
- }
265
-
266
-
267
- /**
268
- * Clones a new Repo to the given directory
269
- *
270
- * @param url The HTTPS URL of the repository
271
- * @param dir The full path of the directory to clone the repository to
272
- * @param branch The branch of the repository to clone
273
- * @param timeout In milliseconds, how long to wait before timing out
274
- *
275
- * @public
276
- */
277
- async gitClone(url: string, dir: string, branch: string, timeout: number = 20000) {
278
- await this.verifyPool()
279
-
280
- return await this.pool.run((worker) => {
281
- const socket = worker.socket
282
- const logger = worker.logger
283
- logger.log("Running gitClone:", url, dir, branch)
284
-
285
- return new Promise<GitCloneResult>((resolve, reject) => {
286
- let isPrivate = false
287
- let finished: (success: boolean, message?: string) => void
288
-
289
- const timeoutObj = setTimeout(() => {
290
- logger.error("Rejected gitClone: 'Timeout'")
291
- finished(false, "Timeout")
292
- }, timeout)
293
-
294
- finished = (success: boolean, message?: string) => {
295
- socket.removeAllListeners("git-clone")
296
- socket.removeAllListeners("git-error")
297
- socket.removeAllListeners("git-success")
298
-
299
- if (success) {
300
- const result: GitCloneResult = {
301
- isPrivate: isPrivate
302
- }
303
-
304
- resolve(result)
305
- } else {
306
- logger.error("Rejected gitClone:", url, dir, branch, message)
307
- reject(message)
308
- }
309
-
310
- clearTimeout(timeoutObj)
311
- }
312
-
313
- const sendRequest = (includeAuth: boolean = false) => {
314
- const data: GitCloneData = { dir: dir, url: url, branch: branch }
315
-
316
- if (includeAuth) {
317
- if (!this.ghToken) {
318
- logger.error("No GitHub token set, can't authenticate")
319
- return finished(false, "Authentication is required, but no GitHub token was set. Can't clone!")
320
- }
321
-
322
- isPrivate = true
323
- data.authkey = this.ghToken
324
- }
325
-
326
- socket.emit("git-clone", data)
327
- }
328
-
329
- socket.once("git-clone", (data) => {
330
- logger.log(`Cloning ${data}`)
331
- })
332
-
333
- socket.once("git-success", () => {
334
- logger.log("Project successfully cloned")
335
- finished(true)
336
- })
337
-
338
- socket.on("git-error", (message) => {
339
- if (message === "Remote authentication required but no callback set") {
340
- logger.log(`Remote authentication required, trying again with authkey: ${dir}`)
341
- sendRequest(true)
342
- } else {
343
- logger.log("Error cloning repo:", url, dir, branch, message)
344
- finished(false, message)
345
- }
346
- })
347
-
348
- sendRequest()
349
- })
350
- })
351
- }
352
-
353
-
354
- /**
355
- * Sets up the console listener worker
356
- *
357
- * @internal
358
- */
359
- setupConsoleListener() {
360
- this.verifyPool().then(() => {
361
- this.pool.run((worker) => {
362
- const logger = worker.logger
363
- logger.log("Running setupConsoleListener")
364
-
365
- return new Promise<void>((resolve) => {
366
- worker.socket.on("console", (data: ConsoleMessage) => {
367
- const line = data.line
368
-
369
- if (this.consoleCallbacks.length == 0) {
370
- return resolve()
371
- }
372
-
373
- this.consoleCallbacks.forEach((callback) => {
374
- try {
375
- callback(line)
376
- } catch(e) {
377
- logger.error("Failed to run console callback", e)
378
- }
379
- })
380
- })
381
- })
382
- })
383
- })
384
- }
385
-
386
-
387
- /**
388
- * Adds a new callback that will run any time a console message is rececived
389
- *
390
- * @param callback The callback to run, takes a single param, `message`, a string
391
- *
392
- * @public
393
- */
394
- addConsoleListener(callback: (message: string) => void) {
395
- if (this.consoleCallbacks.length == 0) {
396
- this.setupConsoleListener()
397
- }
398
-
399
- this.consoleCallbacks.push(callback)
400
- }
401
-
402
-
403
- /**
404
- * Removes a previously added console message callback
405
- *
406
- * @param callback The callback function that was previously added
407
- *
408
- * @public
409
- */
410
- removeConsoleListener(callback: (message: string) => void) {
411
- const index = this.consoleCallbacks.indexOf(callback)
412
- if (index == -1) { return }
413
-
414
- this.consoleCallbacks.splice(index, 1)
415
- }
416
-
417
-
418
- /**
419
- * Sends a command to the server and then waits until output with the given prefix is seen in a console message
420
- *
421
- * @example
422
- * Runs a custom lua command that will prefix its output with our nonce, then prints the output from that command
423
- * ```lua
424
- * -- lua/autorun/server/nonce_example.lua
425
- * concommand.Add( "myCommand", function( ply, _, args )
426
- * if IsValid( ply ) then return end
427
- *
428
- * local nonce = args[1]
429
- * print( nonce .. "Command output" )
430
- * end )
431
- * ```
432
- * ```js
433
- * const nonce = "abc123";
434
- * const command = `myCommand "${nonce}"`;
435
- * try {
436
- * const output = await wisp.socket.sendCommandNonce(nonce, command);
437
- * console.log("Output from command:", output);
438
- * catch (error) {
439
- * console.error(error);
440
- * }
441
- * ```
442
- *
443
- * @remarks
444
- * ℹ️ This is useful if you run code on your Server that will print output with the same prefix, letting you run commands and also receive output for it
445
- *
446
- * @param nonce The short, unique string that your output will be prefixed with
447
- * @param command The full command string to send
448
- * @param timeout In milliseconds, how long to wait for output before timing out
449
- *
450
- * @public
451
- */
452
- async sendCommandNonce(nonce: string, command: string, timeout: number = 1000) {
453
- await this.verifyPool()
454
-
455
- return await this.pool.run((worker) => {
456
- const socket = worker.socket
457
- const logger = worker.logger
458
- logger.log("Running sendCommandNonce: ", nonce, command)
459
-
460
- return new Promise<string>((resolve: Function, reject: Function) => {
461
- let output = ""
462
- let callback: (data: ConsoleMessage) => void
463
-
464
- const timeoutObj = setTimeout(() => {
465
- logger.error(`Command timed out current output: '${output}'`)
466
- socket.off("console", callback)
467
- logger.log("Rejected sendCommandNonce 'Timeout'", nonce, command)
468
- reject("Timeout")
469
- }, timeout)
470
-
471
- callback = (data: ConsoleMessage) => {
472
- const line = data.line
473
- const clean = stripAnsi(line)
474
-
475
- if (clean.startsWith(nonce)) {
476
- const message = clean.slice(nonce.length)
477
-
478
- if (message === "Done.") {
479
- socket.off("console", callback)
480
- clearTimeout(timeoutObj)
481
-
482
- resolve(output)
483
- } else {
484
- output += message
485
- timeoutObj.refresh()
486
- }
487
- }
488
- }
489
-
490
- socket.on("console", callback)
491
- socket.emit("send command", command)
492
- })
493
- })
494
- }
495
- }