This commit is contained in:
SavaletDev
2022-06-07 16:44:52 +02:00
parent db5be50ed4
commit ffaeefa89e
298 changed files with 5914 additions and 5255 deletions

View File

@@ -696,3 +696,110 @@
[2022-06-06 18:41:17] [DEBUG] GET from : 127.0.0.1
[2022-06-06 18:41:40] [DEBUG] GET from : 127.0.0.1
[2022-06-06 18:42:6] [DEBUG] GET from : 127.0.0.1
[2022-06-07 16:28:55]
███╗ ███╗███████╗██████╗ ██████╗██╗ ██╗██████╗ ██╗ ██╗ ██████╗██╗ ██████╗ ██╗ ██╗██████╗ █████╗ ██████╗ ██╗
████╗ ████║██╔════╝██╔══██╗██╔════╝██║ ██║██╔══██╗╚██╗ ██╔╝ ██╔════╝██║ ██╔═══██╗██║ ██║██╔══██╗ ██╔══██╗██╔══██╗██║
██╔████╔██║█████╗ ██████╔╝██║ ██║ ██║██████╔╝ ╚████╔╝ ██║ ██║ ██║ ██║██║ ██║██║ ██║ ███████║██████╔╝██║
██║╚██╔╝██║██╔══╝ ██╔══██╗██║ ██║ ██║██╔══██╗ ╚██╔╝ ██║ ██║ ██║ ██║██║ ██║██║ ██║ ██╔══██║██╔═══╝ ██║
██║ ╚═╝ ██║███████╗██║ ██║╚██████╗╚██████╔╝██║ ██║ ██║ ╚██████╗███████╗╚██████╔╝╚██████╔╝██████╔╝ ██║ ██║██║ ██║
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
[2022-06-07 16:29:5] [ERROR] Database error !
Error: connect ETIMEDOUT
at Connection._handleConnectTimeout (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/Connection.js:409:13)
at Object.onceWrapper (node:events:645:28)
at Socket.emit (node:events:526:28)
at Socket._onTimeout (node:net:502:8)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7)
--------------------
at Protocol._enqueue (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/protocol/Protocol.js:144:48)
at Protocol.handshake (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/protocol/Protocol.js:51:23)
at Connection.connect (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/Connection.js:116:18)
at Object.<anonymous> (/home/savalet/Documents/MercuryCloud_Dashboard/server.js:44:12)
at Module._compile (node:internal/modules/cjs/loader:1103:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1157:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
at node:internal/main/run_main_module:17:47
[2022-06-07 16:30:18]
███╗ ███╗███████╗██████╗ ██████╗██╗ ██╗██████╗ ██╗ ██╗ ██████╗██╗ ██████╗ ██╗ ██╗██████╗ █████╗ ██████╗ ██╗
████╗ ████║██╔════╝██╔══██╗██╔════╝██║ ██║██╔══██╗╚██╗ ██╔╝ ██╔════╝██║ ██╔═══██╗██║ ██║██╔══██╗ ██╔══██╗██╔══██╗██║
██╔████╔██║█████╗ ██████╔╝██║ ██║ ██║██████╔╝ ╚████╔╝ ██║ ██║ ██║ ██║██║ ██║██║ ██║ ███████║██████╔╝██║
██║╚██╔╝██║██╔══╝ ██╔══██╗██║ ██║ ██║██╔══██╗ ╚██╔╝ ██║ ██║ ██║ ██║██║ ██║██║ ██║ ██╔══██║██╔═══╝ ██║
██║ ╚═╝ ██║███████╗██║ ██║╚██████╗╚██████╔╝██║ ██║ ██║ ╚██████╗███████╗╚██████╔╝╚██████╔╝██████╔╝ ██║ ██║██║ ██║
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
[2022-06-07 16:30:28] [ERROR] Database error !
Error: connect ETIMEDOUT
at Connection._handleConnectTimeout (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/Connection.js:409:13)
at Object.onceWrapper (node:events:645:28)
at Socket.emit (node:events:526:28)
at Socket._onTimeout (node:net:502:8)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7)
--------------------
at Protocol._enqueue (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/protocol/Protocol.js:144:48)
at Protocol.handshake (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/protocol/Protocol.js:51:23)
at Connection.connect (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/Connection.js:116:18)
at Object.<anonymous> (/home/savalet/Documents/MercuryCloud_Dashboard/server.js:44:12)
at Module._compile (node:internal/modules/cjs/loader:1103:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1157:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
at node:internal/main/run_main_module:17:47
[2022-06-07 16:31:10]
███╗ ███╗███████╗██████╗ ██████╗██╗ ██╗██████╗ ██╗ ██╗ ██████╗██╗ ██████╗ ██╗ ██╗██████╗ █████╗ ██████╗ ██╗
████╗ ████║██╔════╝██╔══██╗██╔════╝██║ ██║██╔══██╗╚██╗ ██╔╝ ██╔════╝██║ ██╔═══██╗██║ ██║██╔══██╗ ██╔══██╗██╔══██╗██║
██╔████╔██║█████╗ ██████╔╝██║ ██║ ██║██████╔╝ ╚████╔╝ ██║ ██║ ██║ ██║██║ ██║██║ ██║ ███████║██████╔╝██║
██║╚██╔╝██║██╔══╝ ██╔══██╗██║ ██║ ██║██╔══██╗ ╚██╔╝ ██║ ██║ ██║ ██║██║ ██║██║ ██║ ██╔══██║██╔═══╝ ██║
██║ ╚═╝ ██║███████╗██║ ██║╚██████╗╚██████╔╝██║ ██║ ██║ ╚██████╗███████╗╚██████╔╝╚██████╔╝██████╔╝ ██║ ██║██║ ██║
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
[2022-06-07 16:31:20] [ERROR] Database error !
Error: connect ETIMEDOUT
at Connection._handleConnectTimeout (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/Connection.js:409:13)
at Object.onceWrapper (node:events:645:28)
at Socket.emit (node:events:526:28)
at Socket._onTimeout (node:net:502:8)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7)
--------------------
at Protocol._enqueue (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/protocol/Protocol.js:144:48)
at Protocol.handshake (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/protocol/Protocol.js:51:23)
at Connection.connect (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/Connection.js:116:18)
at Object.<anonymous> (/home/savalet/Documents/MercuryCloud_Dashboard/server.js:44:12)
at Module._compile (node:internal/modules/cjs/loader:1103:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1157:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
at node:internal/main/run_main_module:17:47
[2022-06-07 16:31:47]
███╗ ███╗███████╗██████╗ ██████╗██╗ ██╗██████╗ ██╗ ██╗ ██████╗██╗ ██████╗ ██╗ ██╗██████╗ █████╗ ██████╗ ██╗
████╗ ████║██╔════╝██╔══██╗██╔════╝██║ ██║██╔══██╗╚██╗ ██╔╝ ██╔════╝██║ ██╔═══██╗██║ ██║██╔══██╗ ██╔══██╗██╔══██╗██║
██╔████╔██║█████╗ ██████╔╝██║ ██║ ██║██████╔╝ ╚████╔╝ ██║ ██║ ██║ ██║██║ ██║██║ ██║ ███████║██████╔╝██║
██║╚██╔╝██║██╔══╝ ██╔══██╗██║ ██║ ██║██╔══██╗ ╚██╔╝ ██║ ██║ ██║ ██║██║ ██║██║ ██║ ██╔══██║██╔═══╝ ██║
██║ ╚═╝ ██║███████╗██║ ██║╚██████╗╚██████╔╝██║ ██║ ██║ ╚██████╗███████╗╚██████╔╝╚██████╔╝██████╔╝ ██║ ██║██║ ██║
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
[2022-06-07 16:31:57] [ERROR] Database error !
Error: connect ETIMEDOUT
at Connection._handleConnectTimeout (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/Connection.js:409:13)
at Object.onceWrapper (node:events:645:28)
at Socket.emit (node:events:526:28)
at Socket._onTimeout (node:net:502:8)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7)
--------------------
at Protocol._enqueue (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/protocol/Protocol.js:144:48)
at Protocol.handshake (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/protocol/Protocol.js:51:23)
at Connection.connect (/home/savalet/Documents/MercuryCloud_Dashboard/node_modules/mysql/lib/Connection.js:116:18)
at Object.<anonymous> (/home/savalet/Documents/MercuryCloud_Dashboard/server.js:47:12)
at Module._compile (node:internal/modules/cjs/loader:1103:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1157:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
at node:internal/main/run_main_module:17:47

12
node_modules/.bin/color-support generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../color-support/bin.js" "$@"
else
exec node "$basedir/../color-support/bin.js" "$@"
fi

1
node_modules/.bin/color-support generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../color-support/bin.js

17
node_modules/.bin/color-support.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\color-support\bin.js" %*

28
node_modules/.bin/color-support.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../color-support/bin.js" $args
} else {
& "$basedir/node$exe" "$basedir/../color-support/bin.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../color-support/bin.js" $args
} else {
& "node$exe" "$basedir/../color-support/bin.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
node_modules/.bin/mime generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../mime/cli.js" "$@"
else
exec node "$basedir/../mime/cli.js" "$@"
fi

1
node_modules/.bin/mime generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../mime/cli.js

17
node_modules/.bin/mime.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mime\cli.js" %*

28
node_modules/.bin/mime.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../mime/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../mime/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../mime/cli.js" $args
} else {
& "node$exe" "$basedir/../mime/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
node_modules/.bin/mkdirp generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@"
else
exec node "$basedir/../mkdirp/bin/cmd.js" "$@"
fi

1
node_modules/.bin/mkdirp generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../mkdirp/bin/cmd.js

17
node_modules/.bin/mkdirp.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mkdirp\bin\cmd.js" %*

28
node_modules/.bin/mkdirp.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
} else {
& "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
} else {
& "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
node_modules/.bin/node-pre-gyp generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../@mapbox/node-pre-gyp/bin/node-pre-gyp" "$@"
else
exec node "$basedir/../@mapbox/node-pre-gyp/bin/node-pre-gyp" "$@"
fi

1
node_modules/.bin/node-pre-gyp generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../@mapbox/node-pre-gyp/bin/node-pre-gyp

17
node_modules/.bin/node-pre-gyp.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\@mapbox\node-pre-gyp\bin\node-pre-gyp" %*

28
node_modules/.bin/node-pre-gyp.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../@mapbox/node-pre-gyp/bin/node-pre-gyp" $args
} else {
& "$basedir/node$exe" "$basedir/../@mapbox/node-pre-gyp/bin/node-pre-gyp" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../@mapbox/node-pre-gyp/bin/node-pre-gyp" $args
} else {
& "node$exe" "$basedir/../@mapbox/node-pre-gyp/bin/node-pre-gyp" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
node_modules/.bin/nopt generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../nopt/bin/nopt.js" "$@"
else
exec node "$basedir/../nopt/bin/nopt.js" "$@"
fi

1
node_modules/.bin/nopt generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../nopt/bin/nopt.js

17
node_modules/.bin/nopt.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nopt\bin\nopt.js" %*

28
node_modules/.bin/nopt.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../nopt/bin/nopt.js" $args
} else {
& "$basedir/node$exe" "$basedir/../nopt/bin/nopt.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../nopt/bin/nopt.js" $args
} else {
& "node$exe" "$basedir/../nopt/bin/nopt.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
node_modules/.bin/rimraf generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../rimraf/bin.js" "$@"
else
exec node "$basedir/../rimraf/bin.js" "$@"
fi

1
node_modules/.bin/rimraf generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../rimraf/bin.js

17
node_modules/.bin/rimraf.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rimraf\bin.js" %*

28
node_modules/.bin/rimraf.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../rimraf/bin.js" $args
} else {
& "$basedir/node$exe" "$basedir/../rimraf/bin.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../rimraf/bin.js" $args
} else {
& "node$exe" "$basedir/../rimraf/bin.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
node_modules/.bin/semver generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../semver/bin/semver.js" "$@"
else
exec node "$basedir/../semver/bin/semver.js" "$@"
fi

1
node_modules/.bin/semver generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../semver/bin/semver.js

17
node_modules/.bin/semver.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver.js" %*

28
node_modules/.bin/semver.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
node_modules/.bin/sshpk-conv generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../sshpk/bin/sshpk-conv" "$@"
else
exec node "$basedir/../sshpk/bin/sshpk-conv" "$@"
fi

1
node_modules/.bin/sshpk-conv generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../sshpk/bin/sshpk-conv

17
node_modules/.bin/sshpk-conv.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sshpk\bin\sshpk-conv" %*

28
node_modules/.bin/sshpk-conv.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-conv" $args
} else {
& "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-conv" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../sshpk/bin/sshpk-conv" $args
} else {
& "node$exe" "$basedir/../sshpk/bin/sshpk-conv" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
node_modules/.bin/sshpk-sign generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../sshpk/bin/sshpk-sign" "$@"
else
exec node "$basedir/../sshpk/bin/sshpk-sign" "$@"
fi

1
node_modules/.bin/sshpk-sign generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../sshpk/bin/sshpk-sign

17
node_modules/.bin/sshpk-sign.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sshpk\bin\sshpk-sign" %*

28
node_modules/.bin/sshpk-sign.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-sign" $args
} else {
& "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-sign" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../sshpk/bin/sshpk-sign" $args
} else {
& "node$exe" "$basedir/../sshpk/bin/sshpk-sign" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
node_modules/.bin/sshpk-verify generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../sshpk/bin/sshpk-verify" "$@"
else
exec node "$basedir/../sshpk/bin/sshpk-verify" "$@"
fi

1
node_modules/.bin/sshpk-verify generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../sshpk/bin/sshpk-verify

17
node_modules/.bin/sshpk-verify.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sshpk\bin\sshpk-verify" %*

28
node_modules/.bin/sshpk-verify.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-verify" $args
} else {
& "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-verify" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../sshpk/bin/sshpk-verify" $args
} else {
& "node$exe" "$basedir/../sshpk/bin/sshpk-verify" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
node_modules/.bin/uuid generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../uuid/dist/bin/uuid" "$@"
else
exec node "$basedir/../uuid/dist/bin/uuid" "$@"
fi

1
node_modules/.bin/uuid generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../uuid/dist/bin/uuid

17
node_modules/.bin/uuid.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\uuid\dist\bin\uuid" %*

28
node_modules/.bin/uuid.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../uuid/dist/bin/uuid" $args
} else {
& "$basedir/node$exe" "$basedir/../uuid/dist/bin/uuid" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../uuid/dist/bin/uuid" $args
} else {
& "node$exe" "$basedir/../uuid/dist/bin/uuid" $args
}
$ret=$LASTEXITCODE
}
exit $ret

345
node_modules/.package-lock.json generated vendored
View File

@@ -5,12 +5,12 @@
"requires": true,
"packages": {
"node_modules/@devnote-dev/pterojs": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@devnote-dev/pterojs/-/pterojs-1.2.0.tgz",
"integrity": "sha512-Wjres4qZ0pq98fiQ6LYrIjNEa+Qesn1/o4dQffBXcrhDcvJ8yCP8fCRTTb3LvhbLtlAP6Th03Yf2lkjRZGIECA==",
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@devnote-dev/pterojs/-/pterojs-1.4.2.tgz",
"integrity": "sha512-XmIjhyy5QF8Bg27nn6sUEMDx/2vGryR0glBk4rpK88unwXOIXCzR9DLep96rMS9LtUImPldevQ2Csi+jQDqqkw==",
"dependencies": {
"node-fetch": "^2.6.1",
"ws": "^8.2.3"
"node-fetch": "^2.6.7",
"ws": "^8.5.0"
}
},
"node_modules/@devnote-dev/pterojs/node_modules/node-fetch": {
@@ -175,7 +175,7 @@
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"node_modules/asn1": {
"version": "0.2.6",
@@ -188,7 +188,7 @@
"node_modules/assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"engines": {
"node": ">=0.8"
}
@@ -196,12 +196,12 @@
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
"engines": {
"node": "*"
}
@@ -232,7 +232,7 @@
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"dependencies": {
"tweetnacl": "^0.14.3"
}
@@ -246,23 +246,26 @@
}
},
"node_modules/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz",
"integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==",
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
"integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.8.1",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.9.7",
"raw-body": "2.4.3",
"type-is": "~1.6.18"
"on-finished": "2.4.1",
"qs": "6.10.3",
"raw-body": "2.5.1",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/brace-expansion": {
@@ -287,10 +290,22 @@
"node": ">= 0.8"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
},
"node_modules/chownr": {
"version": "2.0.0",
@@ -349,9 +364,9 @@
}
},
"node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"engines": {
"node": ">= 0.6"
}
@@ -359,12 +374,12 @@
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/cross-fetch": {
"version": "3.1.5",
@@ -402,7 +417,7 @@
"node_modules/dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
"dependencies": {
"assert-plus": "^1.0.0"
},
@@ -429,7 +444,7 @@
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
@@ -440,17 +455,21 @@
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
},
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"engines": {
"node": ">= 0.6"
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-libc": {
"version": "2.0.1",
@@ -463,7 +482,7 @@
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
"dependencies": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
@@ -480,7 +499,7 @@
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
@@ -490,7 +509,7 @@
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
@@ -498,48 +517,49 @@
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.17.3",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz",
"integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
"integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.19.2",
"body-parser": "1.20.0",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.4.2",
"cookie": "0.5.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"finalhandler": "1.2.0",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.7",
"qs": "6.9.7",
"qs": "6.10.3",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.17.2",
"serve-static": "1.14.2",
"send": "0.18.0",
"serve-static": "1.15.0",
"setprototypeof": "1.2.0",
"statuses": "~1.5.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
@@ -556,7 +576,7 @@
"node_modules/extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
"engines": [
"node >=0.6.0"
]
@@ -572,9 +592,9 @@
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"node_modules/fetch-blob": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz",
"integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz",
"integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==",
"funding": [
{
"type": "github",
@@ -594,16 +614,16 @@
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"statuses": "2.0.1",
"unpipe": "~1.0.0"
},
"engines": {
@@ -613,7 +633,7 @@
"node_modules/forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
"engines": {
"node": "*"
}
@@ -653,7 +673,7 @@
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"engines": {
"node": ">= 0.6"
}
@@ -661,7 +681,7 @@
"node_modules/fs": {
"version": "0.0.1-security",
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
"integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ="
"integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="
},
"node_modules/fs-minipass": {
"version": "2.1.0",
@@ -679,6 +699,11 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
@@ -698,10 +723,23 @@
"node": ">=10"
}
},
"node_modules/get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
"dependencies": {
"assert-plus": "^1.0.0"
}
@@ -728,7 +766,7 @@
"node_modules/har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==",
"engines": {
"node": ">=4"
}
@@ -746,30 +784,52 @@
"node": ">=6"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"node_modules/http-errors": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
"integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"dependencies": {
"depd": "~1.1.2",
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": ">= 1.5.0 < 2",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.6"
"node": ">= 0.8"
}
},
"node_modules/http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
"dependencies": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@@ -862,7 +922,7 @@
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"node_modules/isarray": {
"version": "1.0.0",
@@ -872,12 +932,12 @@
"node_modules/isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
},
"node_modules/jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
},
"node_modules/json-schema": {
"version": "0.4.0",
@@ -892,7 +952,7 @@
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
},
"node_modules/jsonwebtoken": {
"version": "8.5.1",
@@ -1032,7 +1092,7 @@
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"engines": {
"node": ">= 0.6"
}
@@ -1040,12 +1100,12 @@
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"engines": {
"node": ">= 0.6"
}
@@ -1062,19 +1122,19 @@
}
},
"node_modules/mime-db": {
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.34",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.51.0"
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
@@ -1128,7 +1188,7 @@
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/mysql": {
"version": "2.18.1",
@@ -1181,9 +1241,9 @@
}
},
"node_modules/node-fetch": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.0.tgz",
"integrity": "sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw==",
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.5.tgz",
"integrity": "sha512-u7zCHdJp8JXBwF09mMfo2CL6kp37TslDl1KP3hRGTlCInBtag+UO3LGVy+NF0VzvnL3PVMpA2hXh1EtECFnyhQ==",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
@@ -1238,10 +1298,18 @@
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"dependencies": {
"ee-first": "1.1.1"
},
@@ -1276,12 +1344,12 @@
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
@@ -1314,9 +1382,12 @@
}
},
"node_modules/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
"version": "6.10.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
"integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
@@ -1333,12 +1404,12 @@
}
},
"node_modules/raw-body": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz",
"integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "1.8.1",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
@@ -1368,7 +1439,7 @@
"node_modules/readline": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz",
"integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw="
"integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
},
"node_modules/request": {
"version": "2.88.2",
@@ -1471,23 +1542,23 @@
}
},
"node_modules/send": {
"version": "0.17.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
"integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"dependencies": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "1.8.1",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "~2.3.0",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
"statuses": "2.0.1"
},
"engines": {
"node": ">= 0.8.0"
@@ -1499,14 +1570,14 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/serve-static": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
"integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"dependencies": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.17.2"
"send": "0.18.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -1522,6 +1593,19 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -1560,11 +1644,11 @@
}
},
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"engines": {
"node": ">= 0.6"
"node": ">= 0.8"
}
},
"node_modules/string_decoder": {
@@ -1731,10 +1815,15 @@
"extsprintf": "^1.2.0"
}
},
"node_modules/verror/node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
},
"node_modules/web-streams-polyfill": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
"integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"engines": {
"node": ">= 8"
}
@@ -1767,9 +1856,9 @@
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"node_modules/ws": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
"integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.7.0.tgz",
"integrity": "sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg==",
"engines": {
"node": ">=10.0.0"
},

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Devonte
Copyright (c) 2021-2022 Devonte
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,8 +1,6 @@
<p align="center"><img src="https://cdn.discordapp.com/icons/878103328351977533/c9be4583e92d43efd1df18719f91ec37.webp?size=256" alt="pterojs-logo"></p>
<h1 align="center">PteroJS</h1>
<h3 align="center"><strong>A better API wrapper for Pterodactyl</strong></h3>
<h4 align="center"><i>Logo design by aizakkusnail#1065 (Discord)</i></h4>
<p align="center"><img src="https://img.shields.io/badge/discord-invite-5865f2?style=for-the-badge&logo=discord&logoColor=white"> <img src="https://img.shields.io/badge/version-1.2.0-3572A5?style=for-the-badge"> <img src="https://img.shields.io/github/issues/devnote-dev/PteroJS.svg?style=for-the-badge"> <img src="https://img.shields.io/badge/docs-coming_soon-e67e22?style=for-the-badge"></p>
<p align="center"><img src="https://img.shields.io/badge/discord-invite-5865f2?style=for-the-badge&logo=discord&logoColor=white"> <img src="https://img.shields.io/badge/version-1.4.2-3572A5?style=for-the-badge"> <img src="https://img.shields.io/github/issues/devnote-dev/PteroJS.svg?style=for-the-badge"> <img src="https://img.shields.io/badge/docs-coming_soon-e67e22?style=for-the-badge"></p>
## About
PteroJS is a flexible API wrapper designed to give developers full access over the Pterodactyl API. The library uses a class-based management structure often seen in popular packages like Discord.js which keeps code clean, efficient and practical for any use-case.
@@ -23,9 +21,6 @@ const { PteroApp } = require('@devnote-dev/pterojs');
// Initialising the application
const client = new PteroApp('your.domain.here', 'pterodactyl_api_key');
// Connecting to Pterodactyl
client.connect();
// Accessing information
client.servers.fetch('evuk98yu').then(console.log);
```
@@ -41,16 +36,16 @@ const client = new PteroClient(
{ ws: true }
);
// Adding servers to listen for
client.addSocksetServer([ 'kgujg66h', 'avipgt6e' ]);
// Adding the server to listen for
const shard = client.addSocksetServer('kgujg66h');
// Listening to events
client.on('statusUpdate', (server, status) => {
console.log(`${server.name} status: ${status}`);
shard.on('statusUpdate', status => {
console.log(`server ${shard.id} status: ${status}`);
});
// Connecting to Pterodactyl
client.connect();
// Connecting to the server
shard.connect();
```
## Contributing
@@ -63,10 +58,11 @@ Please see the [todo list](https://github.com/PteroPackages/PteroJS/blob/main/TO
## Contributors
* [Devonte](https://github.com/devnote-dev) - Owner, maintainer
* [Chelog](https://github.com/chelog) - Code contributor
* [Cain](https://github.com/cainthebest) - Code contributor
* [Zumo](https://github.com/ZumoDev) - Tester
* [Dino](https://github.com/DinoTheDevOfficial) - Tester
* [Cain](https://github.com/cainthebest) - Code contributor
This repository is managed under the MIT license.
© 2021 devnote-dev
© 2021-2022 devnote-dev

View File

@@ -21,7 +21,7 @@ Want to contribute? Familiar with JS? You're already halfway there. Below are th
## Global Managers
- [X] Implement helper functions for all the managers
- [X] Create and implement all necessary submanagers ([Dashflo](https://dashflo.net/docs/api/pterodactyl/v1/#req_dc39cc65e67d47bd8fb37449a8559935))
- [ ] Document all functions
- [X] Document all functions (resolved into others)
- [X] Switch `AllocationManager#cache` to maps
## Global Structures
@@ -30,10 +30,12 @@ Want to contribute? Familiar with JS? You're already halfway there. Below are th
## Misc.
- [X] Add proper notes and annotations to JSDocs
- [ ] Overall testing of the package (priority)
- [X] Overall testing of the package (priority)
- [X] TypeScript support (`index.d.ts`)
- [X] Investigate incorrectly documented endpoints
- [ ] Implement tests in `/tests` (or move from `/test`)
- [X] Implement tests in `/tests` (or move from `/test`)
- [X] Remove deprecated `PteroUser#tfa`
- [X] Remove deprecated `Presets` util
## Feature Plans
- [ ] Optional webhook client

View File

@@ -1,6 +1,6 @@
{
"name": "@devnote-dev/pterojs",
"version": "1.2.0",
"version": "1.4.2",
"description": "A flexibile API wrapper for Pterodactyl",
"main": "src/index.js",
"scripts": {
@@ -23,7 +23,7 @@
},
"homepage": "https://github.com/PteroPackages/PteroJS#readme",
"dependencies": {
"node-fetch": "^2.6.1",
"ws": "^8.2.3"
"node-fetch": "^2.6.7",
"ws": "^8.5.0"
}
}

View File

@@ -1,69 +0,0 @@
const fetch = require('node-fetch');
const { RequestError, PteroAPIError } = require('../structures/Errors');
const { version } = require('../../package.json');
/**
* The requests manager for the application API. This is not for public use. Using
* this manually may result in unwanted modifications of your Pterodactyl panel.
*/
class ApplicationRequestManager {
constructor(client) {
this.client = client
this.headers['Authorization'] = `Bearer ${client.auth}`;
}
suspended = false;
headers = {
'User-Agent': `Application PteroJS v${version}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
/** Sends a request to the Pterodactyl API. Returns a json object or an error if
* an unknown response code is received.
* @param {string} path The path to request.
* @param {?object} [params] Optional payload data (POST, PUT and PATCH).
* @param {string} [method] The method or HTTP verb to use.
* @returns {Promise<object|void>} The response object, if any.
*/
async make(path, params, method = 'GET') {
if (this.client.ping === null) throw new Error('Attempted request before application was ready.');
if (this.suspended) throw new RequestError('[429] Application is ratelimited.');
const body = params?.raw ?? (params ? JSON.stringify(params) : null);
const data = await fetch(this.client.domain + path, {
method,
body,
headers: this.headers
});
if ([201, 204].includes(data.status)) return;
if (data.status === 200) return await data.json();
if ([400, 404, 422].includes(data.status)) throw new PteroAPIError(await data.json());
if (data.status === 401) throw new RequestError('[401] Unauthorized API request.');
if (data.status === 403) throw new RequestError('[403] API Path forbidden.');
if (data.status === 429) {
this.suspended = true;
setTimeout(() => this.suspended = false, 600000);
throw new RequestError('[429] Application is ratelimited, retrying in 10 minutes.');
}
throw new RequestError(`Pterodactyl API returned an invalid or malformed payload (code: ${data.status}).`);
}
/**
* Sends a ping request to the Pterodactyl API.
* Because the API has no "ping" endpoint, we use a forced error to receive a 404 response
* then check that it is a valid 404 error by the API and not a timeout response.
* @returns {Promise<boolean>}
*/
async ping() {
try {
this.client.ping = -1;
await this.make('/api/application');
} catch (err) {
if (!err?.code) throw new RequestError('Pterodactyl API is unavailable.');
return true;
}
}
}
module.exports = ApplicationRequestManager;

View File

@@ -1,9 +1,38 @@
const ApplicationServer = require('../structures/ApplicationServer');
const Dict = require('../structures/Dict');
const { PteroUser } = require('../structures/User');
const build = require('../util/query');
const endpoints = require('./endpoints');
class ApplicationServerManager {
/**
* Allowed filter arguments for application servers.
*/
static get FILTERS() {
return Object.freeze([
'name', 'uuid', 'uuidShort',
'externalId', 'image'
]);
}
/**
* Allowed include arguments for application servers.
*/
static get INCLUDES() {
return Object.freeze([
'allocations', 'user', 'subusers',
'nest', 'egg', 'variables',
'location', 'node', 'databases'
]);
}
/**
* Allowed sort arguments for application servers.
*/
static get SORTS() {
return Object.freeze(['id', '-id', 'uuid', '-uuid']);
}
constructor(client) {
this.client = client;
this.cache = new Dict();
@@ -37,6 +66,7 @@ class ApplicationServerManager {
if (this.client.options.servers.cache) res.forEach((v, k) => this.cache.set(k, v));
return res;
}
const s = new ApplicationServer(this.client, data.attributes);
if (this.client.options.servers.cache) this.cache.set(s.id, s);
return s;
@@ -48,16 +78,36 @@ class ApplicationServerManager {
* * a number
* * an object
*
* Returns `null` if not found.
* Returns `undefined` if not found.
* @param {string|number|object|ApplicationServer} obj The object to resolve from.
* @returns {?ApplicationServer} The resolved server.
*/
resolve(obj) {
if (obj instanceof ApplicationServer) return obj;
if (typeof obj === 'number') return this.cache.get(obj) || null;
if (typeof obj === 'string') return this.cache.find(s => s.name === obj) || null;
if (typeof obj === 'number') return this.cache.get(obj);
if (typeof obj === 'string') return this.cache.find(s => s.name === obj);
if (obj.relationships?.servers) return this._patch(obj.relationships.servers);
return null;
return undefined;
}
/**
* Returns a formatted URL to the server.
* @param {string|ApplicationServer} server The server or server identifier.
* @returns {string} The formatted URL.
*/
panelURLFor(server) {
if (server instanceof ApplicationServer) return server.panelURL;
return `${this.client.domain}/server/${server}`;
}
/**
* Returns a formatted URL to the server in the admin panel.
* @param {number|ApplicationServer} server The server or server ID.
* @returns {string} The formatted URL.
*/
adminURLFor(server) {
if (server instanceof ApplicationServer) return server.adminURL;
return `${this.client.domain}/admin/servers/view/${server}`;
}
/**
@@ -69,17 +119,15 @@ class ApplicationServerManager {
* @returns {Promise<ApplicationServer|Dict<number, ApplicationServer>>} The fetched server(s).
*/
async fetch(id, options = {}) {
if (id) {
if (options.force) {
const s = this.cache.get(id);
if (s) return Promise.resolve(s);
}
const data = await this.client.requests.make(
endpoints.servers.get(id) + joinParams(options.include)
);
return this._patch(data);
if (id && !options.force) {
const s = this.cache.get(id);
if (s) return Promise.resolve(s);
}
const data = await this.client.requests.make(endpoints.servers.main);
const query = build(options, { include: ApplicationServerManager.INCLUDES });
const data = await this.client.requests.get(
(id ? endpoints.servers.get(id) : endpoints.servers.main) + query
);
return this._patch(data);
}
@@ -89,7 +137,8 @@ class ApplicationServerManager {
* Available query filters are:
* * name
* * uuid
* * identifier
* * uuidShort
* * identifier (alias for uuidShort)
* * externalId
* * image
*
@@ -98,20 +147,25 @@ class ApplicationServerManager {
* * -id
* * uuid
* * -uuid
*
* @param {string} entity The entity (string) to query.
* @param {string} filter The filter to use for the query.
* @param {string} sort The order to sort the results in.
* @returns {Promise<Dict<number, ApplicationServer>>} A dict of the quiried servers.
*/
async query(entity, filter, sort) {
if (filter && !['name', 'uuid', 'identifier', 'externalId', 'image'].includes(filter)) throw new Error('Invalid query filter.');
if (sort && !['id', '-id', 'uuid', '-uuid'].includes(sort)) throw new Error('Invalid sort type.');
if (!sort && !filter) throw new Error('Sort or filter is required.');
if (filter === 'identifier') filter = 'uuidShort';
if (filter === 'externalId') filter = 'external_id';
const data = await this.client.requests.make(
endpoints.servers.main +
(filter ? `?filter[${filter}]=${entity}` : '') +
(sort && filter ? `&sort=${sort}` : '') +
(sort && !filter ? `?sort=${sort}` : '')
const { FILTERS, SORTS } = ApplicationServerManager;
const query = build(
{ filter:[filter, entity], sort },
{ filters: FILTERS, sorts: SORTS }
);
const data = await this.client.requests.get(
endpoints.servers.main + query
);
return this._patch(data);
}
@@ -151,9 +205,9 @@ class ApplicationServerManager {
payload.limits = options.limits ?? this.defaultLimits;
payload.feature_limits = options.featureLimits ?? this.defaultFeatureLimits;
await this.client.requests.make(endpoints.servers.main, payload, 'POST');
const s = await this.query(payload.name, 'name');
return s.first();
await this.client.requests.post(endpoints.servers.main, payload);
const data = await this.query(payload.name, 'name', '-id');
return data.find(s => s.name === payload.name);
}
/**
@@ -164,8 +218,8 @@ class ApplicationServerManager {
*/
async delete(server, force = false) {
if (server instanceof ApplicationServer) server = server.id;
await this.client.requests.make(
endpoints.servers.get(server) + (force ? '/force' : ''), null, 'DELETE'
await this.client.requests.delete(
endpoints.servers.get(server) + (force ? '/force' : '')
);
this.cache.delete(server);
return true;
@@ -173,14 +227,3 @@ class ApplicationServerManager {
}
module.exports = ApplicationServerManager;
function joinParams(params) {
if (!params || !params.length) return '';
const valid = [
'allocations', 'user', 'subusers',
'pack', 'nest', 'egg', 'variables',
'location', 'node', 'databases'
];
params = params.filter(p => valid.includes(p));
return '?include='+ params.toString();
}

View File

@@ -1,7 +1,18 @@
const Dict = require('../structures/Dict');
const build = require('../util/query');
const endpoints = require('./endpoints');
class NestEggsManager {
/**
* Allowed include arguments for nest eggs.
*/
static get INCLUDES() {
return Object.freeze([
'nest', 'servers', 'config',
'script', 'variables'
]);
}
constructor(client) {
this.client = client;
@@ -9,6 +20,15 @@ class NestEggsManager {
this.cache = new Dict();
}
/**
* Returns a formatted URL to the egg in the admin panel.
* @param {number} id The ID of the egg.
* @returns {string} The formatted URL.
*/
adminURLFor(id) {
return `${this.client.domain}/admin/nests/egg/${id}`;
}
/**
* Fetches the eggs for the specified nest.
* @param {number} nest The ID of the nest to fetch from.
@@ -19,20 +39,16 @@ class NestEggsManager {
* @returns {Promise<object|Dict<number, object>>} The fetched egg(s).
*/
async fetch(nest, id, options = {}) {
if (id) {
if (options.force) {
const e = this.cache.get(id);
if (e) return Promise.resolve(e);
}
const data = await this.client.requests.make(
endpoints.nests.eggs.get(nest, id) + joinParams(options.include)
);
this.cache.set(id, data.data.attributes);
return data.data.attributes;
if (id && !options.force) {
const e = this.cache.get(id);
if (e) return Promise.resolve(e);
}
const data = await this.client.requests.make(
endpoints.nests.eggs.main(nest) + joinParams(options.include)
const query = build(options, { include: NestEggsManager.INCLUDES });
const data = await this.client.requests.get(
(id ? endpoints.nests.eggs.get(nest, id) : endpoints.nests.eggs.main(nest)) + query
);
const res = new Dict();
for (const egg of data.data) {
this.cache.set(egg.attributes.id, egg.attributes);
@@ -54,10 +70,3 @@ class NestEggsManager {
}
module.exports = NestEggsManager;
function joinParams(params) {
if (!params) return '';
const res = [];
params.forEach(p => res.push(['include', p]));
return '?'+ new URLSearchParams(res).toString();
}

View File

@@ -1,12 +1,21 @@
const NestEggsManager = require('./NestEggsManager');
const build = require('../util/query');
const endpoints = require('./endpoints');
class NestManager {
/**
* Allowed include arguments for nests.
*/
static get INCLUDES() {
return Object.freeze(['eggs', 'servers']);
}
constructor(client) {
this.client = client;
/** @type {Set<Nest>} */
this.cache = new Set();
/** @type {NestEggsManager} */
this.eggs = new NestEggsManager(this.client);
}
@@ -26,9 +35,11 @@ class NestManager {
updatedAt: o.updated_at ? new Date(o.updated_at) : null
});
}
if (this.client.options.nests.cache) res.forEach(n => this.cache.add(n));
return res;
}
data = data.attributes;
res.add({
id: data.id,
@@ -39,18 +50,32 @@ class NestManager {
createdAt: new Date(data.created_at),
updatedAt: data.updated_at ? new Date(data.updated_at) : null
});
if (this.client.options.nests.cache) res.forEach(n => this.cache.add(n));
return res;
}
/**
* Fetches a nest from the Pterodactyl API with an optional cache check.
* Returns a formatted URL to the nest in the admin panel.
* @param {number} id The ID of the nest.
* @returns {string} The formatted URL.
*/
adminURLFor(id) {
return `${this.client.domain}/admin/nests/view/${id}`;
}
/**
* Fetches a nest from the Pterodactyl API.
* @param {number} [id] The ID of the nest.
* @param {string[]} [include] Additional data to include about the nest.
* @returns {Promise<Set<Nest>>} The fetched nests.
*/
async fetch(id) {
if (id) return this._patch(await this.client.requests.make(endpoints.nests.get(id)));
return this._patch(await this.client.requests.make(endpoints.nests.main));
async fetch(id, include = []) {
const query = build({ include }, { include: NestManager.INCLUDES });
const data = await this.client.requests.get(
(id ? endpoints.nests.get(id) : endpoints.nests.main) + query
);
return this._patch(data);
}
}
@@ -66,5 +91,4 @@ module.exports = NestManager;
* @property {string} description The description of the nest.
* @property {Date} createdAt The date the nest was created.
* @property {?Date} updatedAt The date the nest was last updated.
* @readonly
*/

View File

@@ -1,7 +1,22 @@
const Dict = require('../structures/Dict');
const build = require('../util/query');
const endpoints = require('./endpoints');
class NodeLocationManager {
/**
* Allowed filter arguments for locations.
*/
static get FILTERS() {
return Object.freeze(['short', 'long']);
}
/**
* Allowed include arguments for locations.
*/
static get INCLUDES() {
return Object.freeze(['nodes', 'servers']);
}
constructor(client) {
this.client = client;
@@ -22,9 +37,11 @@ class NodeLocationManager {
updatedAt: o.updated_at ? new Date(o.updated_at) : null
});
}
if (this.client.options.locations.cache) res.forEach((v, k) => this.cache.set(k, v));
return res;
}
data = data.attributes;
const loc = {
id: data.id,
@@ -33,6 +50,7 @@ class NodeLocationManager {
createdAt: new Date(data.created_at),
updatedAt: data.updated_at ? new Date(data.updated_at) : null
}
if (this.client.options.locations.cache) this.cache.set(data.id, loc);
return loc;
}
@@ -40,34 +58,80 @@ class NodeLocationManager {
/**
* Resolves a node location from an object. This can be:
* * a number
* * a string
* * an object
*
* Returns `null` if not found.
* @param {number|object} obj The object to resolve from.
* Returns `undefined` if not found.
* @param {string|number|object} obj The object to resolve from.
* @returns {?NodeLocation} The resolved node location.
*/
resolve(obj) {
if (typeof obj == 'number') return this.cache.get(obj) || null;
if (obj.relationships?.location?.attributes) return this._patch(obj.relationships.location);
return null;
if (typeof obj === 'number') return this.cache.get(obj);
if (typeof obj === 'string') return this.cache.find(
o => (o.short === obj) || (o.long === obj)
);
if (obj.relationships?.location?.attributes)
return this._patch(obj.relationships.location);
return undefined;
}
/**
* Returns a formatted URL to the node location in the admin panel.
* @param {number} id The ID of the node location.
* @returns {string} The formatted URL.
*/
adminURLFor(id) {
return `${this.client.domain}/admin/locations/view/${id}`;
}
/**
* Fetches a node location from the Pterodactyl API with an optional cache check.
* @param {number} [id] The ID of the location.
* @param {boolean} [force] Whether to skip checking the cache and fetch directly.
* @param {object} [options] Additional fetch options.
* @param {boolean} [options.force] Whether to skip checking the cache and fetch directly.
* @param {string[]} [options.include] Additional data to include about the location.
* @returns {Promise<NodeLocation|Dict<number, NodeLocation>>} The fetched node location(s).
*/
async fetch(id, force = false) {
if (id) {
if (!force) {
const l = this.cache.get(id);
if (l) return Promise.resolve(l);
}
const data = await this.client.requests.make(endpoints.locations.get(id));
return this._patch(data);
async fetch(id, options = {}) {
if (id && !options.force) {
const loc = this.cache.get(id);
if (loc) return Promise.resolve(loc);
}
const data = await this.client.requests.make(endpoints.locations.main);
const query = build(options, { include: NodeLocationManager.INCLUDES });
const data = await this.client.requests.get(
(id ? endpoints.locations.get(id) : endpoints.locations.main) + query
);
return this._patch(data);
}
/**
* Queries the API for a location (or locations) that match the specified query filter/sort.
* This does NOT check the cache first, it is a direct fetch from the API.
* Available filters:
* * short
* * long
*
* Available sort options:
* * id
* * -id
*
* @param {string} entity The entity to query.
* @param {string} [filter] The filter to use for the query.
* @param {string} [sort] The order to sort the results in.
* @returns {Promise<Dict<number, NodeLocation>>} A dict of the queried locations.
*/
async query(entity, filter, sort) {
if (!sort && !filter) throw new Error('Sort or filter is required.');
const query = build(
{ filter:[filter, entity], sort },
{ filters: NodeLocationManager.FILTERS, sorts:['id'] }
);
const data = await this.client.requests.get(
endpoints.locations.main + query
);
return this._patch(data);
}
@@ -79,10 +143,9 @@ class NodeLocationManager {
*/
async create(short, long) {
return this._patch(
await this.client.requests.make(
await this.client.requests.post(
endpoints.locations.main,
{ short, long },
'POST'
{ short, long }
)
);
}
@@ -96,9 +159,11 @@ class NodeLocationManager {
* @returns {Promise<NodeLocation>} The updated node location instance.
*/
async update(id, options) {
if (!options.short && !options.long) throw new Error('Either short or long option is required.');
if (!options.short && !options.long)
throw new Error('Either short or long option is required.');
return this._patch(
await this.client.requests.make(endpoints.locations.get(id), options, 'PATCH')
await this.client.requests.patch(endpoints.locations.get(id), options)
);
}
@@ -108,7 +173,7 @@ class NodeLocationManager {
* @returns {Promise<boolean>}
*/
async delete(id) {
await this.client.requests.make(endpoints.locations.get(id), null, 'DELETE');
await this.client.requests.delete(endpoints.locations.get(id));
this.cache.delete(id);
return true;
}
@@ -118,12 +183,10 @@ module.exports = NodeLocationManager;
/**
* Represents a location on Pterodactyl.
* Location objects have little to no methodic usage so they are readonly.
* @typedef {object} NodeLocation
* @property {number} id The ID of the location.
* @property {string} long The long location code.
* @property {string} short The short location code (or country code).
* @property {Date} createdAt The date the location was created.
* @property {?Date} updatedAt The date the location was last updated.
* @readonly
*/

View File

@@ -1,8 +1,30 @@
const Node = require('../structures/Node');
const Dict = require('../structures/Dict');
const build = require('../util/query');
const endpoints = require('./endpoints');
class NodeManager {
/**
* Allowed filter arguments for nodes.
*/
static get FILTERS() {
return Object.freeze(['uuid', 'name', 'fqdn', 'daemon_token_id']);
}
/**
* Allowed include arguments for nodes.
*/
static get INCLUDES() {
return Object.freeze(['allocations', 'location', 'servers']);
}
/**
* Allowed sort arguments for nodes.
*/
static get SORTS() {
return Object.freeze(['id', 'uuid', 'memory', 'disk']);
}
constructor(client) {
this.client = client;
@@ -17,14 +39,44 @@ class NodeManager {
const n = new Node(this.client, o);
res.set(n.id, n);
}
if (this.client.options.nodes.cache) res.forEach((v, k) => this.cache.set(k, v));
return res;
}
const n = new Node(this.client, data);
if (this.client.options.nodes.cache) this.cache.set(n.id, n);
return n;
}
/**
* Resolves a node from an object. This can be:
* * a string
* * a number
* * an object
*
* Returns `undefined` if not found.
* @param {string|number|object|Node} obj The object to resolve from.
* @returns {?Node} The resolved node.
*/
resolve(obj) {
if (obj instanceof Node) return obj;
if (typeof obj === 'number') return this.cache.get(obj);
if (typeof obj === 'string') return this.cache.find(n => n.name === obj);
if (obj.relationships?.node) return this._patch(obj.relationships.node);
return undefined;
}
/**
* Returns a formatted URL to the node in the admin panel.
* @param {number|Node} node The node or ID of the node.
* @returns {string} The formatted URL.
*/
adminURLFor(node) {
if (node instanceof Node) return node.adminURL;
return `${this.client.domain}/admin/nodes/view/${node}`;
}
/**
* Fetches a node from the Pterodactyl API with an optional cache check.
* @param {number} [id] The ID of the node.
@@ -34,22 +86,56 @@ class NodeManager {
* @returns {Promise<Node|Dict<number, Node>>} The fetched node(s).
*/
async fetch(id, options = {}) {
if (id) {
if (!options.force) {
const n = this.cache.get(id);
if (n) return Promise.resolve(n);
}
const data = await this.client.requests.make(
endpoints.nodes.get(id) + joinParams(options.include)
);
return this._patch(data);
if (id && !options.force) {
const n = this.cache.get(id);
if (n) return Promise.resolve(s);
}
const data = await this.client.requests.make(
endpoints.nodes.main + joinParams(options.include)
const query = build(options, { include: NodeManager.INCLUDES });
const data = await this.client.requests.get(
(id ? endpoints.nodes.get(id) : endpoints.nodes.main) + query
);
return this._patch(data);
}
/**
* Queries the API for a node (or nodes) that match the specified query filter/sort.
* This does NOT check the cache first, it is a direct fetch from the API.
* Available filters:
* * uuid
* * name
* * fqdn
* * daemonTokenId
*
* Available sort options:
* * id
* * -id
* * uuid
* * -uuid
* * memory
* * -memory
* * disk
* * -disk
*
* @param {string} entity The entity to query.
* @param {string} filter The filter to use for the query.
* @param {string} sort The order to sort the results in.
* @returns {Promise<Dict<number, Node>>} A dict of the quiried nodes.
*/
async query(entity, filter, sort) {
if (!sort && !filter) throw new Error('Sort or filter is required.');
if (filter === 'daemonTokenId') filter = 'daemon_token_id';
const { FILTERS, SORTS } = NodeManager;
const query = build(
{ filter:[filter, entity], sort },
{ filters: FILTERS, sorts: SORTS }
);
const data = await this.client.requests.get(endpoints.nodes.main + query);
return this._patch(data);
}
/**
* Creates a new Pterodactyl server node.
* @param {object} options Node creation options.
@@ -91,8 +177,8 @@ class NodeManager {
payload.memory_overallocate = options.memory_overallocate ?? 0;
payload.disk_overallocate = options.disk_overallocate ?? 0;
const data = await this.client.requests.make(
endpoints.nodes.main, payload, 'POST'
const data = await this.client.requests.post(
endpoints.nodes.main, payload
);
return this._patch(data);
}
@@ -125,8 +211,8 @@ class NodeManager {
payload.memory_overallocate = payload.overallocated_memory;
payload.disk_overallocate = payload.overallocated_disk;
const data = await this.client.requests.make(
endpoints.nodes.get(id), payload, 'PATCH'
const data = await this.client.requests.patch(
endpoints.nodes.get(id), payload
);
return this._patch(data);
}
@@ -138,16 +224,10 @@ class NodeManager {
*/
async delete(node) {
if (node instanceof Node) node = node.id;
await this.client.requests.make(endpoints.nodes.get(node), null, 'DELETE');
await this.client.requests.delete(endpoints.nodes.get(node));
this.cache.delete(node);
return true;
}
}
module.exports = NodeManager;
function joinParams(params) {
if (!params || !params.length) return '';
params = params.filter(p => ['allocations', 'location', 'servers'].includes(p));
return '?include='+ params.toString();
}

View File

@@ -1,10 +1,11 @@
const ApplicationRequestManager = require('./ApplicationRequestManager');
const ApplicationServerManager = require('./ApplicationServerManager');
const NestManager = require('./NestManager');
const NodeAllocationManager = require('./NodeAllocationManager');
const NodeLocationManager = require('./NodeLocationManager');
const NodeManager = require('./NodeManager');
const UserManager = require('./UserManager');
const loader = require('../structures/configLoader');
const RequestManager = require('../http/RequestManager');
const loader = require('../util/configLoader');
/**
* The base class for the Pterodactyl application API.
@@ -21,6 +22,12 @@ class PteroApp {
* @param {ApplicationOptions} [options] Additional application options.
*/
constructor(domain, auth, options = {}) {
if (!/https?\:\/\/(?:localhost\:\d{4}|[\w\.\-]{3,256})/gi.test(domain))
throw new SyntaxError(
"Domain URL must start with 'http://' or 'https://' and "+
'must be bound to a port if using localhost.'
);
/**
* The domain for your Pterodactyl panel. This should be the main URL only
* (not "/api"). Any additional paths will count as the API path.
@@ -42,54 +49,45 @@ class PteroApp {
*/
this.options = loader.appConfig(options);
/** @type {?Date} */
this.readyAt = null;
/** @type {?number} */
this.ping = null;
/** @type {UserManager} */
this.users = new UserManager(this);
/** @type {NodeManager} */
this.nodes = new NodeManager(this);
/** @type {NestManager} */
this.nests = new NestManager(this);
/** @type {ApplicationServerManager} */
this.servers = new ApplicationServerManager(this);
/** @type {NodeLocationManager} */
this.locations = new NodeLocationManager(this);
/** @type {ApplicationRequestManager} @internal */
this.requests = new ApplicationRequestManager(this);
/** @type {NodeAllocationManager} */
this.allocations = new NodeAllocationManager(this);
/** @type {RequestManager} @internal */
this.requests = new RequestManager('application', domain, auth);
}
/**
* Sends a ping request to the API before performing additional startup requests.
* Attempting to use the application without connecting to the API will result
* in an error.
* Used for performing preload requests to Pterodactyl.
* @returns {Promise<boolean>}
*/
async connect() {
if (this.readyAt) return;
const start = Date.now();
await this.requests.ping();
this.ping = Date.now() - start;
if (this.options.users.fetch && this.options.users.cache) await this.users.fetch();
if (this.options.nodes.fetch && this.options.nodes.cache) await this.nodes.fetch();
if (this.options.nests.fetch && this.options.nests.cache) await this.nests.fetch();
if (this.options.servers.fetch && this.options.servers.cache) await this.servers.fetch();
if (this.options.locations.fetch && this.options.locations.cache) await this.locations.fetch();
this.readyAt = Date.now();
if (this.options.locations.fetch && this.options.locations.cache)
await this.locations.fetch();
return true;
}
/**
* Disconnects from the Pterodactyl API.
* @returns {void}
*/
async disconnect() {
if (!this.readyAt) return;
this.ping = null;
this.readyAt = null;
get ping() {
return this.requests._ping;
}
}

View File

@@ -1,8 +1,26 @@
const { PteroUser } = require('../structures/User');
const Dict = require('../structures/Dict');
const build = require('../util/query');
const endpoints = require('./endpoints');
class UserManager {
/**
* Allowed filter arguments for users.
*/
static get FILTERS() {
return Object.freeze([
'email', 'uuid', 'uuidShort',
'username', 'image', 'external_id'
]);
}
/**
* Allowed sort arguments for users.
*/
static get SORTS() {
return Object.freeze(['id', '-id', 'uuid', '-uuid']);
}
constructor(client) {
this.client = client;
@@ -18,9 +36,11 @@ class UserManager {
const u = new PteroUser(this.client, o);
res.set(u.id, u);
}
if (this.client.options.users.cache) res.forEach((v, k) => this.cache.set(k, v));
return res;
}
const u = new PteroUser(this.client, data.attributes);
if (this.client.options.users.cache) this.cache.set(u.id, u);
return u;
@@ -32,16 +52,26 @@ class UserManager {
* * a number
* * an object
*
* Returns `null` if not found.
* Returns `undefined` if not found.
* @param {string|number|object|PteroUser} obj The object to resolve from.
* @returns {?PteroUser} The resolved user.
*/
resolve(obj) {
if (obj instanceof PteroUser) return obj;
if (typeof obj === 'number') return this.cache.get(obj) || null;
if (typeof obj === 'string') return this.cache.find(s => s.name === obj) || null;
if (typeof obj === 'number') return this.cache.get(obj);
if (typeof obj === 'string') return this.cache.find(s => s.name === obj);
if (obj.relationships?.user) return this._patch(obj.relationships.user);
return null;
return undefined;
}
/**
* Returns a formatted URL to the user in the admin panel.
* @param {number|PteroUser} user The user or ID of the user.
* @returns {string} The formatted URL.
*/
adminURLFor(user) {
if (user instanceof PteroUser) return user.adminURL;
return `${this.client.domain}/admin/users/view/${user}`;
}
/**
@@ -53,18 +83,14 @@ class UserManager {
* @returns {Promise<PteroUser|Dict<number, PteroUser>>} The fetched user(s).
*/
async fetch(id, options = {}) {
if (id) {
if (!options.force) {
const u = this.cache.get(id);
if (u) return Promise.resolve(u);
}
const data = await this.client.requests.make(
endpoints.users.get(id) + (options.withServers ? '?include=servers' : '')
);
return this._patch(data);
if (id && !options.force) {
const u = this.cache.get(id);
if (u) return Promise.resolve(u);
}
const data = await this.client.requests.make(
endpoints.users.main + (options.withServers ? '?include=servers' : '')
const data = await this.client.requests.get(
(id ? endpoints.users.get(id) : endpoints.users.main) +
(options.withServers ? '?include=servers' : '')
);
return this._patch(data);
}
@@ -78,8 +104,12 @@ class UserManager {
* @returns {Promise<PteroUser>} The fetched user.
*/
async fetchExternal(id, options = {}) {
if (!options.force) for (const [, user] of this.cache) if (id === user.externalId) return user;
const data = await this.client.requests.make(
if (!options.force) {
const u = this.cache.filter(u => u.externalId === id);
if (u) return Promise.resolve(u);
}
const data = await this.client.requests.get(
endpoints.users.ext(id) + (options.withServers ? '?include=servers' : '')
);
return this._patch(data);
@@ -90,9 +120,12 @@ class UserManager {
* Keep in mind this does NOT check the cache first, it will fetch from the API directly.
* Available query filters are:
* * email
* * name
* * uuid
* * username
* * uuidShort
* * identifier (alias for uuidShort)
* * externalId
* * image
*
* Available sort options are:
* * id
@@ -101,21 +134,22 @@ class UserManager {
* * -uuid
*
* @param {string} entity The entity (string) to query.
* @param {string} [filter] The filter to use for the query (see above).
* @param {string} [sort] The order to sort the results in (see above).
* @returns {Promise<Dict<number, PteroUser>>} A dict of the queried user(s).
* @param {string} [filter] The filter to use for the query.
* @param {string} [sort] The order to sort the results in.
* @returns {Promise<Dict<number, PteroUser>>} A dict of the queried users.
*/
async query(entity, filter, sort) {
if (filter && !['email', 'uuid', 'username', 'externalId'].includes(filter)) throw new Error('Invalid query filter.');
if (sort && !['id', '-id', 'uuid', '-uuid'].includes(sort)) throw new Error('Invalid sort type.');
if (!sort && !filter) throw new Error('sort or filter is required to query');
if (!sort && !filter) throw new Error('Sort or filter is required.');
if (filter === 'identifier') filter = 'uuidShort';
if (filter === 'externalId') filter = 'external_id';
const data = await this.client.requests.make(
endpoints.users.main +
(filter ? `?filter[${filter}]=${entity}` : "") +
(sort && filter ? `&sort=${sort}` : "") +
(sort && !filter ? `?sort=${sort}` : "")
const { FILTERS, SORTS } = UserManager;
const query = build(
{ filter:[filter, entity], sort },
{ filters: FILTERS, sorts: SORTS }
);
const data = await this.client.requests.get(endpoints.users.main + query);
return this._patch(data);
}
@@ -128,13 +162,12 @@ class UserManager {
* @returns {Promise<PteroUser>} The new user.
*/
async create(email, username, firstname, lastname) {
await this.client.requests.make(
await this.client.requests.post(
endpoints.users.main,
{ email, username, first_name: firstname, last_name: lastname },
'POST'
{ email, username, first_name: firstname, last_name: lastname }
);
const u = await this.query(email, 'email');
return u.first();
const data = await this.query(email, 'email', '-id');
return data.find(u => u.email === email);
}
/**
@@ -162,10 +195,16 @@ class UserManager {
if (options.lastname) lastname = options.lastname;
if (options.language) language = options.language;
const data = await this.client.requests.make(
const data = await this.client.requests.patch(
endpoints.users.get(id),
{ email, username, first_name: firstname, last_name: lastname, language, password },
'PATCH'
{
email,
username,
first_name: firstname,
last_name: lastname,
language,
password
}
);
return this._patch(data);
}
@@ -177,7 +216,7 @@ class UserManager {
*/
async delete(user) {
if (user instanceof PteroUser) user = user.id;
await this.client.requests.make(endpoints.users.get(user), null, 'DELETE');
await this.client.requests.delete(endpoints.users.get(user));
this.cache.delete(user);
return true;
}

View File

@@ -7,7 +7,11 @@ module.exports = {
nodes:{
main: '/api/application/nodes',
get: n => `/api/application/nodes/${n}`,
config: n => `/api/application/nodes/${n}/configuration`
config: n => `/api/application/nodes/${n}/configuration`,
allocations:{
main: n => `/api/application/nodes/${n}/allocations`,
get: (n, a) => `/api/application/nodes/${n}/allocations/${a}`
}
},
servers:{
main: '/api/application/servers',

View File

@@ -28,6 +28,7 @@ class BackupManager {
}
return s;
}
data = data.attributes;
this.cache.set(data.uuid, {
uuid: data.uuid,
@@ -38,6 +39,7 @@ class BackupManager {
createdAt: new Date(data.created_at),
completedAt: data.completed_at ? new Date(data.completed_at) : null
});
return this.cache.get(data.uuid);
}
@@ -48,18 +50,15 @@ class BackupManager {
* @returns {Promise<Backup|Dict<string, Backup>>} The fetched backup(s).
*/
async fetch(id, force = false) {
if (id) {
if (!force) {
const b = this.cache.get(id);
if (b) return Promise.resolve(b);
}
const data = await this.client.requests.make(
endpoints.servers.backups.get(this.server.identifier, id)
);
return this._patch(data);
if (id && !force) {
const b = this.cache.get(id);
if (b) return Promise.resolve(b);
}
const data = await this.client.requests.make(
endpoints.servers.backups.main(this.server.identifier)
const data = await this.client.requests.get(
id
? endpoints.servers.backups.get(this.server.identifier, id)
: endpoints.servers.backups.main(this.server.identifier)
);
return this._patch(data);
}
@@ -70,9 +69,9 @@ class BackupManager {
*/
async create() {
return this._patch(
await this.client.requests.make(
await this.client.requests.post(
endpoints.servers.backups.main(this.server.identifier),
{}, 'POST'
null
)
);
}
@@ -83,7 +82,7 @@ class BackupManager {
* @returns {Promise<string>} The download link.
*/
async download(id) {
const url = await this.client.requests.make(
const url = await this.client.requests.get(
endpoints.servers.backups.download(this.server.identifier, id)
);
return url.attributes.url;
@@ -95,8 +94,8 @@ class BackupManager {
* @returns {Promise<boolean>}
*/
async delete(id) {
await this.client.requests.make(
endpoints.servers.backups.get(this.server.identifier, id), null, 'DELETE'
await this.client.requests.delete(
endpoints.servers.backups.get(this.server.identifier, id)
);
this.cache.delete(id);
return true;

View File

@@ -1,73 +0,0 @@
const fetch = require('node-fetch');
const { RequestError, PteroAPIError } = require('../structures/Errors');
const { version } = require('../../package.json');
/**
* The requests manager for the client API. This is not for public use. Using
* this manually may result in unwanted modifications of your Pterodactyl
* servers and/or account.
*/
class ClientRequestManager {
constructor(client) {
this.client = client
this.headers['Authorization'] = `Bearer ${client.auth}`;
}
suspended = false;
headers = {
'User-Agent': `Client PteroJS v${version}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
/** Sends a request to the Pterodactyl API. Returns a json object or an error if
* an unknown response code is received.
* @param {string} path The path to request.
* @param {?object} [params] Optional payload data (POST, PUT and PATCH).
* @param {string} [method] The method or HTTP verb to use.
* @returns {Promise<object|void>} The response object, if any.
*/
async make(path, params, method = 'GET') {
if (this.client.ping === null) throw new Error('Attempted request before client was ready.');
if (this.suspended) throw new RequestError('[429] Client is ratelimited.');
const body = params?.raw ?? (params ? JSON.stringify(params) : null);
const data = await fetch(this.client.domain + path, {
method,
body,
headers: this.headers
});
if ([201, 204].includes(data.status)) return;
if (data.status === 200) {
if (data.headers.get('content-type').startsWith('application/json')) return await data.json();
return await data.buffer();
}
if ([400, 404, 422].includes(data.status)) throw new PteroAPIError(await data.json());
if (data.status === 401) throw new RequestError('[401] Unauthorized API request.');
if (data.status === 403) throw new RequestError('[403] API Path forbidden.');
if (data.status === 429) {
this.suspended = true;
setTimeout(() => this.suspended = false, 600000);
throw new RequestError('[429] Client is ratelimited, retrying in 10 minutes.');
}
throw new RequestError(`Pterodactyl API returned an invalid or malformed payload (code: ${data.status}).`);
}
/**
* Sends a ping request to the Pterodactyl API.
* Because the API has no "ping" endpoint, we use a forced error to receive a 404 response
* then check that it is a valid 404 error by the API and not a timeout response.
* @returns {Promise<boolean>}
*/
async ping() {
try {
this.client.ping = -1;
await this.make('/api/client');
} catch (err) {
if (!err?.code) throw new RequestError('Pterodactyl API is unavailable.');
return true;
}
}
}
module.exports = ClientRequestManager;

View File

@@ -1,8 +1,16 @@
const ClientServer = require('../structures/ClientServer');
const Dict = require('../structures/Dict');
const build = require('../util/query');
const endpoints = require('./endpoints');
class ClientServerManager {
/**
* Allowed include arguments for client servers.
*/
static get INCLUDES() {
return Object.freeze(['egg', 'subusers']);
}
constructor(client) {
this.client = client
@@ -21,9 +29,11 @@ class ClientServerManager {
const s = new ClientServer(this.client, o);
res.set(s.identifier, s);
}
if (this.client.options.servers.cache) res.forEach((v, k) => this.cache.set(k, v));
return res;
}
const s = new ClientServer(this.client, data);
if (this.client.options.servers.cache) this.cache.set(s.identifier, s);
return s;
@@ -41,6 +51,16 @@ class ClientServerManager {
}
}
/**
* Returns a formatted URL to the server.
* @param {string|ClientServer} server The server or identifier of the server.
* @returns {string} The formatted URL.
*/
panelURLFor(server) {
if (server instanceof ClientServer) return server.panelURL;
return `${this.client.domain}/server/${server}`;
}
/**
* Fetches a server (or all if no id is specified) from the Pterodactyl API.
* @param {string} [id] The ID of the server.
@@ -55,13 +75,11 @@ class ClientServerManager {
const s = this.cache.get(id);
if (s) return Promise.resolve(s);
}
const data = await this.client.requests.make(
endpoints.servers.get(id) + joinParams(options.include)
);
return this._patch(data);
}
const data = await this.client.requests.make(
endpoints.main + joinParams(options.include)
const query = build(options, { includes: ClientServerManager.INCLUDES });
const data = await this.client.requests.get(
(id ? endpoints.servers.get(id) : endpoints.servers.main) + query
);
return this._patch(data);
}
@@ -69,12 +87,6 @@ class ClientServerManager {
module.exports = ClientServerManager;
function joinParams(params) {
if (!params || !params.length) return '';
params = params.filter(p => ['egg', 'subusers'].includes(p));
return '?include='+ params.toString();
}
/**
* @typedef {object} PageData
* @property {number} current The current page.

View File

@@ -1,17 +1,18 @@
const { EventEmitter } = require('events');
const ClientRequestManager = require('./ClientRequestManager');
const ClientServerManager = require('./ClientServerManager');
const { ClientUser } = require('../structures/User');
const RequestManager = require('../http/RequestManager');
const ScheduleManager = require('./ScheduleManager');
const WebSocketManager = require('./ws/WebSocketManager');
const endpoints = require('./endpoints');
const loader = require('../structures/configLoader');
const loader = require('../util/configLoader');
const Shard = require('./ws/Shard');
/**
* The base class for the Pterodactyl client API.
* This operates using a Pterodactyl user access token which can be found at
* <your.domain.name/admin/api>.
*
*
* The access token will grant you access to your servers only, with the option
* to fetch node and API key information and establish websockets to your servers.
* @extends {EventEmitter}
@@ -25,6 +26,12 @@ class PteroClient extends EventEmitter {
constructor(domain, auth, options = {}) {
super();
if (!/https?\:\/\/(?:localhost\:\d{4}|[\w\.\-]{3,256})/gi.test(domain))
throw new SyntaxError(
"Domain URL must start with 'http://' or 'https://' and "+
'must be bound to a port if using localhost.'
);
/**
* The domain for your Pterodactyl panel. This should be the main URL only
* (not "/api"). Any additional paths will count as the API path.
@@ -46,80 +53,76 @@ class PteroClient extends EventEmitter {
*/
this.options = loader.clientConfig(options);
/** @type {?Date} */
this.readyAt = null;
/** @type {?number} */
this.ping = null;
/** @type {?ClientUser} */
/** @type {ClientUser} */
this.user = null;
/** @type {ClientServerManager} */
this.servers = new ClientServerManager(this);
/** @type {ScheduleManager} */
this.schedules = new ScheduleManager(this);
/** @type {ClientRequestManager} @internal */
this.requests = new ClientRequestManager(this);
/** @type {WebSocketManager} @internal */
/** @type {RequestManager} @internal */
this.requests = new RequestManager('Client', this.domain, this.auth);
/** @type {WebSocketManager} */
this.ws = new WebSocketManager(this);
}
/**
* Sends a ping request to the API before performing additional startup requests
* as well as any websocket connections. Attempting to use the application without
* connecting to the API will result in an error.
* Performs preload requests to Pterodactyl and launches websocket connections.
* @returns {Promise<boolean>}
* @fires PteroClient#ready
*/
async connect() {
if (this.readyAt) return;
const start = Date.now();
await this.requests.ping();
this.ping = Date.now() - start;
if (this.options.fetchClient) this.user = await this.fetchClient();
if (this.options.fetchClient) await this.fetchClient();
if (this.options.servers.fetch && this.options.servers.cache) await this.servers.fetch();
if (this.options.ws) await this.ws.launch();
this.readyAt = Date.now();
return true;
}
get ping() {
return this.requests._ping;
}
/**
* Fetches the client user's account. This will contain information such as 2FA
* recovery tokens, API keys and email data.
* @returns {Promise<ClientUser>} The client user.
*/
async fetchClient() {
const data = await this.requests.make(endpoints.account.main);
return new ClientUser(this, data.attributes);
const data = await this.requests.get(endpoints.account.main);
this.user = new ClientUser(this, data.attributes);
return this.user;
}
/**
* Adds a server or an array of servers to be connected to websockets.
* @param {string|string[]} ids The identifier of the server, or an array of server identifiers.
* @returns {void}
* @param {string[] | string} ids The identifier(s) of the server.
* @returns {Shard|Shard[]} Created (or reused) shard(s).
*/
addSocketServer(ids) {
Array.isArray(ids) ? this.ws.servers.push(...ids) : this.ws.servers.push(ids);
if (typeof ids === 'string')
return this.ws.createShard(ids);
else if (ids instanceof Array)
return ids.map(id => this.ws.createShard(id))
}
/**
* Removes a server from websocket connections.
* @param {string} id The identifier of the server.
* @returns {void}
* @returns {boolean} Whether shard was removed.
*/
removeSocketServer(id) {
this.ws.servers.splice(id);
return this.ws.removeShard(id);
}
/**
* Disconnects from the Pterodactyl API and closes any existing websocket connections.
* Closes any existing websocket connections.
* @returns {void}
*/
disconnect() {
this.ping = null;
this.readyAt = null;
if (!this.ws.readyAt) this.ws.destroy();
if (this.ws.readyAt) this.ws.destroy();
}
}
@@ -135,7 +138,6 @@ module.exports = PteroClient;
/**
* Startup options for the client API.
* @typedef {object} ClientOptions
* @property {boolean} [ws] Whether to enable server websocket connections (default: `false`).
* @property {boolean} [fetchClient] Whether to fetch the client user (default `true`).
* @property {OptionSpec} [servers] Options for fetching and caching servers.
* @property {OptionSpec} [subUsers] Options for fetching and caching server subusers.

View File

@@ -17,11 +17,13 @@ class ScheduleManager {
const s = new Schedule(this.client, id, o);
res.set(s.id, s);
}
let c = this.cache.get(id);
if (c) res.forEach((v, k) => c.set(k, v)); else c = res;
this.cache.set(id, c);
return res;
}
const s = new Schedule(this.client, id, data);
let c = this.cache.get(id);
if (c) c.set(s.id, s); else c = new Dict().set(s.id, s);
@@ -29,6 +31,17 @@ class ScheduleManager {
return s;
}
/**
* Returns a formatted URL to the schedule.
* @param {string} id The identifier of the server.
* @param {string|Schedule} schedule The schedule or identifier of the schedule.
* @returns {string} The formatted URL.
*/
panelURLFor(id, schedule) {
if (schedule instanceof Schedule) return schedule.panelURL;
return `${this.client.domain}/server/${id}/schedules/${schedule}`;
}
/**
* Fetches a schedule or all schedules from a specified server (with optional cache check).
* @param {string} server The identifier of the server.
@@ -37,18 +50,15 @@ class ScheduleManager {
* @returns {Promise<Schedule|Dict<number, Schedule>>} The fetched schedule(s).
*/
async fetch(server, id, force) {
if (id) {
if (!force) {
const sch = this.cache.get(server)?.get(id);
if (sch) return sch;
}
const data = await this.client.requests.make(
endpoints.servers.schedules.get(server, id)
);
return this._patch(server, data);
if (id && !force) {
const s = this.cache.get(server)?.get(id);
if (s) return s;
}
const data = await this.client.requests.make(
endpoints.servers.schedules.main(server)
const data = await this.client.requests.get(
id
? endpoints.servers.schedules.get(id)
: endpoints.servers.schedules.main
);
return this._patch(server, data);
}
@@ -66,7 +76,8 @@ class ScheduleManager {
* @returns {Promise<Schedule>} The new schedule.
*/
async create(server, options = {}) {
if (Object.keys(options).length < 4) throw new Error('Missing required Schedule creation option.');
if (Object.keys(options).length < 4)
throw new Error('Missing required Schedule creation option.');
const payload = {};
payload.name = options.name;
@@ -76,8 +87,8 @@ class ScheduleManager {
payload.day_of_week = options.dayOfWeek || '*';
payload.day_of_month = options.dayOfMonth || '*';
const data = await this.client.requests.make(
endpoints.servers.schedules.main(server), payload, 'POST'
const data = await this.client.requests.post(
endpoints.servers.schedules.main(server), payload
);
return this._patch(data);
}
@@ -107,8 +118,8 @@ class ScheduleManager {
payload.day_of_week = options.dayOfWeek || sch.cron.week;
payload.day_of_month = options.dayOfMonth || sch.cron.month;
const data = await this.client.requests.make(
endpoints.servers.schedules.get(server, id), payload, 'POST'
const data = await this.client.requests.post(
endpoints.servers.schedules.get(server, id), payload
);
return this._patch(data);
}
@@ -120,10 +131,10 @@ class ScheduleManager {
* @returns {Promise<boolean>}
*/
async delete(server, id) {
await this.client.requests.make(
endpoints.servers.schedules.get(server, id), null, 'DELETE'
await this.client.requests.delete(
endpoints.servers.schedules.get(server, id)
);
this.cache.get(server).delete(id);
this.cache.get(server)?.delete(id);
return true;
}
}

View File

@@ -21,9 +21,11 @@ class SubUserManager {
const u = new PteroSubUser(this.client, this.server.identifier, o);
s.set(u.uuid, u);
}
if (this.client.options.subUsers.cache) s.forEach((v, k) => this.cache.set(k, v));
return s;
}
const u = new PteroSubUser(this.client, this.server.identifier, data.attributes);
if (this.client.options.subUsers.cache) this.cache.set(u.uuid, u);
return u;
@@ -35,16 +37,24 @@ class SubUserManager {
* * a number
* * an object
*
* Returns `null` if not found.
* Returns `undefined` if not found.
* @param {string|number|object|PteroSubUser} obj The object to resolve from.
* @returns {?PteroSubUser} The resolved subuser.
*/
resolve(obj) {
if (obj instanceof PteroSubUser) return obj;
if (typeof obj === 'number') return this.cache.get(obj) || null;
if (typeof obj === 'string') return this.cache.find(s => s.name === obj) || null;
if (typeof obj === 'number') return this.cache.get(obj);
if (typeof obj === 'string') return this.cache.find(s => s.name === obj);
if (obj.relationships?.user) return this._patch(obj.relationships.user);
return null;
return undefined;
}
/**
* Returns a formatted URL to the subuser.
* @returns {string} The formatted URL.
*/
get panelURL() {
return `${this.client.domain}/server/${this.server.identifier}/users`;
}
/**
@@ -54,18 +64,15 @@ class SubUserManager {
* @returns {Promise<PteroSubUser|Dict<string, PteroSubUser>>} The fetched user(s).
*/
async fetch(id, force = false) {
if (id) {
if (!force) {
const u = this.cache.get(id);
if (u) return Promise.resolve(u);
}
const data = await this.client.requests.make(
endpoints.servers.users.get(this.server.identifier, id)
);
return this._patch(data);
if (id && !force) {
const u = this.cache.get(id);
if (u) return Promise.resolve(u);
}
const data = await this.client.requests.make(
endpoints.servers.users.main(this.server.identifier)
const data = await this.client.requests.get(
id
? endpoints.servers.users.get(this.server.identifier, id)
: endpoints.servers.users.main(this.server.identifier)
);
return this._patch(data);
}
@@ -78,11 +85,13 @@ class SubUserManager {
*/
async add(email, permissions) {
if (typeof email !== 'string') throw new Error('Email must be a string.');
const perms = new Permissions(permissions).toStrings();
if (!perms.length) throw new Error('Need at least 1 permission for the subuser.');
const data = await this.client.requests.make(
const data = await this.client.requests.post(
endpoints.servers.users.main(this.server.identifier),
{ email, permissions: perms }, 'POST'
{ email, permissions: perms }
);
return this._patch(data);
}
@@ -95,9 +104,10 @@ class SubUserManager {
async setPermissions(uuid, permissions) {
const perms = new Permissions(permissions).toStrings();
if (!perms.length) throw new Error('Need at least 1 permission for the subuser.');
const data = await this.client.requests.make(
const data = await this.client.requests.post(
endpoints.servers.users.get(this.server.identifier, uuid),
{ permissions: perms }, 'POST'
{ permissions: perms }
);
return this._patch(data);
}
@@ -108,8 +118,8 @@ class SubUserManager {
* @returns {Promise<boolean>}
*/
async remove(id) {
await this.client.requests.make(
endpoints.servers.users.get(this.server.identifier, id), null, 'DELETE'
await this.client.requests.delete(
endpoints.servers.users.get(this.server.identifier, id)
);
this.cache.delete(id);
return true;

View File

@@ -7,7 +7,7 @@ module.exports = {
apikeys: '/api/client/account/api-keys'
},
servers:{
main: '/api/client/servers',
main: '/api/client',
get: s => `/api/client/servers/${s}`,
databases:{
main: s => `/api/client/servers/${s}/databases`,

View File

@@ -1,87 +1,146 @@
const EventEmitter = require('events');
const WebSocket = require('ws');
const handle = require('./packetHandler');
const endpoints = require('../endpoints');
class Shard {
constructor(client, id, auth) {
class Shard extends EventEmitter {
constructor(client, id) {
super();
this.client = client;
this.id = id;
this.token = auth.token;
this.token = null;
this.socket = null;
this.status = 'CLOSED';
this.readyAt = 0;
this.ping = -1;
this.lastPing = 0;
this.connect(auth);
}
/**
* Emit `debug` client event with given message
* @param {string} message Message text
*/
#debug(message) {
this.client.emit('debug', `[SHARD ${this.id}] ${message}`);
this.emit('debug', `[SHARD ${this.id}] ${message}`);
}
connect({ socket }) {
if (!['CLOSED', 'RECONNECTING'].includes(this.status)) return;
if (this.socket) this.socket = null;
this.socket = new WebSocket(socket);
this.status = 'CONNECTING';
/**
* Initialize connection and resolve after full authentication
* @param {WebSocketAuth?} auth WebSocket URL to connect to
* @returns {Promise<WebSocket>} WebSocket connection
*/
connect(auth) {
return new Promise(async (resolve, reject) => {
if (!['CLOSED', 'RECONNECTING'].includes(this.status)) return;
if (this.socket) this.socket = null;
if (!auth)
({ data: auth } = await this.client.requests.get(endpoints.servers.ws(this.id)));
this.socket = new WebSocket(auth.socket);
this.status = 'CONNECTING';
this.socket.on('open', () => this._onOpen());
this.socket.on('message', data => this._onMessage(data.toString()));
this.socket.on('error', error => this._onError(error));
this.socket.on('close', () => this._onClose());
this.token = auth.token;
this.once('authSuccess', () => {
this.emit('serverConnect', this.socket);
resolve(this.socket);
});
})
}
/**
* Close socket connection and start a new one
* @returns {Promise<WebSocket>} WebSocket connection
*/
async reconnect() {
if (this.status === 'RECONNECTING') return;
this.status = 'RECONNECTING';
const data = await this.client.requests.make(endpoints.servers.ws(this.id));
this.socket.close(4009, 'pterojs::reconnect');
this.token = data.token;
this.connect(data);
const { data } = await this.client.requests.get(endpoints.servers.ws(this.id));
return this.connect(data.socket, data.token);
}
/**
* Close socket connection
*/
disconnect() {
if (!this.readyAt) return;
this.socket.close(1000, 'pterojs::disconnect');
return new Promise(async (resolve, reject) => {
if (!this.readyAt) return reject('Socket is not connected');
this.readyAt = 0;
this.lastPing = 0;
this.ping = -1;
this.token = null;
this.once('serverDisconnect', resolve);
this.socket.close(1000, 'pterojs::disconnect');
this.readyAt = 0;
this.lastPing = 0;
this.ping = -1;
});
}
/**
* Get a new token from API and send it to active socket connection
* @returns {Promise<void>}
*/
async refreshToken() {
return new Promise(async (resolve, reject) => {
if (this.status !== 'CONNECTED') return reject('Socket is not connected');
// using this transitional property to avoid double token issuing during init
if (!this.token) {
const { data } = await this.client.requests.get(endpoints.servers.ws(this.id));
this.token = data.token;
}
this.send('auth', this.token);
this.token = null;
this.lastPing = Date.now();
this.once('authSuccess', () => resolve(this.socket));
});
}
/**
* Send a message to socket server
* @param {string} event Name of event
* @param {any|any[]|undefined} args Event data
*/
send(event, args) {
if (!this.socket) return; // throw error
if (!this.socket) throw new Error('Socket for this shard is unavailable.');
if (!Array.isArray(args)) args = [args];
this.socket.send({ event, args });
this.#debug(`Sending event '${event}'`);
this.socket.send(JSON.stringify({ event, args }));
}
_onOpen() {
this.#debug('Socket connected');
this.status = 'CONNECTED';
this.readyAt = Date.now();
this.refreshToken();
this.#debug('Connection opened');
}
_onMessage({ data }) {
_onMessage(data) {
if (!data) return this.#debug('Received a malformed packet');
data = JSON.parse(data);
if (this.status === 'CONNECTING') {
this.status = 'CONNECTED';
this.lastPing = Date.now();
return this.send('auth', this.token);
}
this.client.emit('rawPayload', data);
this.emit('rawPayload', data);
switch (data.event) {
case 'auth success':
this.ping = Date.now() - this.lastPing;
this.emit('authSuccess');
break;
case 'token expiring':
// irrelevant
this.refreshToken();
this.#debug('Auth token refreshed');
return;
case 'token expired':
@@ -89,18 +148,25 @@ class Shard {
break;
}
handle(this.client, data, this.id);
handle(this, data, this.id);
}
_onError({ error }) {
_onError(error) {
if (!error) return;
this.#debug(`Error received: ${error}`);
}
_onClose() {
this.status = 'CLOSED';
this.emit('serverDisconnect');
this.#debug('Connection closed');
}
}
/**
* @typedef {object} WebSocketAuth
* @property {string} token
* @property {string} socket
*/
module.exports = Shard;

View File

@@ -1,57 +1,24 @@
const Shard = require('./Shard');
const endpoints = require('../endpoints');
// const EVENTS = {
// 'auth success': 'serverConnect',
// 'console output': 'serverOutput',
// 'token expired': 'serverDisconnect',
// 'status': 'statusUpdate',
// 'stats': 'statsUpdate'
// }
class WebSocketManager {
constructor(client) {
this.client = client;
this.servers = [];
/**
* A map of active server shards.
* @type {Map<string, Shard>}
*/
this.shards = new Map();
this.totalShards = 0;
this.readyAt = 0;
}
async launch() {
if (!this.servers.length) {
this.client.emit('debug', '[WS] No shards to launch');
return;
}
this.client.emit('debug', `[WS] Attempting to launch ${this.servers.length} shard(s)`);
for (const id of this.servers) {
const data = await this.client.requests.make(endpoints.servers.ws(id));
try {
const shard = new Shard(this.client, id, data);
this.shards.set(id, shard);
this.totalShards++;
} catch {
this.client.emit('debug', `[WS] Shard ${id} failed to launch`);
}
}
this.readyAt = Date.now();
this.client.emit('ready');
}
destroy() {
if (!this.readyAt) return;
for (const shard of this.shards.values()) shard.disconnect();
this.shards.clear();
this.readyAt = 0;
this.client.emit('debug', `[WS] Destroyed ${this.totalShards} shards`);
this.client.emit('debug', `[WS] Destroyed ${this.totalShards} shard(s)`);
this.totalShards = 0;
}
@@ -61,6 +28,37 @@ class WebSocketManager {
for (const shard of this.shards.values()) sum += shard.ping;
return sum / this.totalShards;
}
/**
* Adds a server to be connected to websockets.
* @param {string} id The identifier of the server.
* @returns {Shard} Created (or reused) shard.
*/
createShard(id) {
if (this.shards.has(id))
return this.shards.get(id);
const shard = new Shard(this.client, id);
this.shards.set(id, shard);
this.totalShards++;
return shard;
}
/**
* Removes a server from websocket connections.
* @param {string} id The identifier of the server.
* @returns {boolean} Whether shard was removed.
*/
removeShard(id) {
if (!this.shards.has(id))
return false;
this.shards.delete(id);
this.totalShards--;
return true;
}
}
module.exports = WebSocketManager;

View File

@@ -1,51 +1,51 @@
function handle(client, { event, args }, id) {
function getServer(id) {
return client.servers.cache.get(id);
}
const caseConv = require('../../util/caseConv');
function handle(shard, { event, args }) {
if (!Array.isArray(args)) args = [args];
switch (event) {
case 'auth success':
return client.emit('serverConnect', getServer(id));
return shard.emit('authSuccess');
case 'status':
return client.emit('statusUpdate', getServer(id), ...args);
return shard.emit('statusUpdate', ...args);
case 'console output':
return client.emit('serverOutput', id, ...args);
return shard.emit('serverOutput', ...args);
case 'daemon message':
return client.emit('daemonMessage', getServer(id), ...args);
return shard.emit('daemonMessage', ...args);
case 'install started':
return client.emit('installStart', id);
return shard.emit('installStart');
case 'install output':
return client.emit('installOutput', id, ...args);
return shard.emit('installOutput', ...args);
case 'install completed':
return client.emit('installComplete', id);
return shard.emit('installComplete');
case 'stats':
return client.emit('statsUpdate', getServer(id), ...args);
const stats = JSON.parse(...args);
stats.network = caseConv.camelCase(stats.network);
return shard.emit('statsUpdate', caseConv.camelCase(stats));
case 'transferLogs':
case 'transferStatus':
return client.emit('transferUpdate', id, ...args);
return shard.emit('transferUpdate', ...args);
case 'backup completed':
let backup = {};
if (args.length) backup = getServer(id).backups._patch(args[0]);
return client.emit('backupComplete', getServer(id), backup);
return shard.emit('backupComplete', args ?? {});
case 'token expired':
return client.emit('serverDisconnect', id);
return shard.emit('serverDisconnect');
case 'daemon error':
case 'jwt error':
return client.emit('error', id, ...args);
return shard.emit('error', ...args);
default:
return client.emit('debug', `[SHARD ${id}] Received unknown event: '${event}'`);
return shard.emit('debug', `[SHARD ${id}] Received unknown event: '${event}'`);
}
}

View File

@@ -1,12 +1,12 @@
const { EventEmitter } = require('events');
const fetch = require('node-fetch');
const caseConv = require('../../structures/caseConv');
const caseConv = require('../../util/caseConv');
class NodeStatus extends EventEmitter {
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'NodeStatus PteroJS v1.0.2'
'User-Agent': 'NodeStatus PteroJS v1.0.3'
}
#interval = null;
#connected = new Set();
@@ -18,15 +18,23 @@ class NodeStatus extends EventEmitter {
super();
Object.assign(this, options);
if (!/https?\:\/\/(?:localhost\:\d{4}|[\w\.\-]{3,256})/gi.test(this.domain))
throw new SyntaxError(
"Domain URL must start with 'http://' or 'https://' and "+
'must be bound to a port if using localhost.'
);
this.headers['Authorization'] = 'Bearer '+ options.auth;
this.nextInterval ||= 5;
this.retryLimit ||= 0;
/** @type {?Function} */
/** @type {null | (id: number) => void} */
this.onConnect = null;
/** @type {?Function} */
/** @type {null | (d: object) => void} */
this.onInterval = null;
/** @type {?Function} */
/** @type {null | (id: number) => void} */
this.onDisconnect = null;
this.ping = -1;
@@ -50,6 +58,7 @@ class NodeStatus extends EventEmitter {
this.#debug('Starting connection to API');
await this.#ping();
await this.#handleNext();
this.#interval = setInterval(() => this.#handleNext(), this.callInterval).unref();
this.readyAt = Date.now();
process.on('SIGINT', _ => this.close());
@@ -61,9 +70,15 @@ class NodeStatus extends EventEmitter {
const res = await fetch(`${this.domain}/api/application`, {
method: 'GET', headers: this.headers
});
if (res.status === 401)
return this.close('[NS:401] Invalid API credentials. Contact your panel administrator.', true);
return this.close(
'[NS:401] Invalid API credentials. Contact your panel administrator.',
true
);
if (res.status === 403) return this.close('[NS:403] Missing access.', true);
this.ping = Date.now() - start;
const data = await res.json().catch(()=>{});
if (data?.errors?.length) return;
@@ -88,7 +103,11 @@ class NodeStatus extends EventEmitter {
if (!res.ok) {
if (res.status === 401)
return this.close('[NS:401] Invalid API credentials. Contact your panel administrator.', true);
return this.close(
'[NS:401] Invalid API credentials. Contact your panel administrator.',
true
);
if (res.status === 403) return this.close('[NS:403] Missing access.', true);
if (res.status === 404) {
if (this.#connected.has(id)) {
@@ -98,7 +117,10 @@ class NodeStatus extends EventEmitter {
}
return;
}
if (this.current > this.retryLimit) return this.close('[NS] Maximum retry limit exceeded.');
if (this.current > this.retryLimit)
return this.close('[NS] Maximum retry limit exceeded.');
this.current++;
this.#debug('Attempting retry fetch');
this.#request(id);
@@ -112,14 +134,17 @@ class NodeStatus extends EventEmitter {
this.emit('connect', id);
if (this.onConnect !== null) this.onConnect(id);
}
this.emit('interval', attributes);
if (this.onInterval !== null) this.onInterval(attributes);
}
close(message = 'None', error = false) {
if (!this.readyAt) return;
this.#debug('Closing connection');
if (this.#interval) clearInterval(this.#interval);
this.removeAllListeners();
this.#connected.clear();
if (error && message) throw new Error(message);

File diff suppressed because it is too large Load Diff

View File

@@ -2,48 +2,46 @@ module.exports = {
version: require('../package.json').version,
// Application API
ApplicationServerManager: require('./application/ApplicationServerManager'),
NestEggsManager: require('./application/NestEggsManager'),
NestManager: require('./application/NestManager'),
NodeAllocationManager: require('./application/NodeAllocationManager'),
NodeLocationManager: require('./application/NodeLocationManager'),
NodeManager: require('./application/NodeManager'),
PteroApp: require('./application/PteroApp'),
UserManager: require('./application/UserManager'),
NodeManager: require('./application/NodeManager'),
NestManager: require('./application/NestManager'),
NestEggsManager: require('./application/NestEggsManager'),
ApplicationServerManager: require('./application/ApplicationServerManager'),
NodeLocationManager: require('./application/NodeLocationManager'),
ApplicationRequestManager: require('./application/ApplicationRequestManager'),
// Client API
PteroClient: require('./client/PteroClient'),
ClientServerManager: require('./client/ClientServerManager'),
ScheduleManager: require('./client/ScheduleManager'),
ClientRequestManager: require('./client/ClientRequestManager'),
BackupManager: require('./client/BackupManager'),
SubUserManager: require('./client/SubUserManager'),
// Websocket
Shard: require('./client/ws/Shard'),
WebSocketManager: require('./client/ws/WebSocketManager'),
// Global Managers
AllocationManager: require('./managers/AllocationManager'),
DatabaseManager: require('./managers/DatabaseManager'),
FileManager: require('./managers/FileManager'),
BackupManager: require('./client/BackupManager'),
ClientDatabaseManager: require('./client/ClientDatabaseManager'),
ClientServerManager: require('./client/ClientServerManager'),
FileManager: require('./client/FileManager'),
NetworkAllocationManager: require('./client/NetworkAllocationManager'),
PteroClient: require('./client/PteroClient'),
ScheduleManager: require('./client/ScheduleManager'),
SubUserManager: require('./client/SubUserManager'),
// Package Structures
// Extensions
NodeStatus: require('./extensions/NodeStatus'),
// HTTP
RequestManager: require('./http/RequestManager'),
// Structures
ApplicationServer: require('./structures/ApplicationServer'),
ClientServer: require('./structures/ClientServer'),
Dict: require('./structures/Dict'),
...require('./structures/Errors'),
Node: require('./structures/Node'),
Permissions: require('./structures/Permissions'),
/** @deprecated Use configLoader util instead. */
Presets: require('./structures/Presets'),
Schedule: require('./structures/Schedule'),
...require('./structures/Errors'),
...require('./structures/User'),
// Utils
configLoader: require('./structures/configLoader'),
caseConv: require('./structures/caseConv'),
// Extensions
NodeStatus: require('./extensions/NodeStatus')
// Utility
caseConv: require('./util/caseConv'),
configLoader: require('./util/configLoader'),
query: require('./util/query')
};

View File

@@ -1,104 +0,0 @@
const Dict = require('../structures/Dict');
const endpoints = require('../client/endpoints');
class AllocationManager {
constructor(client, server, data) {
this.client = client;
this.server = server;
/**
* Whether the client using this manager is the PteroClient or PteroApp.
* @type {boolean}
*/
this.isClient = client.constructor.name === 'PteroClient';
/** @type {Dict<number, Allocation>} */
this.cache = new Dict();
this._patch(data);
}
_patch(data) {
if (!data?.allocations && !data?.data && !data?.attributes) return;
if (data.allocations) data = data.allocations;
if (data.data) {
const res = new Dict();
for (let o of data.data) {
o = o.attributes;
res.set(o.id, {
id: o.id,
ip: o.ip,
ipAlias: o.ip_alias,
port: o.port,
notes: o.notes ?? null,
isDefault: o.is_default
});
}
res.forEach((v, k) => this.cache.set(k, v));
return res;
} else {
data = data.attributes;
const o = {
id: data.id,
ip: data.ip,
ipAlias: data.ip_alias,
port: data.port,
notes: data.notes ?? null,
isDefault: data.is_default
}
this.cache.set(data.id, o);
return o;
}
}
async fetch() {
return this._patch(
await this.client.requests.make(
endpoints.servers.network.main(this.server.identifier)
)
);
}
async assign() {
return this._patch(
await this.client.requests.make(
endpoints.servers.network.main(this.server.identifier), null, 'POST'
)
);
}
async setNote(id, note) {
const data = await this.client.requests.make(
endpoints.servers.network.get(this.server.identifier, id),
{ notes: note }, 'POST'
);
return this._patch(data);
}
async setPrimary(id) {
return this._patch(
await this.client.requests.make(
endpoints.servers.network.primary(this.server.identifier, id), null, 'POST'
)
);
}
async unassign(id) {
await this.client.requests.make(
endpoints.servers.network.get(this.server.identifier, id), null, 'DELETE'
);
this.cache.delete(id);
}
}
module.exports = AllocationManager;
/**
* Represents an allocation for a server.
* @typedef {object} Allocation
* @property {number} id The ID of the allocation.
* @property {string} ip The IP of the allocation.
* @property {?string} ipAlias An alias for the IP.
* @property {number} port The port for the allocation.
* @property {?string} notes Additional notes for the allocation.
* @property {boolean} isDefault Whether it is a default allocation.
*/

View File

@@ -1,104 +0,0 @@
const Dict = require('../structures/Dict');
const endpoints = require('../client/endpoints');
class DatabaseManager {
constructor(client, server, data) {
this.client = client;
this.server = server;
/**
* Whether the client using this manager is the PteroClient or PteroApp.
* @type {boolean}
*/
this.isClient = client.constructor.name === 'PteroClient';
/** @type {Dict<string, Database>} */
this.cache = new Dict();
this._patch(data);
}
_patch(data) {
if (!data?.databases && !data?.data && !data?.attributes) return;
if (data.databases) data = data.databases.data;
if (data.data) {
const res = new Dict();
for (let db of data.data) {
db = db.attributes;
res.set(db.id, {
id: db.id,
host: db.host,
name: db.name,
username: db.username,
password: db.password ?? null,
connections: db.connections,
maxConnections: db.max_connections
});
}
res.forEach((v, k) => this.cache.set(k, v));
return res;
} else {
data = data.attributes;
const o = {
id: data.id,
host: data.host,
name: data.name,
username: data.username,
password: data.password ?? null,
connections: data.connections,
maxConnections: data.max_connections
}
this.cache.set(data.id, o);
return o;
}
}
async fetch(withPass = false) {
if (!this.isClient) return Promise.resolve();
const data = await this.client.requests.make(
endpoints.servers.databases.main(this.server.identifier) + (withPass ? '?include=password' : '')
);
return this._patch(data);
}
async create(database, remote) {
if (!this.isClient) return Promise.resolve();
const data = await this.client.requests.make(
endpoints.servers.databases.get(this.server.identifier),
{ database, remote }, 'POST'
);
return this._patch(data);
}
async rotate(id) {
if (!this.isClient) return Promise.resolve();
const data = await this.client.requests.make(
endpoints.servers.databases.rotate(this.server.identifier, id), null, 'POST'
);
return this._patch(data);
}
async delete(id) {
if (!this.isClient) return Promise.resolve();
await this.client.requests.make(
endpoints.servers.databases.delete(this.server.identifier, id), null, 'DELETE'
);
this.cache.delete(id);
return true;
}
}
module.exports = DatabaseManager;
/**
* Represents a server database object.
* @typedef {object} Database
* @property {string} id The ID of the database.
* @property {object} host Host information for the database.
* @property {string} host.address The address of the database.
* @property {number} host.port The port allocated to this database.
* @property {string} name The name of the database.
* @property {string} username The username for the database.
* @property {?string} password The password for this database.
* @property {string} connections Connections to the database.
* @property {number} maxConnections The max amount of connections allowed for this database.
*/

View File

@@ -1,273 +0,0 @@
const endpoints = require('../client/endpoints');
class FileManager {
constructor(client, server, data) {
this.client = client;
this.server = server;
/**
* Whether the client using this manager is the PteroClient or PteroApp.
* @type {boolean}
*/
this.isClient = client.constructor.name === 'PteroClient';
/** @type {Map<string, Map<string, PteroFile>>} */
this.cache = new Map();
this.cache.set('/', new Map());
this._patch(data);
}
_patch(data) {
if (!data?.files && !data?.data && !data?.attributes) return;
if (data.files) data = data.files.data;
const dir = decodeURIComponent(data._dir);
if (data.data) {
const res = new Map();
for (let o of data.data) {
o = o.attributes;
res.set(o.name, {
name: o.name,
mode: o.mode,
modeBits: BigInt(o.mode_bits ?? -1),
size: o.size,
isFile: o.is_file,
isSymlink: o.is_symlink,
isEditable: o.is_editable,
mimetype: o.mimetype,
createdAt: new Date(o.created_at),
modifiedAt: o.modified_at ? new Date(o.modified_at) : null
});
}
let hold = this.cache.get(dir);
if (!hold) {
this.cache.set(dir, new Map());
hold = new Map();
}
res.forEach((v, k) => hold.set(k, v));
this.cache.set(dir, hold);
return res;
} else {
data = data.attributes;
const o = {
name: data.name,
mode: data.mode,
modeBits: BigInt(data.mode_bits ?? -1),
size: data.size,
isFile: data.is_file,
isSymlink: data.is_symlink,
isEditable: data.is_editable,
mimetype: data.mimetype,
createdAt: new Date(data.created_at),
modifiedAt: data.modified_at ? new Date(data.modified_at) : null
}
let hold = this.cache.get(dir);
if (!hold) {
this.cache.set(dir, new Map());
hold = new Map();
}
hold.set(data.name, o);
this.cache.set(dir, hold);
return o;
}
}
/**
* Fetches all files from a specified directory (default is the root folder: `/home/container`).
* @param {string} [dir] The directory (folder) to fetch from.
* @returns {Promise<Map<string, PteroFile>>}
*/
async fetch(dir) {
if (!this.isClient) return Promise.resolve();
dir &&= dir.startsWith('.') ? dir.slice(1) : dir;
dir &&= encodeURIComponent(dir);
const data = await this.client.requests.make(
endpoints.servers.files.main(this.server.identifier) + (dir ? `?directory=${dir}` : '')
);
data._dir = dir ?? '/';
return this._patch(data);
}
/**
* Returns the contents of a specified file.
* @param {string} filePath The path to the file in the server.
* @returns {Promise<string>}
*/
async getContents(filePath) {
if (!this.isClient) return Promise.resolve();
if (filePath.startsWith('.')) filePath = filePath.slice(1);
filePath = encodeURIComponent(filePath);
const data = await this.client.requests.make(
endpoints.servers.files.contents(this.server.identifier, filePath)
);
return data.toString();
}
/**
* Returns a URL that can be used to download the specified file.
* @param {string} filePath The path to the file in the server.
* @returns {Promise<string>}
*/
async download(filePath) {
if (!this.isClient) return Promise.resolve();
if (filePath.startsWith('.')) filePath = filePath.slice(1);
filePath = encodeURIComponent(filePath);
const data = await this.client.requests.make(
endpoints.servers.files.download(this.server.identifier, filePath)
);
return data.attributes.url;
}
/**
* Renames the specified file.
* EXPERIMENTAL: may not be working properly.
* @param {string} filePath The path to the file in the server.
* @param {string} name The new name of the file.
* @returns {Promise<void>}
* @protected
*/
async rename(filePath, name) {
if (!this.isClient) return Promise.resolve();
filePath ??= '/';
filePath = filePath.startsWith('.') ? filePath.slice(1) : filePath;
const sub = filePath.split('/');
await this.client.requests.make(
endpoints.servers.files.rename(this.server.identifier),
{
root: filePath,
files:[{
from: sub.pop(),
to: name
}]
},
'PUT'
);
}
/**
* Creates a copy of the specified file in the same directory.
* @param {string} filePath The path to the file in the server.
* @returns {Promise<void>}
*/
async copy(filePath) {
if (!this.isClient) return Promise.resolve();
if (filePath.startsWith('.')) filePath = filePath.slice(1);
await this.client.requests.make(
endpoints.servers.files.copy(this.server.identifier),
{ location: filePath }, 'POST'
);
}
/**
* Writes content to the specified file.
* @param {string} filePath The path to the file in the server.
* @param {string|Buffer} content The content to write to the file.
* @returns {Promise<void>}
*/
async write(filePath, content) {
if (!this.isClient) return Promise.resolve();
if (filePath.startsWith('.')) filePath = filePath.slice(1);
filePath = encodeURIComponent(filePath);
if (content instanceof Buffer) content = content.toString();
await this.client.requests.make(
endpoints.servers.files.write(this.server.identifier, filePath),
{ raw: content }, 'POST'
);
}
/**
* Compresses one or more files into a zip file (`tar.gz`).
* @param {string} dir The directory (folder) of the file(s).
* @param {string[]} files An array of the file name(s) to compress.
* @returns {Promise<PteroFile>} The compressed file.
*/
async compress(dir, files) {
if (!this.isClient) return Promise.resolve();
if (!Array.isArray(files)) throw new TypeError('Files must be an array.');
if (!files.every(n => typeof n === 'string')) throw new Error('File names must be type string.');
if (dir.startsWith('.')) dir = dir.slice(1);
const data = await this.client.requests.make(
endpoints.servers.files.compress(this.server.identifier),
{ root: dir, files }, 'POST'
);
return this._patch(data);
}
/**
* Decompresses a zip file to it's original contents.
* @param {string} dir The directory (folder) of the file.
* @param {string} file The name of file to decompress.
* @returns {Promise<void>}
*/
async decompress(dir, file) {
if (!this.isClient) return Promise.resolve();
if (dir.startsWith('.')) dir = dir.slice(1);
if (file.startsWith('.')) file = file.slice(1);
await this.client.requests.make(
endpoints.servers.files.decompress(this.server.identifier),
{ root: dir, file }, 'POST'
);
}
/**
* Deletes one or more files in the specified directory.
* @param {string} dir The directory (folder) of the file(s).
* @param {string[]} files An array of the file name(s) to delete.
* @returns {Promise<void>}
*/
async delete(dir, files) {
if (!this.isClient) return Promise.resolve();
if (!Array.isArray(files)) throw new TypeError('Files must be an array.');
if (!files.every(n => typeof n === 'string')) throw new Error('File names must be type string.');
if (dir.startsWith('.')) dir = dir.slice(1);
await this.client.requests.make(
endpoints.servers.files.delete(this.server.identifier),
{ root: dir, files }, 'POST'
);
}
/**
* Creates a new folder in a specified directory.
* @param {string} dir The directory (folder) to create the folder in.
* @param {string} name The name of the folder.
* @returns {Promise<void>}
*/
async createFolder(dir, name) {
if (!this.isClient) return Promise.resolve();
await this.client.requests.make(
endpoints.servers.files.create(this.server.identifier),
{ root: dir, name }, 'POST'
);
}
/**
* Returns an upload URL that can be used to upload files to the server.
* @returns {Promise<string>} The upload URL.
*/
async getUploadURL() {
if (!this.isClient) return Promise.resolve();
const data = await this.client.requests.make(
endpoints.servers.files.upload(this.server.identifier)
);
return data.attributes.url;
}
}
module.exports = FileManager;
/**
* Represents a file-like object on a Pterodactyl server.
* The object can be an instance of a file, directory or symbolic link.
* * {@link PteroFile.isFile}
* * {@link PteroFile.isSymlink}
* @typedef {object} PteroFile
* @property {string} name The name of the file.
* @property {string} mode The file permissions mode.
* @property {bigint} modeBits The bitfield representatio of the mode.
* @property {number} size The size of the file (bytes).
* @property {boolean} isFile Whether the object is a file.
* @property {boolean} isSymlink Whether the file is a symbolic link.
* @property {boolean} isEditable Whether the file can be edited.
* @property {string} mimetype The mimetype of the file.
* @property {Date} createdAt The date when the database was created.
* @property {?Date} modifiedAt The date of the last recorded modification.
*/

View File

@@ -1,20 +1,36 @@
const AllocationManager = require('../managers/AllocationManager');
const DatabaseManager = require('../managers/DatabaseManager');
const FileManager = require('../managers/FileManager');
const { PteroUser } = require('./User');
const Node = require('./Node');
const caseConv = require('./caseConv');
const caseConv = require('../util/caseConv');
const endpoints = require('../application/endpoints');
class ApplicationServer {
constructor(client, data) {
this.client = client;
/**
* The of the server (separate from UUID).
* @type {number}
*/
this.id = data.id;
/**
* The internal UUID of the server.
* @type {string}
*/
this.uuid = data.uuid;
/**
* A substring of the server's UUID to easily identify it.
* @type {string}
*/
this.identifier = data.identifier;
/**
* The date the server was created.
* @type {Date}
*/
this.createdAt = new Date(data.created_at);
/** @type {number} */
this.createdTimestamp = this.createdAt.getTime();
@@ -23,28 +39,14 @@ class ApplicationServer {
* @type {?Date}
*/
this.updatedAt = data.updated_at ? new Date(data.updated_at) : null;
/** @type {?number} */
this.updatedTimestamp = this.updatedAt?.getTime() || null;
/** @type {DatabaseManager} */
this.databases = new DatabaseManager(client, data);
/** @type {FileManager} */
this.files = new FileManager(client, data);
/** @type {AllocationManager} */
this.allocations = new AllocationManager(client, this, data);
this._patch(data);
}
_patch(data) {
if ('id' in data) {
/**
* The of the server (separate from UUID).
* @type {number}
*/
this.id = data.id;
}
if ('external_id' in data) {
/**
* The external ID of the server (if set).
@@ -53,22 +55,6 @@ class ApplicationServer {
this.externalId = data.external_id ?? null;
}
if ('uuid' in data) {
/**
* The internal UUID of the server.
* @type {string}
*/
this.uuid = data.uuid;
}
if ('identifier' in data) {
/**
* A substring of the server's UUID to easily identify it.
* @type {string}
*/
this.identifier = data.identifier;
}
if ('name' in data) {
/**
* The name of the server.
@@ -136,13 +122,13 @@ class ApplicationServer {
this.nodeId = data.node;
}
if ('-' in data) {
if (!this.node) {
/**
* The node object that the server is part of. This can be fetched by including
* 'node' in the ApplicationServerManager.fetch.
* @type {?Node}
*/
this.node = null;
this.node = this.client.nodes.resolve(data);
}
if ('allocation' in data) {
@@ -177,6 +163,22 @@ class ApplicationServer {
}
}
/**
* Returns a formatted URL to the server.
* @returns {string} The formatted URL.
*/
get panelURL() {
return `${this.client.domain}/server/${this.identifier}`;
}
/**
* Returns a formatted URL to the server in the admin panel.
* @returns {string} The formatted URL.
*/
get adminURL() {
return `${this.client.domain}/admin/servers/view/${this.id}`;
}
/**
* Fetches the PteroUser object of the server owner.
* The user can be accessed via {@link ApplicationServer.owner}.
@@ -208,8 +210,8 @@ class ApplicationServer {
payload.external_id = options.externalId ?? this.externalId;
payload.description = options.description ?? this.description;
await this.client.requests.make(
endpoints.servers.details(this.id), payload, 'PATCH'
await this.client.requests.patch(
endpoints.servers.details(this.id), payload
);
this._patch(payload);
@@ -218,10 +220,44 @@ class ApplicationServer {
/**
* Updates the server's build structure.
* @param {object} options Build options.
* @todo
* @param {object} options Update build options.
* @param {number} [options.allocation] The ID of the allocation for the server.
* @param {number} [options.swap] Server space swap option.
* @param {number} [options.memory] The amount of memory allowed for the server.
* @param {number} [options.disk] The amount of disk allowed for the server.
* @param {number} [options.cpu] The amount of CPU to allow for the server.
* @param {?number} [options.threads] The number of threads for the server.
* @param {number} [options.io]
* @param {object} [options.featureLimits] Feature limits options.
* @param {number} [options.featureLimits.allocations] The server allocations limit.
* @param {number} [options.featureLimits.backups] The server backups limit.
* @param {number} [options.featureLimits.databases] The server databases limit.
* @returns {Promise<ApplicationServer>} The updated server instance.
*/
async updateBuild(options = {}) {}
async updateBuild(options = {}) {
if (!Object.keys(options).length) throw new Error('Too few options to update.');
options.allocation ??= this.allocation;
options.swap ??= this.limits.swap ?? 0;
options.memory ??= this.memory;
options.disk ??= this.disk;
options.cpu ??= this.limits.cpu ?? 0;
options.threads ??= this.limits.threads;
options.io ??= this.limits.io;
options.featureLimits ??= {};
options.featureLimits.allocations ??= this.featureLimits.allocations ?? 0;
options.featureLimits.backups ??= this.featureLimits.backups ?? 0;
options.featureLimits.databases ??= this.featureLimits.databases ?? 0;
// TODO: caseConv update
options.feature_limits = caseConv.snakeCase(options.featureLimits);
await this.client.requests.patch(
endpoints.servers.build(this.id), options
);
this._patch(options);
return this;
}
/**
* Updates the server's startup configuration.
@@ -235,7 +271,9 @@ class ApplicationServer {
* @returns {Promise<void>}
*/
async suspend() {
await this.client.requests.make(endpoints.servers.suspend(this.id), null, 'POST');
await this.client.requests.post(
endpoints.servers.suspend(this.id), null
);
this.suspended = true;
}
@@ -244,7 +282,9 @@ class ApplicationServer {
* @returns {Promise<void>}
*/
async unsuspend() {
await this.client.requests.make(endpoints.servers.unsuspend(this.id), null, 'POST');
await this.client.requests.post(
endpoints.servers.unsuspend(this.id), null
);
this.suspended = false;
}
@@ -253,7 +293,9 @@ class ApplicationServer {
* @returns {Promise<void>}
*/
async reinstall() {
await this.client.requests.make(endpoints.servers.reinstall(this.id), null, 'POST');
await this.client.requests.post(
endpoints.servers.reinstall(this.id), null
);
}
/**

View File

@@ -1,8 +1,8 @@
const SubUserManager = require('../client/SubUserManager');
const AllocationManager = require('../managers/AllocationManager');
const DatabaseManager = require('../managers/DatabaseManager');
const FileManager = require('../managers/FileManager');
const ClientDatabaseManager = require('../client/ClientDatabaseManager');
const FileManager = require('../client/FileManager');
const NetworkAllocationManager = require('../client/NetworkAllocationManager');
const Permissions = require('./Permissions');
const SubUserManager = require('../client/SubUserManager');
const endpoints = require('../client/endpoints');
class ClientServer {
@@ -10,14 +10,30 @@ class ClientServer {
this.client = client;
const attr = data.attributes;
/**
* The internal UUID of the server.
* @type {string}
*/
this.uuid = attr.uuid;
/**
* A substring of the server's UUID to easily identify it.
* @type {string}
*/
this.identifier = attr.identifier;
/** @type {SubUserManager} */
this.users = new SubUserManager(client, this);
/** @type {AllocationManager} */
this.allocations = new AllocationManager(client, this, attr.relationships);
/** @type {NetworkAllocationManager} */
this.allocations = new NetworkAllocationManager(client, this);
/** @type {Permissions} */
this.permissions = new Permissions(data.meta?.user_permissions ?? {});
/** @type {DatabaseManager} */
this.databases = new DatabaseManager(client, this, attr.relationships);
/** @type {ClientDatabaseManager} */
this.databases = new ClientDatabaseManager(client, this, attr.relationships);
/** @type {FileManager} */
this.files = new FileManager(client, this, attr.relationships);
@@ -33,22 +49,6 @@ class ClientServer {
this.isOwner = data.server_owner;
}
if ('identifier' in data) {
/**
* A substring of the server's UUID to easily identify it.
* @type {string}
*/
this.identifier = data.identifier;
}
if ('uuid' in data) {
/**
* The internal UUID of the server.
* @type {string}
*/
this.uuid = data.uuid;
}
if ('name' in data) {
/**
* The name of the server.
@@ -127,6 +127,14 @@ class ClientServer {
}
}
/**
* Returns a formatted URL to the server.
* @returns {string} The formatted URL.
*/
get panelURL() {
return `${this.client.domain}/server/${this.identifier}`;
}
/**
* Adds the server to the WebSocket connection list to be established.
* @returns {void}
@@ -140,7 +148,7 @@ class ClientServer {
}
/** @todo */
get resources() {}
async fetchResources() {}
/**
* Sends a command to the server terminal.
@@ -148,8 +156,8 @@ class ClientServer {
* @returns {Promise<void>}
*/
async sendCommand(command) {
await this.client.requests.make(
endpoints.servers.command(this.identifier), { command }, 'POST'
await this.client.requests.post(
endpoints.servers.command(this.identifier), { command }
);
}
@@ -163,9 +171,11 @@ class ClientServer {
* @returns {Promise<void>}
*/
async setPowerState(state) {
if (!['start', 'stop', 'restart', 'kill'].includes(state)) throw new Error('Invalid power state.');
await this.client.requests.make(
endpoints.servers.power(this.identifier), { signal: state }, 'POST'
if (!['start', 'stop', 'restart', 'kill'].includes(state))
throw new Error('Invalid power state.');
await this.client.requests.post(
endpoints.servers.power(this.identifier), { signal: state }
);
this.state = state;
}

View File

@@ -4,9 +4,12 @@ exports.RequestError = class RequestError extends Error {
exports.PteroAPIError = class PteroAPIError extends Error {
constructor(data) {
data = data.errors[0];
super(`[${data.status}] ${data.detail}`);
this.code = data.code;
const fmt = data.errors.map(
e => `- ${e.status}: ${e.detail || 'No details provided'}`
).join('\n');
super('\n'+ fmt);
this.code = data.errors[0].code;
}
}

View File

@@ -1,5 +1,5 @@
const { NodeLocation } = require('../application/NodeLocationManager');
const caseConv = require('./caseConv');
const caseConv = require('../util/caseConv');
const endpoints = require('../application/endpoints');
class Node {
@@ -7,6 +7,18 @@ class Node {
this.client = client;
data = data.attributes;
/**
* The ID of the node.
* @type {number}
*/
this.id = data.id;
/**
* The internal UUID of the node.
* @type {string}
*/
this.uuid = data.uuid;
/**
* The date the node was created.
* @type {Date}
@@ -23,22 +35,6 @@ class Node {
}
_patch(data) {
if ('id' in data) {
/**
* The ID of the node.
* @type {number}
*/
this.id = data.id;
}
if ('uuid' in data) {
/**
* The internal UUID of the node.
* @type {string}
*/
this.uuid = data.uuid;
}
if ('public' in data) {
/**
* Whether the node is public to other users.
@@ -174,12 +170,22 @@ class Node {
}
}
/**
* Returns a formatted URL to the node in the admin panel.
* @returns {string} The formatted URL.
*/
get adminURL() {
return `${this.client.domain}/admin/nodes/view/${this.id}`;
}
/**
* Returns the node's config (untyped).
* @returns {Promise<object>} The node config.
*/
async getConfig() {
return await this.client.requests.make(endpoints.nodes.config(this.id));
return await this.client.requests.get(
endpoints.nodes.config(this.id)
);
}
/**

View File

@@ -13,42 +13,42 @@ const FLAGS = {
FILE_CREATE: 9,
FILE_READ: 10,
FILE_UPDATE: 11,
FILE_DELETE: 12,
FILE_ARCHIVE: 13,
FILE_SFTP: 14,
'FILE_READ-CONTENT': 11,
FILE_UPDATE: 12,
FILE_DELETE: 13,
FILE_ARCHIVE: 14,
FILE_SFTP: 15,
BACKUP_CREATE: 15,
BACKUP_READ: 16,
BACKUP_UPDATE: 17,
BACKUP_DELETE: 18,
BACKUP_CREATE: 16,
BACKUP_READ: 17,
BACKUP_UPDATE: 18,
BACKUP_DELETE: 19,
ALLOCATION_READ: 19,
ALLOCATION_CREATE: 20,
ALLOCATION_UPDATE: 21,
ALLOCATION_DELETE: 22,
ALLOCATION_READ: 20,
ALLOCATION_CREATE: 21,
ALLOCATION_UPDATE: 22,
ALLOCATION_DELETE: 23,
STARTUP_READ: 23,
STARTUP_UPDATE: 24,
STARTUP_READ: 24,
STARTUP_UPDATE: 25,
DATABASE_CREATE: 25,
DATABASE_READ: 26,
DATABASE_UPDATE: 27,
DATABASE_DELETE: 28,
DATABASE_VIEW_PASSWORD: 29,
DATABASE_CREATE: 26,
DATABASE_READ: 27,
DATABASE_UPDATE: 28,
DATABASE_DELETE: 29,
DATABASE_VIEW_PASSWORD: 30,
SCHEDULE_CREATE: 30,
SCHEDULE_READ: 31,
SCHEDULE_UPDATE: 32,
SCHEDULE_DELETE: 33,
SCHEDULE_CREATE: 31,
SCHEDULE_READ: 32,
SCHEDULE_UPDATE: 33,
SCHEDULE_DELETE: 34,
SETTINGS_RENAME: 34,
SETTINGS_REINSTALL: 35,
SETTINGS_RENAME: 35,
SETTINGS_REINSTALL: 36,
'*': 40,
ADMIN_WEBSOCKET_ERRORS: 41,
ADMIN_WEBSOCKET_INSTALL: 42,
ADMIN_WEBSOCKET_TRANSFER: 43
ADMIN_WEBSOCKET_ERRORS: 40,
ADMIN_WEBSOCKET_INSTALL: 41,
ADMIN_WEBSOCKET_TRANSFER: 42
}
class Permissions {
@@ -163,6 +163,7 @@ class Permissions {
*/
static fromStrings(perms) {
const res = {};
if (perms.includes('*')) return Object.assign({}, Permissions.FLAGS);
for (let p of perms) {
p = p.toUpperCase().replace(/\./g, '_');
if (Permissions.FLAGS[p] === undefined) throw new Error(`Unknown permission '${p}'.`);

View File

@@ -1,50 +0,0 @@
const { ApplicationOptions } = require('../application/PteroApp');
const { ClientOptions } = require('../client/PteroClient');
/**
* Creates a preset application option object with all the options not
* specified by the user.
* @param {object} data Data to parse application options from.
* @returns {ApplicationOptions}
* @deprecated Use configLoader util instead.
*/
function application(data) {
if (typeof data !== 'object') throw new TypeError('Invalid application options object.');
data.fetchUsers ??= false;
data.fetchNodes ??= false;
data.fetchNests ??= false;
data.fetchServers ??= false;
data.fetchLocations ??= false;
data.cacheUsers ??= true;
data.cacheNodes ??= true;
data.cacheNests ??= true;
data.cacheServers ??= true;
data.cacheLocations ??= true;
return data;
}
/**
* Creates a preset client option object with all the options not
* specified by the user.
* @param {object} data Data to parse client options from.
* @returns {ClientOptions}
* @deprecated Use configLoader util instead.
*/
function client(data) {
if (typeof data !== 'object') throw new TypeError('Invalid client options object.');
data.ws ??= false;
data.fetchClient ??= true;
data.fetchServers ??= false;
data.cacheServers ??= true;
data.cacheSubUsers ??= true;
data.disableEvents ??= [];
return data;
}
exports.application = application;
exports.client = client;

View File

@@ -5,6 +5,7 @@ class Schedule {
constructor(client, serverId, data) {
this.client = client;
this.serverId = serverId;
/** @type {Dict<number, ScheduleTask>} */
this.tasks = new Dict();
data = data.attributes;
@@ -111,10 +112,19 @@ class Schedule {
createdAt: new Date(data.created_at),
updatedAt: data.updated_at ? new Date(data.updated_at) : null
}
this.tasks.set(obj.id, obj);
return obj;
}
/**
* Returns a formatted URL to the schedule.
* @returns {string} The formatted URL.
*/
get panelURL() {
return `${this.client.domain}/server/${this.serverId}/schedules/${this.id}`;
}
/**
* Updates the schedule.
* @param {object} options Schedule update options.
@@ -138,10 +148,12 @@ class Schedule {
* @returns {Promise<ScheduleTask>} The new schedule task.
*/
async createTask(action, payload, offset) {
if (!['command', 'power', 'backup'].includes(action)) throw new TypeError('Invalid task action type.');
const data = await this.client.requests.make(
if (!['command', 'power', 'backup'].includes(action))
throw new TypeError('Invalid task action type.');
const data = await this.client.requests.post(
endpoints.servers.schedules.tasks.main(this.serverId, this.id),
{ action, payload, time_offset: offset }, 'POST'
{ action, payload, time_offset: offset }
);
return this._resolveTask(data);
}
@@ -156,14 +168,16 @@ class Schedule {
* @returns {Promise<ScheduleTask>} The updated schedule task.
*/
async updateTask(id, options = {}) {
if (Object.keys(options).length < 3) throw new Error('Missing required ScheduleTask update options.');
if (Object.keys(options).length < 3)
throw new Error('Missing required ScheduleTask update options.');
if (!['command', 'power', 'backup'].includes(options.action))
throw new TypeError('Invalid task action type.');
options.time_offset = options.offset;
const data = await this.client.requests.make(
const data = await this.client.requests.post(
endpoints.servers.schedules.tasks.get(this.serverId, this.id, id),
options, 'POST'
options
);
return this._resolveTask(data);
}
@@ -174,9 +188,8 @@ class Schedule {
* @returns {Promise<boolean>}
*/
async deleteTask(id) {
await this.client.requests.make(
endpoints.servers.schedules.tasks.get(this.serverId, this.id, id),
null, 'DELETE'
await this.client.requests.delete(
endpoints.servers.schedules.tasks.get(this.serverId, this.id, id)
);
this.tasks.delete(id);
return true;

View File

@@ -2,9 +2,11 @@ const ApplicationServer = require('./ApplicationServer');
const Permissions = require('./Permissions');
const { PermissionResolvable } = require('./Permissions');
const Dict = require('./Dict');
const caseConv = require('./caseConv');
const caseConv = require('../util/caseConv');
const c_path = require('../client/endpoints');
let loggedDeprecated = false;
class BaseUser {
constructor(client, data) {
this.client = client;
@@ -63,6 +65,11 @@ class BaseUser {
class PteroUser extends BaseUser {
constructor(client, data) {
super(client, data);
/** @type {string} */
this.uuid = data.uuid;
this._patch(data);
}
_patch(data) {
@@ -73,24 +80,34 @@ class PteroUser extends BaseUser {
this.externalId = data.external_id;
}
if ('uuid' in data) {
/** @type {string} */
this.uuid = data.uuid;
}
if ('root_admin' in data) {
/** @type {boolean} */
this.isAdmin = data.root_admin ?? false;
}
if ('2fa' in data) {
/** @type {boolean} */
/**
* @type {boolean}
* @deprecated Use {@link PteroUser.twoFactor} instead.
*/
this.tfa = data['2fa'];
/** @type {boolean} */
this.twoFactor = data['2fa'];
if (!loggedDeprecated) {
process.emitWarning(
"'PteroUser#tfa' is deprecated, use 'PteroUser#twoFactor' instead",
'Deprecated'
);
loggedDeprecated = true;
}
}
if ('created_at' in data) {
/** @type {Date} */
this.createdAt = new Date(data.created_at);
/** @type {number} */
this.createdTimestamp = this.createdAt.getTime();
}
@@ -98,6 +115,7 @@ class PteroUser extends BaseUser {
if ('updated_at' in data) {
/** @type {?Date} */
this.updatedAt = data['updated_at'] ? new Date(data['updated_at']) : null;
/** @type {?number} */
this.updatedTimestamp = this.updatedAt?.getTime() || null;
}
@@ -111,6 +129,14 @@ class PteroUser extends BaseUser {
}
}
/**
* Returns a formatted URL to the user in the admin panel.
* @returns {string} The formatted URL.
*/
get adminURL() {
return `${this.client.domain}/admin/users/view/${this.id}`;
}
/**
* Updates the specified user's account.
* @param {number|PteroUser} user The user to update.
@@ -140,11 +166,15 @@ class PteroSubUser extends BaseUser {
constructor(client, server, data) {
super(client, data);
/** @type {string} */
this.uuid = data.uuid;
/** @type {string} */
this._server = server;
/** @type {Date} */
this.createdAt = new Date(data.created_at);
/** @type {number} */
this.createdTimestamp = this.createdAt.getTime();
@@ -157,11 +187,6 @@ class PteroSubUser extends BaseUser {
_patch(data) {
super._patch(data);
if ('uuid' in data) {
/** @type {string} */
this.uuid = data.uuid;
}
if ('image' in data) {
/** @type {string} */
this.image = data.image;
@@ -173,6 +198,14 @@ class PteroSubUser extends BaseUser {
}
}
/**
* Returns a formatted URL to the subuser.
* @returns {string} The formatted URL.
*/
get panelURL() {
return `${this.client.domain}/server/${this._server}/users`;
}
/**
* Updates the subuser's server permissions.
* @param {PermissionResolvable} perms The permissions to set.
@@ -180,8 +213,9 @@ class PteroSubUser extends BaseUser {
*/
async setPermissions(perms) {
perms = new Permissions(perms);
await this.client.requests.make(
c_path.servers.users.get(this._server, this.uuid), { permissions: perms.toStrings() }, 'POST'
await this.client.requests.post(
c_path.servers.users.get(this._server, this.uuid),
{ permissions: perms.toStrings() }
);
this.permissions = perms;
return this;
@@ -209,6 +243,14 @@ class ClientUser extends BaseUser {
this.apikeys = [];
}
/**
* Returns a formatted URL to the client account.
* @returns {string} The formatted URL.
*/
get panelURL() {
return `${this.client.domain}/account`;
}
/**
* Fetches a 2FA code linked to the client's account.
* @returns {Promise<string>} The 2FA code.
@@ -224,8 +266,8 @@ class ClientUser extends BaseUser {
* @returns {Promise<string[]>} The auth tokens.
*/
async enable2fa(code) {
const data = await this.client.requests.make(
c_path.account.tfa, { code }, 'POST'
const data = await this.client.requests.post(
c_path.account.tfa, { code }
);
this.tokens.push(...data.attributes.tokens);
return this.tokens;
@@ -237,8 +279,8 @@ class ClientUser extends BaseUser {
* @returns {Promise<void>}
*/
async disable2fa(password) {
await this.client.requests.make(
c_path.account.tfa, { password }, 'DELETE'
await this.client.requests.delete(
c_path.account.tfa, { password }
);
this.tokens = [];
}
@@ -250,8 +292,8 @@ class ClientUser extends BaseUser {
* @returns {Promise<ClientUser>} The updated client user instance.
*/
async updateEmail(email, password) {
await this.client.requests.make(
c_path.account.email, { email, password }, 'PUT'
await this.client.requests.put(
c_path.account.email, { email, password }
);
this.email = email;
return this;
@@ -265,15 +307,14 @@ class ClientUser extends BaseUser {
* @returns {Promise<void>}
*/
async updatePassword(oldpass, newpass) {
if (oldpass === newpass) return;
return await this.client.requests.make(
if (oldpass === newpass) return Promise.resolve();
return await this.client.requests.put(
c_path.account.password,
{
current_password: oldpass,
password: newpass,
password_confirmation: newpass
},
'PUT'
}
);
}
@@ -284,6 +325,7 @@ class ClientUser extends BaseUser {
async fetchKeys() {
const data = await this.client.requests.make(c_path.account.apikeys);
this.apikeys = [];
for (let o of data.data) {
o = o.attributes;
this.apikeys.push({
@@ -294,6 +336,7 @@ class ClientUser extends BaseUser {
createdAt: new Date(o.created_at)
});
}
return this.apikeys;
}
@@ -304,11 +347,11 @@ class ClientUser extends BaseUser {
* @returns {Promise<APIKey>} The new API key.
*/
async createKey(description, allowed = []) {
const data = await this.client.requests.make(
const data = await this.client.requests.post(
c_path.account.apikeys,
{ description, allowed_ips: allowed },
'POST'
{ description, allowed_ips: allowed }
);
const att = data.attributes;
this.apikeys.push({
identifier: att.identifier,
@@ -317,6 +360,7 @@ class ClientUser extends BaseUser {
lastUsedAt: att.last_used_at ? new Date(att.last_used_at) : null,
createdAt: new Date(att.created_at)
});
return this.apikeys.find(k => k.identifier === att.identifier);
}
@@ -326,7 +370,9 @@ class ClientUser extends BaseUser {
* @returns {Promise<void>}
*/
async deleteKey(id) {
await this.client.requests.make(c_path.account.apikeys +`/${id}`, null, 'DELETE');
await this.client.requests.delete(
c_path.account.apikeys +`/${id}`
);
this.apikeys = this.apikeys.filter(k => k.identifier !== id);
}
}

View File

@@ -1,60 +0,0 @@
/**
* Parses a class or object into a JSON object with camelCase keys.
* @param {any} obj The object to convert.
* @param {string[]} [ignore] An array of keys to ignore when parsing.
* @returns {object} The parsed JSON object.
*/
function camelCase(obj, ignore = []) {
const entries = Object.entries(obj);
const parsed = {};
for (const [k, v] of entries) {
if (ignore.includes(k)) continue;
parsed[toCamelCase(k)] = v;
}
return parsed;
}
function toCamelCase(str) {
let res = '';
let next = false;
str.split('').forEach(c => {
if (next) {
next = false;
res += c.toUpperCase();
} else if (c === '_') {
next = true;
} else res += c;
});
return res;
}
/**
* Parses a class or object into a JSON object with snake_case keys.
* @param {any} obj The object to convert.
* @param {string[]} [ignore] An array of keys to ignore when parsing.
* @returns {object} The parsed JSON object.
*/
function snakeCase(obj, ignore = []) {
const entries = Object.entries(obj);
const parsed = {};
for (const [k, v] of entries) {
if (ignore.includes(k)) continue;
parsed[toSnakeCase(k)] = v;
}
return parsed;
}
function toSnakeCase(str) {
let res = '';
str.split('').forEach(c => {
if (isUpper(c)) res += '_';
res += c.toLowerCase();
});
return res;
}
function isUpper(c) {
return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').includes(c);
}
module.exports = { camelCase, snakeCase };

View File

@@ -1,89 +0,0 @@
const { join } = require('path');
const DEFAULT = {
APPLICATION:{
users:{
fetch: false,
cache: true,
max: -1
},
nodes:{
fetch: false,
cache: true,
max: -1
},
nests:{
fetch: false,
cache: true,
max: -1
},
servers:{
fetch: false,
cache: true,
max: -1
},
locations:{
fetch: false,
cache: true,
max: -1
}
},
CLIENT:{
ws: false,
fetchClient: true,
servers:{
fetch: false,
cache: true,
max: -1
},
subUsers:{
fetch: false,
cache: true,
max: -1
},
disableEvents:[]
}
}
function parseAs(from, to) {
const res = {};
for (const [k, v] of Object.entries(to)) res[k] = k in from ? from[k] : v;
for (const [k, v] of Object.entries(res)) if (v.max === -1) res[k].max = Infinity;
return res;
}
function appConfig(options) {
if (
options !== null &&
typeof options === 'object' &&
Object.keys(options).length
) return parseAs(options, DEFAULT.APPLICATION);
try {
options = require(join(process.cwd(), 'pterojs.json'));
return parseAs(options.application ?? {}, DEFAULT.APPLICATION);
} catch {
return DEFAULT.APPLICATION;
}
}
function clientConfig(options) {
if (
options !== null &&
typeof options === 'object' &&
Object.keys(options).length
) return parseAs(options, DEFAULT.CLIENT);
try {
options = require(join(process.cwd(), 'pterojs.json'));
return parseAs(options.client ?? {}, DEFAULT.CLIENT);
} catch {
return DEFAULT.CLIENT;
}
}
module.exports = {
DEFAULT,
parseAs,
appConfig,
clientConfig
}

View File

@@ -1,28 +0,0 @@
const { PteroApp } = require('@devnote-dev/pterojs');
const { assert } = require('.');
const { api_url, app_key } = require('./auth.json');
module.exports = async () => {
const client = new PteroApp(api_url, app_key);
await client.connect();
const users = await client.users.fetch();
assert(users.size);
const nodes = await client.nodes.fetch();
assert(nodes.size);
const nests = await client.nests.fetch();
assert(nests.size);
const servers = await client.servers.fetch();
assert(servers.size);
const locations = await client.locations.fetch();
assert(locations.size);
console.log(
`Fetch Results:\n${users.size} Users\n${nodes.size} `+
`Node(s)\n${nests.size} Nest(s)\n${servers.size} server(s)`+
`\n${locations.size} location(s).`
);
client.disconnect();
delete users, nodes, nests, servers, locations;
}

View File

@@ -1,26 +1,24 @@
const { PteroClient } = require('@devnote-dev/pterojs');
const { assert } = require('.');
const { api_url, client_key } = require('./auth.json');
const assert = require('assert');
const { PteroClient } = require('../src');
const { api_url, api_key } = require('./auth.json');
module.exports = async () => {
const client = new PteroClient(api_url, client_key, { fetchClient: true });
await client.connect();
const app = new PteroClient(api_url, api_key);
assert(client.user !== null);
const servers = await client.servers.fetch();
assert(servers.size);
const server = servers.random();
const users = await server.users.fetch();
assert(users.size !== null);
const schedules = await client.schedules.fetch(server.identifier);
assert(schedules.size !== null);
assert.doesNotThrow(
(async () => await app.connect()),
'could not connect to api'
);
console.log(
`Fetch Results:\n${servers.size} Server(s)\n${users.size} `+
`User(s) in server ${server.identifier}\n${schedules.size} Schedule(s) `+
`for server ${server.identifier}`
);
assert.doesNotThrow(
(async () => await app.fetchClient()),
'could not fetch users endpoint'
);
client.disconnect();
delete servers, server, users, schedules;
}
assert.ok(app.user, 'user not fetched');
assert.doesNotThrow(
(async () => await app.servers.fetch()),
'could not fetch servers endpoint'
);
app.disconnect();

View File

@@ -1,49 +1,13 @@
class AssertionError extends Error {
constructor() { super('Assertion failed') }
}
function logTest(test) {
console.log(
'-'.repeat(15 + test.baseReturn.length) +'\n'+
`Running Test\nName : ${test.name}\n`+
`Description : ${test.description}\n`+
`Return : ${test.baseReturn}\n`+
'-'.repeat(15 + test.baseReturn.length)
);
}
function assert(ops) {
if (Boolean(ops) !== true) throw new AssertionError();
}
function test(name, exec, returnValue = null) {
this.meta = {
name,
description: null,
group: null,
baseReturn: (returnValue && typeof returnValue) || 'null'
}
logTest(this.meta);
if (returnValue !== undefined) {
assert(exec() === returnValue);
} else {
assert(exec());
}
}
module.exports = { test, assert };
(async () => {
console.log('Running all tests...');
const { readdirSync } = require('fs');
let count = 0, test;
let count = 0;
for (const mod of readdirSync(__dirname)) {
if (!mod.endsWith('.test.js')) continue;
console.log(`\nRunning Test #${count++}\n`+ '='.repeat(20));
test = require(`${__dirname}/${mod}`);
console.log(test, '\n');
if (test) await test(); else 'Test Skipped...';
require(`${__dirname}/${mod}`);
}
console.log('Completed all tests.');
})();

View File

@@ -1 +0,0 @@
module.exports = () => {}

View File

@@ -1,30 +1,31 @@
const { NodeStatus } = require('@devnote-dev/pterojs');
const { assert } = require('.');
const assert = require('assert');
const { NodeStatus } = require('../src');
const { api_url, app_key } = require('./auth.json');
module.exports = async () => {
const status = new NodeStatus({
domain: api_url,
auth: app_key,
nodes:[1],
callInterval: 30_000
});
const status = new NodeStatus({
domain: api_url,
auth: app_key,
nodes:[1],
callInterval: 30_000
});
status.on('connect', id => console.log(`Connected to node ${id}`));
status.on('disconnect', id => console.log(`Disconnected from node ${id}`));
status.on('interval', node => {
assert(node !== null);
assert(Object.keys(node).length);
status.on('connect', id => console.log(`connected to node ${id}`));
status.on('disconnect', id => console.log(`disconnected from node ${id}`));
status.on('interval', node => {
assert.ok(node, 'invalid node payload received');
assert.ok(Object.entries(node).length, 'empty node payload received');
console.log(`
Node Info
ID: ${node.id}
Name: ${node.name}
Memory: ${node.memory}
Disk: ${node.disk}
`);
status.close();
});
console.log(`
Node Info
ID: ${node.id}
Name: ${node.name}
Memory: ${node.memory}
Disk: ${node.disk}
`);
status.close();
});
await status.connect();
}
assert.doesNotThrow(
(async () => await status.connect()),
'could not connect node status to api'
);

0
node_modules/@mapbox/node-pre-gyp/bin/node-pre-gyp generated vendored Normal file → Executable file
View File

BIN
node_modules/bcrypt/lib/binding/napi-v3/bcrypt_lib.node generated vendored Normal file → Executable file

Binary file not shown.

0
node_modules/bcrypt/test_alpine.sh generated vendored Normal file → Executable file
View File

18
node_modules/body-parser/HISTORY.md generated vendored
View File

@@ -1,3 +1,21 @@
1.20.0 / 2022-04-02
===================
* Fix error message for json parse whitespace in `strict`
* Fix internal error when inflated body exceeds limit
* Prevent loss of async hooks context
* Prevent hanging when request already read
* deps: depd@2.0.0
- Replace internal `eval` usage with `Function` constructor
- Use instance methods on `process` to check for listeners
* deps: http-errors@2.0.0
- deps: depd@2.0.0
- deps: statuses@2.0.1
* deps: on-finished@2.4.1
* deps: qs@6.10.3
* deps: raw-body@2.5.1
- deps: http-errors@2.0.0
1.19.2 / 2022-02-15
===================

10
node_modules/body-parser/README.md generated vendored
View File

@@ -342,6 +342,14 @@ to this middleware. This module operates directly on bytes only and you cannot
call `req.setEncoding` when using this module. The `status` property is set to
`500` and the `type` property is set to `'stream.encoding.set'`.
### stream is not readable
This error will occur when the request is no longer readable when this middleware
attempts to read it. This typically means something other than a middleware from
this module read the reqest body already and the middleware was also configured to
read the same request. The `status` property is set to `500` and the `type`
property is set to `'stream.not.readable'`.
### too many parameters
This error will occur when the content of the request exceeds the configured
@@ -453,4 +461,4 @@ app.use(bodyParser.text({ type: 'text/html' }))
[downloads-image]: https://img.shields.io/npm/dm/body-parser.svg
[downloads-url]: https://npmjs.org/package/body-parser
[github-actions-ci-image]: https://img.shields.io/github/workflow/status/expressjs/body-parser/ci/master?label=ci
[github-actions-ci-url]: https://github.com/expressjs/body-parser?query=workflow%3Aci
[github-actions-ci-url]: https://github.com/expressjs/body-parser/actions/workflows/ci.yml

28
node_modules/body-parser/lib/read.js generated vendored
View File

@@ -12,9 +12,11 @@
*/
var createError = require('http-errors')
var destroy = require('destroy')
var getBody = require('raw-body')
var iconv = require('iconv-lite')
var onFinished = require('on-finished')
var unpipe = require('unpipe')
var zlib = require('zlib')
/**
@@ -89,9 +91,14 @@ function read (req, res, next, parse, debug, options) {
_error = createError(400, error)
}
// unpipe from stream and destroy
if (stream !== req) {
unpipe(req)
destroy(stream, true)
}
// read off entire request
stream.resume()
onFinished(req, function onfinished () {
dump(req, function onfinished () {
next(createError(400, _error))
})
return
@@ -179,3 +186,20 @@ function contentstream (req, debug, inflate) {
return stream
}
/**
* Dump the contents of a request.
*
* @param {object} req
* @param {function} callback
* @api private
*/
function dump (req, callback) {
if (onFinished.isFinished(req)) {
callback(null)
} else {
onFinished(req, callback)
req.resume()
}
}

View File

@@ -37,7 +37,7 @@ module.exports = json
* %x0D ) ; Carriage return
*/
var FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*(.)/ // eslint-disable-line no-control-regex
var FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*([^\x20\x09\x0a\x0d])/ // eslint-disable-line no-control-regex
/**
* Create a middleware to parse JSON bodies.
@@ -122,7 +122,7 @@ function json (options) {
// assert charset per RFC 7159 sec 8.1
var charset = getCharset(req) || 'utf-8'
if (charset.substr(0, 4) !== 'utf-') {
if (charset.slice(0, 4) !== 'utf-') {
debug('invalid charset')
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
charset: charset,
@@ -152,7 +152,9 @@ function json (options) {
function createStrictSyntaxError (str, char) {
var index = str.indexOf(char)
var partial = str.substring(0, index) + '#'
var partial = index !== -1
? str.substring(0, index) + '#'
: ''
try {
JSON.parse(partial); /* istanbul ignore next */ throw new SyntaxError('strict violation')
@@ -173,7 +175,11 @@ function createStrictSyntaxError (str, char) {
*/
function firstchar (str) {
return FIRST_CHAR_REGEXP.exec(str)[1]
var match = FIRST_CHAR_REGEXP.exec(str)
return match
? match[1]
: undefined
}
/**

View File

@@ -1,7 +1,7 @@
{
"name": "body-parser",
"description": "Node.js body parsing middleware",
"version": "1.19.2",
"version": "1.20.0",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
@@ -12,13 +12,15 @@
"bytes": "3.1.2",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.8.1",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.9.7",
"raw-body": "2.4.3",
"type-is": "~1.6.18"
"on-finished": "2.4.1",
"qs": "6.10.3",
"raw-body": "2.5.1",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"devDependencies": {
"eslint": "7.32.0",
@@ -29,7 +31,7 @@
"eslint-plugin-promise": "5.2.0",
"eslint-plugin-standard": "4.1.0",
"methods": "1.1.2",
"mocha": "9.2.0",
"mocha": "9.2.2",
"nyc": "15.1.0",
"safe-buffer": "5.2.1",
"supertest": "6.2.2"
@@ -38,10 +40,12 @@
"lib/",
"LICENSE",
"HISTORY.md",
"SECURITY.md",
"index.js"
],
"engines": {
"node": ">= 0.8"
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
},
"scripts": {
"lint": "eslint .",

0
node_modules/color-support/bin.js generated vendored Normal file → Executable file
View File

8
node_modules/cookie/HISTORY.md generated vendored
View File

@@ -1,3 +1,11 @@
0.5.0 / 2022-04-11
==================
* Add `priority` option
* Fix `expires` option to reject invalid dates
* pref: improve default decode speed
* pref: remove slow string split in parse
0.4.2 / 2022-02-02
==================

Some files were not shown because too many files have changed in this diff Show More