DesarrolladoresSDK de TypeScript

SDK de Hostwolf para JavaScript & TypeScript

@hostwolf/sdk es el cliente tipado oficial de la API de Hostwolf. Con él controlas tus servidores de juegos desde código: encendido, consola, archivos, copias de seguridad, mods, jugadores, programaciones y más.

npm install https://hostwolf.net/dl/hostwolf-sdk-0.1.8.tgz

El SDK se sirve desde hostwolf.net en lugar del registro de npm. El comando de arriba lo añade a tu package.json como "@hostwolf/sdk": "https://hostwolf.net/dl/hostwolf-sdk-0.1.8.tgz", y lo importas como @hostwolf/sdk. pnpm, yarn y bun aceptan la misma URL. Cada versión tiene su propia URL; consulta Versiones.

Funciona en Node 18+, Bun y Deno, y no tiene dependencias en tiempo de ejecución. Para la consola en vivo en versiones de Node anteriores a la 22, ejecuta también npm install ws.

Inicio rápido#

Crea una clave API en panel.hostwolf.net/account/api y luego:

import { Hostwolf } from '@hostwolf/sdk';

const hw = new Hostwolf({ apiKey: process.env.HOSTWOLF_API_KEY! });

const { data: servers } = await hw.servers.list();
for (const s of servers) {
    const res = await hw.servers.resources(s.identifier);
    console.log(s.name, s.egg_name, res.current_state);
}

const id = servers[0].identifier;
await hw.servers.command(id, 'say Backup in 1 minute');
await hw.backups.create(id, { name: 'before-update' });
console.log(await hw.tail(id, { lines: 20 }));

Cada método de servidor recibe como primer argumento el identifier corto del servidor (los 8 caracteres de su URL en el panel) o su UUID. Los objetos de respuesta usan los nombres de campo de la API (snake_case). Sus tipos de TypeScript se exportan, p. ej. Server, Backup y FileObject.

Opciones#

new Hostwolf({
    apiKey: 'ptlc_...',                     // required
    baseUrl: 'https://panel.hostwolf.net',  // default
    timeoutMs: 30_000,                      // per request
    maxRetries: 2,                          // 429s, and 502/503/504 on GET
    maxRetryWaitMs: 10_000,                 // longer Retry-After waits fail fast instead
    fetch: customFetch,                     // optional
    WebSocket: customWebSocket,             // optional, for the console
});

Servidores#

MétodoQué hace
servers.list({ page, per_page })Una página de tus servidores: { data, pagination }
servers.listAll()Iterador asíncrono sobre todos los servidores
servers.get(id, ['egg', 'subusers'])Detalles del servidor, con asignaciones y variables de arranque
servers.resources(id)Estado en vivo, CPU, memoria, disco, red, tiempo encendido
servers.power(id, signal) / start / stop / restart / killAcciones de encendido
servers.command(id, command)Ejecutar un comando de consola
servers.activity(id)Registro de actividad (paginado)
servers.rename(id, name, description?)Renombrar
servers.reinstall(id)Volver a ejecutar el script de instalación
servers.setDockerImage(id, image)Cambia la imagen de ejecución (p. ej. la versión de Java)
servers.websocket(id)Token de consola y URL del socket (la consola de abajo los usa)
servers.waitForState(id, 'running', { timeoutMs })Espera a que el servidor llegue a un estado, p. ej. después de start(). Devuelve sus recursos
await hw.servers.start(id);
await hw.servers.waitForState(id, 'running');               // default timeout 5 minutes
const line = await hw.waitForConsole(id, /There are \d+ of a max/, { send: 'list' });

Consola#

const c = await hw.console(id).connect();
c.on('output', (line) => console.log(line));
c.on('stats', (s) => console.log(s.state, s.memory_bytes));
c.requestLogs();          // replay recent output
c.sendCommand('list');
// later
c.close();

// Or just fetch the recent lines (ANSI colours stripped):
const lines = await hw.tail(id, { lines: 50 });

// Or wait for one line: send a command and get its reply.
const reply = await hw.waitForConsole(id, /There are \d+ of a max/, { send: 'list', timeoutMs: 20_000 });

waitForConsole(id, pattern, { send, includeRecent, timeoutMs }) devuelve la primera línea que coincide con un texto o una RegExp. includeRecent también busca en el registro reciente, y falla con el código Timeout tras timeoutMs (30 segundos por defecto).

La consola se conecta directamente al nodo donde se ejecuta tu servidor y renueva su token automáticamente.

Eventos:

  • ready y close
  • output
  • status
  • stats
  • install output
  • daemon message y daemon error
  • error

Archivos#

await hw.files.list(id, '/plugins');
const props = await hw.files.read(id, 'server.properties');
await hw.files.write(id, 'server.properties', props.replace('pvp=true', 'pvp=false'));
await hw.files.rename(id, '/', [{ from: 'world', to: 'world-old' }]);
await hw.files.delete(id, '/logs', ['old.log']);
await hw.files.createFolder(id, '/', 'maps');
await hw.files.compress(id, '/', ['world']);       // returns the archive
await hw.files.decompress(id, '/', 'world.tar.gz');
await hw.files.pull(id, 'https://example.com/map.zip', { directory: '/maps' });
await hw.files.exists(id, 'plugins/LuckPerms.jar'); // true or false

// Binary files: upload and download go straight to the server's node.
await hw.files.upload(id, '/plugins', jarBytes, 'MyPlugin.jar');   // Blob, ArrayBuffer, Uint8Array or string
const buf = await hw.files.download(id, 'world.tar.gz');           // ArrayBuffer, in memory

// Or work with the signed URLs yourself, e.g. to stream a big file:
const url = await hw.files.downloadUrl(id, 'world.tar.gz');
const uploadUrl = await hw.files.uploadUrl(id);    // POST multipart "files" to `${uploadUrl}&directory=/maps`

Leer un archivo que no existe lanza un HostwolfError con estado 404 y código NotFound. También tienes files.copy(id, path) y files.chmod(id, root, [{ file, mode }]).

Copias de seguridad#

const { data } = await hw.backups.list(id);
const b = await hw.backups.create(id, { name: 'nightly', is_locked: false });
await hw.backups.waitUntilDone(id, b.uuid);   // polls until finished; rejects with code BackupFailed if it failed
await hw.backups.get(id, b.uuid);
await hw.backups.restore(id, b.uuid, { truncate: false });
await hw.backups.toggleLock(id, b.uuid);
await hw.backups.downloadUrl(id, b.uuid);
await hw.backups.delete(id, b.uuid);
await hw.backups.smartState(id);
await hw.backups.setSmart(id, true);   // automatic backups before risky changes

Puedes crear 2 copias de seguridad cada 10 minutos. Por encima de eso, create() falla al momento con error.retryAfter definido.

Bases de datos#

Cada servidor tiene hasta 5 bases de datos compatibles con MySQL en su propia máquina. La primera también arranca el servicio de bases de datos, lo que tarda unos 40 segundos: la API responde 202 y create() devuelve un resultado pendiente, salvo que pases wait.

import { isDatabasePending } from '@hostwolf/sdk';

// Simplest: wait until the database exists (up to 2 minutes), then use it
const db = await hw.databases.create(id, 'luckperms', { wait: true });
console.log(db.host.address, db.name, db.username, db.relationships?.password?.password);

// Or handle the pending answer yourself
const r = await hw.databases.create(id, 'coreprotect');
if (isDatabasePending(r)) {
    const ready = await hw.databases.waitUntilReady(id, 'coreprotect', { timeoutMs: 120_000 });
}

await hw.databases.service(id);   // { status, local_address, public_address, other_servers }
await hw.databases.setRemoteAccess(id, db.id, { remoteIps: ['203.0.113.7'], anywhere: false });
await hw.databases.rotatePassword(id, db.id);
const dump = await hw.databases.export(id, db.id);   // .sql.gz bytes
await hw.databases.import(id, db.id, dump, { filename: 'luckperms.sql.gz' });
await hw.databases.delete(id, db.id);

Los servidores de juego se conectan a db.host (172.18.0.1:3306). db.name lleva un prefijo, por ejemplo s12_luckperms; waitUntilReady() recibe el nombre que pediste. Los demás métodos reciben db.id. waitUntilReady() falla con el código DatabaseSetupFailed si el servicio no pudo arrancar, o con Timeout. Puedes crear 2 bases de datos por minuto y exportar o importar 6 por minuto.

Juego, jugadores y ajustes#

await hw.game.versions(id);                          // current + available versions
await hw.game.setVersion(id, '1.21.4');              // switches and reinstalls; { resetWorld, force } for downgrades
await hw.game.settings(id);                          // friendly settings, grouped by file ({ supported: false } on Java Minecraft)
await hw.game.updateSettings(id, { difficulty: 'hard' }, true);
await hw.game.diagnostics(id);                       // "Fix my server" checks

await hw.players.info(id);                           // online count (+ names on Minecraft)
await hw.players.list(id);                           // live list via RCON/query
await hw.players.kick(id, 'Steve');
await hw.players.ban(id, { steamid: '7656...' });
await hw.players.unban(id, 'Steve');
await hw.players.broadcast(id, 'Restarting in 5 minutes');

Mods, plugins y modpacks#

const profile = await hw.addons.profile(id);         // sources, install dir, frameworks
const { items } = await hw.addons.search(id, 'modrinth', 'worldedit');
await hw.addons.install(id, 'modrinth', items[0].id); // newest compatible version
const installed = await hw.addons.installed(id, { checkUpdates: true });
await hw.addons.uninstall(id, installed.addons[0].id);

const packs = await hw.modpacks.search(id, 'cobblemon');
await hw.modpacks.install(id, packs.items[0].id, { freshWorld: false });

Otras llamadas de addons:

  • addons.versions(id, source, project): versiones, marcadas como compatibles o no.
  • addons.installFeatured(id, project): addons destacados con un clic, desde profile.featured.
  • addons.upload(id, blob, name): cuando profile.upload está definido. Si no, pon los archivos en profile.directory con files.upload().
  • addons.removeManual(id, file): quita un archivo que añadiste tú.
  • addons.setFramework(id, framework): p. ej. Oxide o Carbon en Rust.
  • addons.workshop / setWorkshop / workshopPreview (juegos con Steam Workshop).
  • addons.cs2Framework(id, { repair }): Metamod + CounterStrikeSharp en CS2.
  • modpacks.current / versions / remove.

Más#

Espacio de nombresMétodos
startupget(id), setVariable(id, key, value)
scheduleslist, get, create, update, execute, delete, createTask, updateTask, deleteTask
databaseslist(id, { includePassword }), create, waitUntilReady, service, setRemoteAccess, rotatePassword, export, import, delete. Ver Bases de datos
networkallocations, createAllocation, setNotes, setPrimary, deleteAllocation
userslist, get, create(id, publicId, permissions), update, delete, permissions(). Las cuentas se añaden por su ID pública (HW-XXXXXX), no por correo
notificationsAvisos de Discord por servidor: get, update, test
domainDominio propio: get, check, set, remove
mcNetworkRedes Velocity/BungeeCord: get, link, unlink, installFabricProxy
accountget, updateEmail, updatePassword, updateProfile, updateLanguage(locale, currency?), uploadAvatar, deleteAvatar, activity, getNotifications, updateNotifications, testNotification, getCurseforgeKey, setCurseforgeKey, getFactorioCredentials, setFactorioCredentials
account.apiKeyslist, create(description, allowedIps?) (devuelve la clave completa en token, una sola vez), delete(identifier)
account.sshKeyslist, create(name, publicKey), delete(fingerprint)
storeSolo lectura: catalog(), orders(), billing()

Contratar, cancelar y pagar servidores solo se puede hacer en el panel, así que una clave API nunca puede gastar dinero.

Paginación#

servers.list, backups.list y los métodos activity devuelven { data, pagination }. Para recorrer todos los servidores:

import { collect } from '@hostwolf/sdk';
const all = await collect(hw.servers.listAll());

Errores#

Las llamadas que fallan lanzan un HostwolfError.

import { HostwolfError } from '@hostwolf/sdk';
try {
    await hw.game.setVersion(id, '1.16.5');
} catch (e) {
    if (e instanceof HostwolfError && e.code === 'downgrade_requires_reset') {
        // older versions can't load a newer world: retry with { resetWorld: true } or { force: true }
    }
}

Tiene estas propiedades:

  • status: el estado HTTP, o 0 en errores de red y tiempos agotados.
  • code: el código de error. Además de los códigos de la API, el SDK usa NetworkError (sin respuesta o tiempo agotado), Timeout (un helper waitFor… o waitUntil… se rindió), BackupFailed, DatabaseSetupFailed, DatabaseNotCreated y NotFound (un archivo que no existe).
  • retryAfter: en un 429, los segundos que faltan para que la acción vuelva a estar permitida. Las esperas cortas se reintentan por ti; las largas (como el límite de 2 copias por cada 10 minutos) fallan al momento con este valor.
  • message: una descripción legible.
  • errors: la lista de errores sin procesar.
  • body: el cuerpo de la respuesta.
  • Atajos: isNotFound, isRateLimited e isValidation.

Los más comunes:

  • 401: la clave es incorrecta, está incompleta o revocada.
  • 403 con This IP address (…) does not have permission: la clave tiene una lista de IP permitidas sin tu dirección.
  • 409 downgrade_requires_reset y 422 unknown_version: de game.setVersion().
  • 429: un límite de peticiones; mira retryAfter.
  • Una escritura que agotó el tiempo (estado 0) puede terminar igualmente en el panel, así que compruébalo antes de reintentarla.

Peticiones directas#

Si un endpoint aún no tiene método, llámalo directamente:

await hw.http.get(`/servers/${id}/players`);
await hw.http.post(`/servers/${id}/command`, { command: 'list' });

Las rutas son relativas a /api/client, y las respuestas se desempaquetan igual que con los métodos incluidos. Consulta todos los endpoints en la referencia de la API.

Versiones#

ArchivoUso
https://hostwolf.net/dl/hostwolf-sdk-.tgzUna versión fija. Úsala en package.json.
https://hostwolf.net/dl/hostwolf-sdk.tgzSiempre la versión más reciente. Los gestores de paquetes guardan en caché por URL, así que es mejor usar URLs con versión.
https://hostwolf.net/dl/index.jsonVersiones actuales, con hashes SHA-256 y de integridad de npm.

Para actualizar, instala la URL con la versión más nueva.

Licencia#

MIT

¿Atascado, o te falta un endpoint? Escríbenos en game.hostwolf.net/contact o a support@hostwolf.net.