CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-portfinder

A simple tool to find an open port on the current machine

82

1.64x
Overview
Eval results
Files

socket-finding.mddocs/

Socket Finding

Unix socket path discovery for finding available socket file paths with configurable directory and permission settings.

Capabilities

Get Socket Function

Finds and returns an unbound Unix socket path. Creates necessary directories if they don't exist.

/**
 * Responds with a unbound socket using the specified directory and base name on the current machine
 * @param {SocketFinderOptions} [options] - Socket search options
 * @param {function} [callback] - Callback function (err, socketPath) => void
 * @returns {Promise<string>} Promise resolving to available socket path (when no callback provided)
 */
function getSocket(options?: SocketFinderOptions): Promise<string>;
function getSocket(callback: (err: Error, socket: string) => void): void;
function getSocket(options: SocketFinderOptions, callback: (err: Error, socket: string) => void): void;

Usage Examples:

const portfinder = require('portfinder');

// Basic usage with callback - uses default path
portfinder.getSocket(function (err, socketPath) {
  if (err) throw err;
  console.log('Available socket:', socketPath); // e.g., '/tmp/portfinder.sock'
});

// Promise-based usage
portfinder.getSocketPromise()
  .then((socketPath) => {
    console.log('Available socket:', socketPath);
  })
  .catch((err) => {
    console.error('Error finding socket:', err);
  });

// With custom path - callback style
portfinder.getSocket({
  path: '/var/run/myapp.sock'
}, function(err, socketPath) {
  if (err) throw err;
  console.log('Socket path:', socketPath); // '/var/run/myapp.sock' or '/var/run/myapp1.sock'
});

// With custom directory permissions - Promise style
portfinder.getSocket({
  path: '/tmp/sockets/app.sock',
  mod: parseInt('755', 8)  // Directory permissions in octal
})
.then(socketPath => {
  console.log('Socket with custom permissions:', socketPath);
});

Get Socket Promise Function

Promise-based version of getSocket (alias for getSocket without callback).

/**
 * Responds a promise that resolves to an unbound socket path
 * @param {SocketFinderOptions} [options] - Socket search options
 * @returns {Promise<string>} Promise resolving to available socket path
 */
function getSocketPromise(options?: SocketFinderOptions): Promise<string>;

Usage Example:

const portfinder = require('portfinder');

// Equivalent to getSocket without callback
const socketPath = await portfinder.getSocketPromise({
  path: '/run/user/1000/app.sock'
});
console.log('Found socket path:', socketPath);

Next Socket Function

Gets the next socket path in sequence by incrementing numeric suffix.

/**
 * Gets the next socket path in sequence from the specified socketPath
 * @param {string} socketPath - Path to increment from
 * @returns {string} Next socket path with incremented numeric suffix
 */
function nextSocket(socketPath: string): string;

Usage Examples:

const portfinder = require('portfinder');

// Basic increment
const currentSocket = '/tmp/app.sock';
const nextSocket = portfinder.nextSocket(currentSocket);
console.log('Next socket:', nextSocket); // '/tmp/app1.sock'

// With existing number
const numbered = '/tmp/server5.sock';
const incremented = portfinder.nextSocket(numbered);
console.log('Incremented socket:', incremented); // '/tmp/server6.sock'

// Manual socket iteration
let testSocket = '/var/run/worker.sock';
for (let i = 0; i < 5; i++) {
  console.log('Testing socket:', testSocket);
  testSocket = portfinder.nextSocket(testSocket);
}
// Outputs: worker.sock, worker1.sock, worker2.sock, worker3.sock, worker4.sock

Options Interface

/**
 * Options for socket finding operations
 */
interface SocketFinderOptions {
  /**
   * Path to the socket file to create
   * Defaults to ${basePath}.sock (e.g., '/tmp/portfinder.sock')
   */
  path?: string;
  
  /**
   * Mode to use when creating folder for socket if it doesn't exist
   * Should be specified in octal format (e.g., parseInt('755', 8))
   * Defaults to 755 (rwxr-xr-x)
   */
  mod?: number;
}

Socket Path Resolution

Default Behavior

const portfinder = require('portfinder');

// Uses global basePath setting
console.log('Base path:', portfinder.basePath); // '/tmp/portfinder'

portfinder.getSocketPromise().then(socketPath => {
  console.log('Default socket:', socketPath); // '/tmp/portfinder.sock'
});

Path Conflicts and Resolution

const portfinder = require('portfinder');
const fs = require('fs');

// Create a conflicting socket file
fs.writeFileSync('/tmp/test.sock', '');

portfinder.getSocket({ path: '/tmp/test.sock' }, (err, socketPath) => {
  console.log('Resolved path:', socketPath); // '/tmp/test1.sock'
});

Advanced Usage Patterns

Application-Specific Sockets

const portfinder = require('portfinder');
const path = require('path');
const os = require('os');

async function createAppSocket(appName, userId) {
  const userRunDir = `/run/user/${userId}`;
  const socketPath = path.join(userRunDir, `${appName}.sock`);
  
  try {
    const finalPath = await portfinder.getSocketPromise({
      path: socketPath,
      mod: parseInt('700', 8) // Only user can access
    });
    
    return finalPath;
  } catch (err) {
    // Fallback to temp directory
    const tempPath = path.join(os.tmpdir(), `${appName}-${userId}.sock`);
    return await portfinder.getSocketPromise({ path: tempPath });
  }
}

createAppSocket('myapp', 1000).then(socketPath => {
  console.log('App socket path:', socketPath);
});

Multiple Service Sockets

const portfinder = require('portfinder');

async function allocateServiceSockets() {
  const services = ['api', 'worker', 'scheduler', 'monitor'];
  const baseDir = '/var/run/myapp';
  
  const allocations = await Promise.all(
    services.map(async (service) => {
      const socketPath = await portfinder.getSocketPromise({
        path: `${baseDir}/${service}.sock`,
        mod: parseInt('755', 8)
      });
      return { service, socketPath };
    })
  );
  
  return allocations;
}

allocateServiceSockets().then(allocations => {
  console.log('Service socket allocations:', allocations);
  // e.g., [
  //   { service: 'api', socketPath: '/var/run/myapp/api.sock' },
  //   { service: 'worker', socketPath: '/var/run/myapp/worker.sock' },
  //   { service: 'scheduler', socketPath: '/var/run/myapp/scheduler1.sock' }, // if scheduler.sock exists
  //   { service: 'monitor', socketPath: '/var/run/myapp/monitor.sock' }
  // ]
});

Socket Cleanup and Management

const portfinder = require('portfinder');
const fs = require('fs').promises;

class SocketManager {
  constructor() {
    this.activeSockets = new Set();
  }
  
  async createSocket(options) {
    const socketPath = await portfinder.getSocketPromise(options);
    this.activeSockets.add(socketPath);
    return socketPath;
  }
  
  async cleanup() {
    const cleanupPromises = Array.from(this.activeSockets).map(async (socketPath) => {
      try {
        await fs.unlink(socketPath);
        console.log('Cleaned up socket:', socketPath);
      } catch (err) {
        if (err.code !== 'ENOENT') {
          console.error('Failed to cleanup socket:', socketPath, err);
        }
      }
    });
    
    await Promise.all(cleanupPromises);
    this.activeSockets.clear();
  }
}

// Usage
const manager = new SocketManager();

async function example() {
  const socket1 = await manager.createSocket({ path: '/tmp/service1.sock' });
  const socket2 = await manager.createSocket({ path: '/tmp/service2.sock' });
  
  console.log('Created sockets:', socket1, socket2);
  
  // Cleanup on process exit
  process.on('exit', () => manager.cleanup());
}

Directory Creation Behavior

When a socket path is requested, portfinder automatically handles directory creation:

  1. Existing Directory: If the parent directory exists, uses it directly
  2. Missing Directory: Creates the directory with specified permissions (mod option)
  3. Recursive Creation: Creates parent directories as needed using fs.mkdir with recursive: true
  4. Permission Inheritance: Uses the mod option for all created directories in the path
const portfinder = require('portfinder');

// This will create /deep/nested/path/ if it doesn't exist
portfinder.getSocket({
  path: '/deep/nested/path/app.sock',
  mod: parseInt('755', 8)
}).then(socketPath => {
  console.log('Socket created with full path:', socketPath);
});

Install with Tessl CLI

npx tessl i tessl/npm-portfinder

docs

index.md

port-configuration.md

port-finding.md

socket-finding.md

tile.json