diff --git a/ghost/admin/package.json b/ghost/admin/package.json index e844220b902..50148adcb4f 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -1,6 +1,6 @@ { "name": "ghost-admin", - "version": "6.44.1-rc.0", + "version": "6.44.2-rc.0", "description": "Ember.js admin client for Ghost", "author": "Ghost Foundation", "homepage": "http://ghost.org", diff --git a/ghost/core/core/server/lib/bootstrap-socket.js b/ghost/core/core/server/lib/bootstrap-socket.ts similarity index 59% rename from ghost/core/core/server/lib/bootstrap-socket.js rename to ghost/core/core/server/lib/bootstrap-socket.ts index 7b00df3d9c6..2c0681f63e6 100644 --- a/ghost/core/core/server/lib/bootstrap-socket.js +++ b/ghost/core/core/server/lib/bootstrap-socket.ts @@ -1,16 +1,44 @@ -const logging = require('@tryghost/logging'); - -module.exports.connectAndSend = (socketAddress, message) => { +import net from 'node:net'; +import logging from '@tryghost/logging'; +import type {JsonValue} from 'type-fest'; + +type SocketAddress = Readonly<{ + host: string; + port: number; +}>; + +type ConnectOptions = Readonly<{ + tries: number; +}>; + +const getErrorCode = (err: unknown): undefined | string => ( + err && typeof err === 'object' && 'code' in err && typeof err.code === 'string' + ? err.code + : undefined +); + +const isValidPort = (port: unknown): port is number => ( + typeof port === 'number' && + Number.isInteger(port) && + port >= 1 && + port <= 65535 +); + +const hasSocketAddress = (socketAddress?: Partial): socketAddress is SocketAddress => ( + typeof socketAddress?.host === 'string' && Boolean(socketAddress.host) && + isValidPort(socketAddress.port) +); + +export const connectAndSend = (socketAddress?: Partial, message?: JsonValue): Promise => { // Very basic guard against bad calls - if (!socketAddress || !socketAddress.host || !socketAddress.port || !logging || !logging.info || !logging.warn || !message) { + if (!hasSocketAddress(socketAddress) || !logging || !logging.info || !logging.warn || message === undefined) { return Promise.resolve(); } - const net = require('net'); const client = new net.Socket(); - return new Promise((resolve) => { - const connect = (options = {}) => { + return new Promise((resolve: () => void) => { + const connect = (options: ConnectOptions) => { let wasResolved = false; const waitTimeout = setTimeout(() => { @@ -51,8 +79,8 @@ module.exports.connectAndSend = (socketAddress, message) => { } }); - client.on('error', (err) => { - logging.warn(`Can't connect to the bootstrap socket (${socketAddress.host} ${socketAddress.port}) ${err.code}.`); + client.on('error', (err: Error) => { + logging.warn(`Can't connect to the bootstrap socket (${socketAddress.host} ${socketAddress.port}) ${getErrorCode(err)}.`); client.removeAllListeners(); @@ -66,10 +94,9 @@ module.exports.connectAndSend = (socketAddress, message) => { // retry logging.warn('Retrying...'); - options.tries = options.tries + 1; const retryTimeout = setTimeout(() => { clearTimeout(retryTimeout); - connect(options); + connect({tries: options.tries + 1}); }, 150); } else { if (wasResolved) { diff --git a/ghost/core/package.json b/ghost/core/package.json index 13b53c73aad..f73280e9f9c 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -1,6 +1,6 @@ { "name": "ghost", - "version": "6.44.1-rc.0", + "version": "6.44.2-rc.0", "description": "The professional publishing platform", "author": "Ghost Foundation", "homepage": "https://ghost.org", diff --git a/ghost/core/test/unit/server/lib/bootstrap-socket.test.js b/ghost/core/test/unit/server/lib/bootstrap-socket.test.js deleted file mode 100644 index 4831798212d..00000000000 --- a/ghost/core/test/unit/server/lib/bootstrap-socket.test.js +++ /dev/null @@ -1,7 +0,0 @@ -const bootstrapSocket = require('../../../../core/server/lib/bootstrap-socket'); - -describe('Connect and send', function () { - it('Resolves a promise for a bad call', async function () { - await bootstrapSocket.connectAndSend(); - }); -}); diff --git a/ghost/core/test/unit/server/lib/bootstrap-socket.test.ts b/ghost/core/test/unit/server/lib/bootstrap-socket.test.ts new file mode 100644 index 00000000000..0d99dfd194a --- /dev/null +++ b/ghost/core/test/unit/server/lib/bootstrap-socket.test.ts @@ -0,0 +1,7 @@ +import {connectAndSend} from '../../../../core/server/lib/bootstrap-socket'; + +describe('Connect and send', function () { + it('Resolves a promise for a bad call', async function () { + await connectAndSend(); + }); +}); diff --git a/ghost/core/test/unit/server/notify.test.js b/ghost/core/test/unit/server/notify.test.js index 9eeaae13dfe..90362028b0f 100644 --- a/ghost/core/test/unit/server/notify.test.js +++ b/ghost/core/test/unit/server/notify.test.js @@ -1,9 +1,9 @@ const assert = require('node:assert/strict'); +const rewire = require('rewire'); const sinon = require('sinon'); const configUtils = require('../../utils/config-utils'); const events = require('../../../core/server/lib/common/events'); -const bootstrapSocket = require('../../../core/server/lib/bootstrap-socket'); describe('Notify', function () { describe('notifyServerStarted', function () { @@ -14,14 +14,22 @@ describe('Notify', function () { beforeEach(function () { // Have to re-require each time to clear the internal flag delete require.cache[require.resolve('../../../core/server/notify')]; - notify = require('../../../core/server/notify'); + + socketStub = sinon.stub(); + notify = rewire('../../../core/server/notify'); + notify.__set__('require', (path) => { + if (path === './lib/bootstrap-socket') { + return { + connectAndSend: socketStub + }; + } + + return require(path); + }); // process.send isn't set for tests, we can safely override; process.send = sinon.stub(); - // stub socket connectAndSend method - socketStub = sinon.stub(bootstrapSocket, 'connectAndSend'); - // Spy for the events that get called eventSpy = sinon.spy(events, 'emit'); }); @@ -29,7 +37,6 @@ describe('Notify', function () { afterEach(async function () { process.send = undefined; await configUtils.restore(); - socketStub.restore(); eventSpy.restore(); });