CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-phoenix-websocket

A custom implementation of the Channels API for communicating with an Elixir/Phoenix backend via WebSockets.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

Phoenix WebSocket

Phoenix WebSocket is a TypeScript WebSocket client library for communicating with Elixir/Phoenix Channels. It provides an async/await-based API for establishing WebSocket connections, subscribing to channels/topics, sending messages, and handling server responses with automatic reconnection and comprehensive error handling.

Package Information

  • Package Name: phoenix-websocket
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install phoenix-websocket

Core Imports

import {
  PhoenixWebsocket,
  WebsocketStatuses,
  PhoenixWebsocketLogLevels,
  TopicStatuses,
  TopicMessageHandler,
  PhoenixError,
  PhoenixReply
} from "phoenix-websocket";

For CommonJS:

const {
  PhoenixWebsocket,
  WebsocketStatuses,
  PhoenixWebsocketLogLevels,
  TopicStatuses,
  TopicMessageHandler,
  PhoenixError,
  PhoenixReply
} = require("phoenix-websocket");

Basic Usage

import { PhoenixWebsocket } from "phoenix-websocket";

// Create connection instance
const socket = new PhoenixWebsocket("wss://example.io/channel-endpoint", {
  token: "auth-token"
});

// Set up callbacks
socket.onConnectedCallback = () => {
  console.log("Connected to Phoenix server");
};

socket.onDisconnectedCallback = () => {
  console.log("Disconnected from Phoenix server");
};

// Connect and subscribe to a topic
await socket.connect();
await socket.subscribeToTopic("lobby", { user_id: "123" }, {
  user_joined: (payload) => console.log("User joined:", payload),
  user_left: (payload) => console.log("User left:", payload),
});

// Send a message
const reply = await socket.sendMessage("lobby", "new_message", {
  text: "Hello, world!"
});

Architecture

Phoenix WebSocket is built around several key components:

  • Connection Management: Automatic connection handling with configurable retry logic and timeout settings
  • Topic Subscription System: Manage multiple topic subscriptions with individual message handlers
  • Message Queue System: Promise-based message sending with reply handling and timeout management
  • Error Handling: Comprehensive error types for different failure scenarios
  • Reconnection Logic: Automatic reconnection with exponential backoff and online/offline detection

Capabilities

Connection Management

Core connection functionality for establishing and maintaining WebSocket connections to Phoenix servers.

class PhoenixWebsocket {
  constructor(
    url: string,
    queryParams?: { [key: string]: string },
    timeoutInMs?: number
  );

  connect(): Promise<void>;
  disconnect(clearTopics?: boolean): void;

  get connectionStatus(): WebsocketStatuses;
  get subscribedTopics(): string[];

  onConnectedCallback?: (() => void) | undefined;
  onDisconnectedCallback?: (() => void) | undefined;

  setLogLevel(logLevel: PhoenixWebsocketLogLevels): void;
  disposeEvents(): void;
}

Usage Examples:

// Basic connection
const socket = new PhoenixWebsocket("wss://localhost:4000/socket");
await socket.connect();

// With query parameters and timeout
const socket = new PhoenixWebsocket(
  "wss://example.com/socket",
  { token: "abc123", user: "alice" },
  30000 // 30 second timeout
);

// Check connection status
if (socket.connectionStatus === WebsocketStatuses.Connected) {
  console.log("Socket is connected");
}

// Clean disconnect (removes all topics)
socket.disconnect(true);

Topic Subscription

Topic subscription system for joining Phoenix channels and handling server messages.

type TopicMessageHandler = (data: { [key: string]: any } | undefined) => void;

// Object-based message handlers
subscribeToTopic(
  topic: string,
  payload?: { [key: string]: any },
  messageHandlers?: { [message: string]: TopicMessageHandler },
  reconnectHandler?: (reconnectPromise: Promise<void>) => void
): Promise<void>;

// Map-based message handlers
subscribeToTopic(
  topic: string,
  payload?: { [key: string]: any },
  messageHandlers?: Map<string, TopicMessageHandler>,
  reconnectHandler?: (reconnectPromise: Promise<void>) => void
): Promise<void>;

unsubscribeToTopic(topic: string): void;

Usage Examples:

// Simple subscription without message handlers
await socket.subscribeToTopic("lobby");

// With join payload
await socket.subscribeToTopic("room:123", { user_id: "alice" });

// With message handlers
await socket.subscribeToTopic("chat", { user: "alice" }, {
  user_joined: (payload) => {
    console.log(`${payload.username} joined the chat`);
  },
  user_left: (payload) => {
    console.log(`${payload.username} left the chat`);
  },
  new_message: (payload) => {
    console.log(`Message: ${payload.text}`);
  }
});

// With Map-based handlers
const handlers = new Map([
  ["user_joined", (data) => console.log("User joined:", data)],
  ["user_left", (data) => console.log("User left:", data)]
]);
await socket.subscribeToTopic("lobby", {}, handlers);

// With reconnect handler for error recovery
await socket.subscribeToTopic("critical_topic", { userId: "123" }, {
  error: (payload) => console.error("Topic error:", payload)
}, (reconnectPromise) => {
  // Called on rejoin attempts (NOT initial join)
  console.log("Topic reconnecting...");
  reconnectPromise.catch((error) => {
    if (error instanceof PhoenixRespondedWithError) {
      if (error.reply?.response?.reason === "Invalid User") {
        console.error("Invalid user on reconnect");
      }
    }
  });
});

// Handle join errors
try {
  await socket.subscribeToTopic("restricted_topic", { userId: "123" });
} catch (error) {
  if (error instanceof PhoenixRespondedWithError) {
    if (error.reply?.response?.reason === "Invalid User") {
      console.error("Access denied: Invalid user");
    }
  }
}

// Unsubscribe from topic
socket.unsubscribeToTopic("lobby");

Message Sending

Message sending functionality for communicating with Phoenix channels.

sendMessage(
  topic: string,
  message: string,
  payload?: { [key: string]: any }
): Promise<PhoenixReply>;

Usage Examples:

// Send message without payload
const reply = await socket.sendMessage("lobby", "ping");

// Send message with payload
const reply = await socket.sendMessage("chat", "new_message", {
  text: "Hello everyone!",
  user_id: "alice"
});

// Handle reply
if (reply.status === "ok") {
  console.log("Message sent successfully:", reply.response);
} else {
  console.error("Message failed:", reply.response);
}

// Error handling
try {
  const reply = await socket.sendMessage("room:123", "join_request");
} catch (error) {
  if (error instanceof PhoenixInvalidTopicError) {
    console.error("Not subscribed to topic");
  } else if (error instanceof PhoenixTimeoutError) {
    console.error("Message timed out");
  }
}

Types

Message Handler

type TopicMessageHandler = (data: { [key: string]: any } | undefined) => void;

Connection Status

enum WebsocketStatuses {
  Disconnected = 0,
  Connected = 1,
  Disconnecting = 2,
  Reconnecting = 3,
}

Topic Status

enum TopicStatuses {
  Unsubscribed = 0,
  Leaving = 1,
  Joining = 2,
  Subscribed = 3,
}

Logging Levels

enum PhoenixWebsocketLogLevels {
  Informative = 1,
  Warnings = 2,
  Errors = 3,
  Quiet = 4,
}

Server Replies

type PhoenixReply = PhoenixOkReply | PhoenixErrorReply;

type PhoenixOkReply = {
  response: { [key: string]: any } | string;
  status: "ok";
};

type PhoenixErrorReply = {
  response: { [key: string]: any } | string;
  status: "error";
};

Error Types

abstract class PhoenixError extends Error {}

class PhoenixInvalidTopicError extends PhoenixError {
  constructor(topic?: string);
}

class PhoenixInvalidStateError extends PhoenixError {
  constructor();
}

class PhoenixConnectionError extends PhoenixError {
  constructor(topic?: string);
}

class PhoenixInternalServerError extends PhoenixError {
  constructor();
}

class PhoenixRespondedWithError extends PhoenixError {
  constructor(reply?: PhoenixReply);
  reply?: PhoenixReply;
}

class PhoenixDisconnectedError extends PhoenixError {
  constructor();
}

class PhoenixTimeoutError extends PhoenixError {
  constructor();
}

Error Handling

The library provides comprehensive error handling with specific error types:

import {
  PhoenixInvalidTopicError,
  PhoenixInvalidStateError,
  PhoenixConnectionError,
  PhoenixTimeoutError
} from "phoenix-websocket";

try {
  await socket.sendMessage("nonexistent_topic", "hello");
} catch (error) {
  if (error instanceof PhoenixInvalidTopicError) {
    console.error("Topic not subscribed");
  } else if (error instanceof PhoenixInvalidStateError) {
    console.error("WebSocket not connected");
  } else if (error instanceof PhoenixConnectionError) {
    console.error("Connection error occurred");
  } else if (error instanceof PhoenixTimeoutError) {
    console.error("Request timed out");
  }
}

Advanced Configuration

Logging Configuration

socket.setLogLevel(PhoenixWebsocketLogLevels.Errors); // Only show errors
socket.setLogLevel(PhoenixWebsocketLogLevels.Quiet);  // No logging

Connection Callbacks

socket.onConnectedCallback = () => {
  // Called on initial connection and reconnections
  console.log("WebSocket connected");
};

socket.onDisconnectedCallback = () => {
  // Called when WebSocket disconnects
  console.log("WebSocket disconnected");
};

Reconnection Handling

// Subscribe with reconnection handler
await socket.subscribeToTopic("important_topic", {}, {
  data_update: (payload) => console.log("Update:", payload)
}, async (reconnectPromise) => {
  console.log("Topic is reconnecting...");
  try {
    await reconnectPromise;
    console.log("Topic successfully reconnected");
  } catch (error) {
    console.error("Topic reconnection failed:", error);
  }
});

Resource Cleanup

// Clean up event listeners
socket.disposeEvents();

// Disconnect and clear all topics
socket.disconnect(true);

// Disconnect but keep topics for reconnection
socket.disconnect(false);
Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/phoenix-websocket@2.0.x
Publish Source
CLI
Badge
tessl/npm-phoenix-websocket badge