Replace remaining usage of phin (#862)

Replace the remaining places in the codebase using phin with fetch.
With the exception of HttpCloser which uses the http/https request
method instead.
This commit is contained in:
Hornwitser authored and GitHub committed 2026-03-06 15:27:12 +01:00
1 parent ef0baaa30f
commit 05b6bf8c04
13 files changed
+91 -102

No files matched your search

-1
View File
@@ -56,7 +56,6 @@
"jszip": "^3.10.1",
"mocha": "^11.7.1",
"nyc": "^17.1.0",
"phin": "^3.7.1",
"typedoc": "^0.28.9",
"typescript": "^5.9.2",
"yargs": "^18.0.0"
-1
View File
@@ -24,7 +24,6 @@
"set-blocking": "catalog:",
"fs-extra": "catalog:",
"winston": "catalog:",
"phin": "catalog:",
"as-table": "^1.0.55",
"yargs": "^17.7.2"
},
+2 -6
View File
@@ -1,7 +1,6 @@
import fs from "fs-extra";
import asTableModule from "as-table";
import events from "events";
import phin from "phin";
import os from "os";
import child_process from "child_process";
@@ -166,11 +165,8 @@ controllerPluginCommands.add(new lib.Command({
handler: async function(args: object, control: Control) {
let url = new URL(control.config.get("control.controller_url")!);
url.pathname += "api/plugins";
let response = await phin<[]>({
url,
parse: "json",
});
print(asTable(response.body));
let response = await fetch(url);
print(asTable(await response.json() as []));
},
}));
controllerPluginCommands.add(new lib.Command({
+18 -18
View File
@@ -2,7 +2,6 @@ import fs from "fs-extra";
import asTableModule from "as-table";
import events from "events";
import path from "path";
import phin from "phin";
import os from "os";
import child_process from "child_process";
import stream from "stream";
@@ -330,8 +329,6 @@ instanceSaveCommands.add(new lib.Command({
if (!filename.endsWith(".zip")) {
throw new lib.CommandError("Save name must end with .zip");
}
// phin doesn't support streaming requests :(
let content = await fs.readFile(args.filepath);
let instanceId = await lib.resolveInstance(control, args.instance);
let url = new URL(control.config.get("control.controller_url")!);
@@ -339,29 +336,32 @@ instanceSaveCommands.add(new lib.Command({
url.searchParams.append("instance_id", String(instanceId));
url.searchParams.append("filename", filename);
let result = await phin<{ errors?: string[], request_errors?: string[], saves?: string[] }>({
url, method: "POST",
const response = await fetch(url, {
method: "POST",
body: await fs.createReadStream(args.filepath),
duplex: "half",
headers: {
"X-Access-Token": control.config.get("control.controller_token"),
"X-Access-Token": control.config.get("control.controller_token")!,
"Content-Type": "application/zip",
},
data: content,
parse: "json",
});
for (let error of result.body.errors || []) {
type ResponseType = { errors?: string[], request_errors?: string[], saves?: string[] };
const result = await response.json() as ResponseType;
for (let error of result.errors || []) {
logger.error(error);
}
for (let requestError of result.body.request_errors || []) {
for (let requestError of result.request_errors || []) {
logger.error(requestError);
}
if (result.body.saves && result.body.saves.length) {
logger.info(`Successfully uploaded as ${result.body.saves[0]}`);
if (result.saves && result.saves.length) {
logger.info(`Successfully uploaded as ${result.saves[0]}`);
}
if ((result.body.errors || []).length || (result.body.request_errors || []).length) {
if ((result.errors || []).length || (result.request_errors || []).length) {
throw new lib.CommandError("Uploading save failed");
}
},
@@ -417,10 +417,10 @@ instanceSaveCommands.add(new lib.Command({
let url = new URL(control.config.get("control.controller_url")!);
url.pathname += `api/stream/${streamId}`;
let response = await phin({
url, method: "GET",
stream: true,
});
let response = await fetch(url);
if (!response.body) {
throw new lib.CommandError("Got empty response from server.");
}
let writeStream;
let tempFilename = args.save.replace(/(\.zip)?$/, ".tmp.zip");
@@ -437,7 +437,7 @@ instanceSaveCommands.add(new lib.Command({
}
}
}
response.pipe(writeStream);
stream.Readable.fromWeb(response.body).pipe(writeStream);
await finished(writeStream);
let filename = await lib.findUnusedName(".", args.save, ".zip");
+18 -20
View File
@@ -2,7 +2,6 @@ import fs from "fs-extra";
import asTableModule from "as-table";
import events from "events";
import path from "path";
import phin from "phin";
import stream from "stream";
import util from "util";
@@ -145,39 +144,38 @@ modCommands.add(new lib.Command({
if (!filename.endsWith(".zip")) {
throw new lib.CommandError("Mod filename must end with .zip");
}
// phin doesn't support streaming requests :(
let content = await fs.readFile(args.file);
let url = new URL(control.config.get("control.controller_url")!);
url.pathname += "api/upload-mod";
url.searchParams.append("filename", filename);
let result = await phin<
{ errors?: [], request_errors?: [], mods: Parameters<typeof lib.ModInfo.fromJSON>[0][]}
>({
url, method: "POST",
let response = await fetch(url, {
method: "POST",
body: fs.createReadStream(args.file),
duplex: "half",
headers: {
"X-Access-Token": control.config.get("control.controller_token"),
"X-Access-Token": control.config.get("control.controller_token")!,
"Content-Type": "application/zip",
},
data: content,
parse: "json",
});
for (let error of result.body.errors || []) {
type ResponseType = { errors?: [], request_errors?: [], mods: Parameters<typeof lib.ModInfo.fromJSON>[0][]};
const result = await response.json() as ResponseType;
for (let error of result.errors || []) {
logger.error(error);
}
for (let requestError of result.body.request_errors || []) {
for (let requestError of result.request_errors || []) {
logger.error(requestError);
}
if (result.body.mods && result.body.mods.length) {
const mod = lib.ModInfo.fromJSON(result.body.mods[0]);
if (result.mods && result.mods.length) {
const mod = lib.ModInfo.fromJSON(result.mods[0]);
logger.info(`Successfully uploaded ${mod.filename}`);
}
if ((result.body.errors || []).length || (result.body.request_errors || []).length) {
if ((result.errors || []).length || (result.request_errors || []).length) {
throw new lib.CommandError("Uploading mod failed");
}
},
@@ -196,10 +194,10 @@ modCommands.add(new lib.Command({
let url = new URL(control.config.get("control.controller_url")!);
url.pathname += `api/stream/${streamId}`;
let response = await phin({
url, method: "GET",
stream: true,
});
let response = await fetch(url);
if (!response.body) {
throw new lib.CommandError("Got empty response from server");
}
let writeStream;
let filename = `${args.name}_${args.modVersion}.zip`;
@@ -217,7 +215,7 @@ modCommands.add(new lib.Command({
}
}
}
response.pipe(writeStream);
stream.Readable.fromWeb(response.body).pipe(writeStream);
await finished(writeStream);
await fs.rename(tempFilename, filename);
-1
View File
@@ -26,7 +26,6 @@
"set-blocking": "catalog:",
"fs-extra": "catalog:",
"jszip": "catalog:",
"phin": "catalog:",
"winston": "catalog:",
"winston-daily-rotate-file": "catalog:",
"jimp": "^0.22.8",
+18 -20
View File
@@ -3,7 +3,6 @@ import path from "path";
import events from "events";
import pidusage from "pidusage";
import setBlocking from "set-blocking";
import phin from "phin";
import stream from "stream";
import util from "util";
@@ -585,17 +584,17 @@ export default class Host extends lib.Link {
let url = new URL(this.config.get("host.controller_url"));
url.pathname += `api/stream/${streamId}`;
let response = await phin({
url, method: "GET",
stream: true,
});
let response = await fetch(url);
if (!response.body) {
throw new Error(`Empty body downloading stream ${streamId} from controller`);
}
const file = lib.ModInfo.filename(mod.name, mod.version);
const filePath = path.join(this.modStore.modsDirectory, file);
const tempFilePath = filePath.replace(/(\.zip)?$/, ".tmp.zip");
const writeStream = fs.createWriteStream(tempFilePath, { flags: "w" });
await events.once(writeStream, "open");
response.pipe(writeStream);
stream.Readable.fromWeb(response.body).pipe(writeStream);
await finished(writeStream);
await fs.rename(tempFilePath, filePath);
@@ -1018,14 +1017,11 @@ export default class Host extends lib.Link {
let url = new URL(this.config.get("host.controller_url"));
url.pathname += `api/stream/${streamId}`;
let response = await phin({
url, method: "GET",
stream: true,
});
let response = await fetch(url);
if (response.statusCode !== 200) {
let content = await lib.readStream(response);
throw new lib.RequestError(`Stream returned ${response.statusCode}: ${content.toString()}`);
if (response.status !== 200 || !response.body) {
let body = Buffer.from(await response.arrayBuffer());
throw new lib.RequestError(`Stream returned ${response.status}: ${body.toString()}`);
}
let savesDir = path.join(instance.path, "saves");
@@ -1044,7 +1040,7 @@ export default class Host extends lib.Link {
}
}
}
response.pipe(writeStream);
stream.Readable.fromWeb(response.body).pipe(writeStream);
await finished(writeStream);
name = await lib.findUnusedName(savesDir, name, ".zip");
@@ -1059,10 +1055,11 @@ export default class Host extends lib.Link {
checkRequestSaveName(name);
let instance = this.getRequestInstance(instanceId);
let content: Buffer;
let content = fs.createReadStream(path.join(instance.path, "saves", name));
try {
// phin doesn't support streaming requests :(
content = await fs.readFile(path.join(instance.path, "saves", name));
// TODO: Due to a bug in fs-extra the ready event is not emitted here. Replace
// "open" with "ready" when fs-extra is replaced with node's fs module.
await events.once(content, "open");
} catch (err: any) {
if (err.code === "ENOENT") {
throw new lib.RequestError(`${name} does not exist`);
@@ -1072,9 +1069,10 @@ export default class Host extends lib.Link {
let url = new URL(this.config.get("host.controller_url"));
url.pathname += `api/stream/${streamId}`;
phin({
url, method: "PUT",
data: content,
fetch(url, {
method: "PUT",
body: content,
duplex: "half",
}).catch(err => {
logger.error(`Error pushing save to controller:\n${err.stack}`, this.instanceLogMeta(instanceId));
});
+7 -6
View File
@@ -1,7 +1,6 @@
import fs from "fs-extra";
import path from "path";
import pidusage from "pidusage";
import phin from "phin";
import util from "util";
import type { Static } from "@sinclair/typebox";
import { exec } from "child_process";
@@ -1292,16 +1291,18 @@ end`.replace(/\r?\n/g, " ");
let url = new URL(this._host.config.get("host.controller_url"));
url.pathname += "api/upload-export";
url.searchParams.set("mod_pack_id", String(this.activeModPack.id));
let response = await phin({
url, method: "PUT",
data: content,
let response = await fetch(url, {
method: "PUT",
body: content,
duplex: "half",
headers: {
"Content-Type": "application/zip",
"x-access-token": this._host.config.get("host.controller_token"),
},
});
if (response.statusCode !== 200) {
throw Error(`Upload failed: ${response.statusCode} ${response.statusMessage}: ${response.body}`);
if (response.status !== 200) {
const body = Buffer.from(await response.arrayBuffer());
throw Error(`Upload failed: ${response.status} ${response.statusText}: ${body.toString()}`);
}
} finally {
+1 -1
View File
@@ -9,7 +9,7 @@ describe("subspace_storage endpoints", function() {
describe("GET /api/inventory", function() {
it("should return the controllers current inventory", async function() {
let res = await get("/api/inventory");
let inventory = res.body;
let inventory = await res.json();
assert.equal(typeof inventory, "object", "Inventory should be an object");
return; // XXX Disabled until a suitable test interface is added.
-1
View File
@@ -33,7 +33,6 @@ catalog:
"set-blocking": ^2.0.0
"jszip": ^3.10.1
"chalk": ^4.1.2
"phin": ^3.7.0
"ws": ^8.18.1
catalogMode: strict
+18 -14
View File
@@ -4,8 +4,7 @@ const fs = require("fs-extra");
const http = require("http");
const https = require("https");
const util = require("util");
const phin = require("phin");
const events = require("node:events");
const HttpCloser = require("@clusterio/controller/dist/node/src/HttpCloser").default;
@@ -38,8 +37,16 @@ function serverSuite(proto) {
}
async function get(url, options = {}) {
let core = { ...clientOptions, ...(options.core || {}) };
return await phin({ ...options, url, core });
const request = api.request(url, { ...clientOptions, ...options });
request.end();
const [response] = await events.once(request, "response");
response.body = await new Promise((resolve, reject) => {
const chunks = [];
response.on("data", chunk => { chunks.push(chunk); });
response.on("error", err => { reject(err); });
response.on("end", () => { resolve(Buffer.concat(chunks)); });
});
return response;
}
before(async function() {
@@ -101,7 +108,7 @@ function serverSuite(proto) {
});
let agent = new api.Agent({ keepAlive: true });
let request = catched(get(addr, { core: { agent }}));
let request = catched(get(addr, { agent }));
await waitForStage1;
stage2();
await closer.close();
@@ -121,7 +128,7 @@ function serverSuite(proto) {
});
let agent = new api.Agent({ keepAlive: true });
let request = catched(get(addr, { core: { agent }}));
let request = catched(get(addr, { agent }));
await waitForStage1;
await closer.close();
@@ -145,7 +152,7 @@ function serverSuite(proto) {
let close = catched(closer.close());
let agent = new api.Agent({ keepAlive: true });
await assert.rejects(get(addr, { core: { agent }}), { code: "ECONNREFUSED" });
await assert.rejects(get(addr, { agent }), { code: "ECONNREFUSED" });
let response = await request;
assert.equal(response.statusCode, 200, "non-200 response status code");
@@ -165,7 +172,7 @@ function serverSuite(proto) {
});
let agent = new api.Agent({ keepAlive: true });
let request = catched(get(addr, { core: { agent }}));
let request = catched(get(addr, { agent }));
await waitForStage1;
stage2();
await closer.close();
@@ -186,8 +193,8 @@ function serverSuite(proto) {
});
let agent = new api.Agent({ keepAlive: true, maxSockets: 1 });
let request = catched(get(addr, { core: { agent }}));
let droppedRequest = catched(get(addr, { core: { agent }}));
let request = catched(get(addr, { agent }));
let droppedRequest = catched(get(addr, { agent }));
await waitForStage1;
stage2();
await closer.close();
@@ -212,10 +219,7 @@ function serverSuite(proto) {
await closer.close(tick*2);
stage2();
// Due to a bug in phin [https://github.com/ethanent/phin/issues/59]
// the request Promise never settles.
// TODO: uncomment when bug is fixed in phin.
// await assert.rejects(request);
await assert.rejects(request);
});
}
+3 -5
View File
@@ -4,7 +4,6 @@ const fs = require("fs-extra");
const jwt = require("jsonwebtoken");
const path = require("path");
const events = require("events");
const phin = require("phin");
const lib = require("@clusterio/lib");
const libBuildMod = require("@clusterio/lib/build_mod");
@@ -79,16 +78,15 @@ async function runWithAltHost(callback) {
}
async function uploadSave(instanceId, name, content) {
return await phin({
url: `https://localhost:4443/api/upload-save?instance_id=${instanceId}&filename=${name}`,
const uploadUrl = `https://localhost:4443/api/upload-save?instance_id=${instanceId}&filename=${name}`;
return await fetch(uploadUrl, {
method: "POST",
core: { rejectUnauthorized: false },
headers: {
"X-Access-Token": controlToken,
"Content-Type": "application/zip",
"Content-Length": String(content.length),
},
data: content,
body: content,
});
}
+6 -8
View File
@@ -4,7 +4,6 @@ const path = require("path");
const fs = require("fs-extra");
const child_process = require("child_process");
const jwt = require("jsonwebtoken");
const phin = require("phin");
const util = require("util");
const events = require("events");
@@ -148,13 +147,12 @@ function hasFactorio() {
}
async function get(urlPath) {
let res = await phin({
method: "GET",
url: `https://localhost:4443${urlPath}`,
core: { rejectUnauthorized: false },
});
if (res.statusCode !== 200) {
throw new Error(`Got response code ${res.statusCode}, content: ${res.body}`);
const url = new URL("https://localhost:4443");
url.pathname = urlPath;
let res = await fetch(url);
if (res.status !== 200) {
const body = Buffer.from(await res.arrayBuffer());
throw new Error(`Got response code ${res.status}, content: ${body.toString()}`);
}
return res;
}