CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-types--phoenix

TypeScript definitions for Phoenix JavaScript client library enabling real-time WebSocket communication with Phoenix Framework applications

Overview
Eval results
Files

utilities.mddocs/

Transport and Utilities

Supporting utilities including Timer for reconnection backoff, LongPoll fallback transport, and Ajax helper for HTTP requests when WebSocket is unavailable.

Capabilities

Timer

Utility class for managing reconnection timing with configurable backoff strategies.

/**
 * Creates a Timer for managing reconnection backoff
 * @param callback - Function to call when timer fires
 * @param timerCalc - Function to calculate timeout based on attempt count
 */
constructor(callback: () => void | Promise<void>, timerCalc: (tries: number) => number);

/**
 * Reset timer state (clears current timeout and resets attempt counter)
 */
reset(): void;

/**
 * Schedule the next timeout based on current attempt count
 */
scheduleTimeout(): void;

Usage Example:

import { Timer } from "phoenix";

// Basic exponential backoff
const reconnectTimer = new Timer(
  () => {
    console.log("Attempting to reconnect...");
    socket.connect();
  },
  (tries) => {
    // Exponential backoff: 1s, 2s, 4s, 8s, max 30s
    return Math.min(1000 * Math.pow(2, tries - 1), 30000);
  }
);

// Start the timer
reconnectTimer.scheduleTimeout();

// Reset on successful connection
socket.onOpen(() => {
  reconnectTimer.reset();
});

// Advanced backoff with jitter
const advancedTimer = new Timer(
  async () => {
    try {
      await attemptReconnection();
    } catch (error) {
      console.error("Reconnection failed:", error);
    }
  },
  (tries) => {
    const baseDelay = [1000, 2000, 5000, 10000, 30000][tries - 1] || 30000;
    const jitter = Math.random() * 1000; // Add up to 1s jitter
    return baseDelay + jitter;
  }
);

LongPoll

Fallback transport mechanism for long polling when WebSocket connections are unavailable.

/**
 * Creates a LongPoll transport instance
 * @param endPoint - Long polling endpoint URL
 */
constructor(endPoint: string);

/**
 * Normalize endpoint URL for long polling
 * @param endPoint - Raw endpoint URL
 * @returns Normalized endpoint URL
 */
normalizeEndpoint(endPoint: string): string;

/**
 * Get the formatted endpoint URL
 * @returns Complete long polling URL
 */
endpointURL(): string;

/**
 * Close current connection and retry
 */
closeAndRetry(): void;

/**
 * Handle timeout events
 */
ontimeout(): void;

/**
 * Perform long polling request
 */
poll(): void;

/**
 * Send data via long polling
 * @param body - Data to send
 */
send(body: any): void;

/**
 * Close the long polling connection
 * @param code - Close code (optional)
 * @param reason - Close reason (optional)
 */
close(code?: any, reason?: any): void;

Usage Example:

import { LongPoll } from "phoenix";

// Create long poll transport
const longPoll = new LongPoll("/longpoll");

console.log("LongPoll URL:", longPoll.endpointURL());

// Send data
longPoll.send({
  event: "heartbeat",
  payload: {},
  ref: "unique-ref"
});

// Start polling
longPoll.poll();

// Handle timeout
longPoll.ontimeout = () => {
  console.log("Long poll timed out, retrying...");
  longPoll.closeAndRetry();
};

// Close when done
longPoll.close(1000, "Normal closure");

// Custom transport implementation
class CustomLongPoll extends LongPoll {
  constructor(endpoint: string) {
    super(endpoint);
  }

  poll() {
    console.log("Custom polling logic");
    super.poll();
  }

  send(body: any) {
    console.log("Sending via custom long poll:", body);
    super.send(body);
  }
}

Ajax

HTTP request utility class for Phoenix communication with various request methods.

/**
 * Request state constants
 */
static states: { [state: string]: number };

/**
 * Make HTTP request
 * @param method - HTTP method (GET, POST, PUT, DELETE, etc.)
 * @param endPoint - Request endpoint URL
 * @param accept - Accept header value
 * @param body - Request body
 * @param timeout - Request timeout in milliseconds (optional)
 * @param ontimeout - Timeout callback (optional)
 * @param callback - Response callback (optional)
 */
static request(
  method: string,
  endPoint: string,
  accept: string,
  body: any,
  timeout?: number,
  ontimeout?: any,
  callback?: (response?: any) => void | Promise<void>
): void;

/**
 * Cross-domain request handler
 * @param req - Request object
 * @param method - HTTP method
 * @param endPoint - Request endpoint
 * @param body - Request body
 * @param timeout - Request timeout (optional)
 * @param ontimeout - Timeout callback (optional)
 * @param callback - Response callback (optional)
 */
static xdomainRequest(
  req: any,
  method: string,
  endPoint: string,
  body: any,
  timeout?: number,
  ontimeout?: any,
  callback?: (response?: any) => void | Promise<void>
): void;

/**
 * XMLHttpRequest wrapper
 * @param req - XMLHttpRequest object
 * @param method - HTTP method
 * @param endPoint - Request endpoint
 * @param accept - Accept header value
 * @param body - Request body
 * @param timeout - Request timeout (optional)
 * @param ontimeout - Timeout callback (optional)
 * @param callback - Response callback (optional)
 */
static xhrRequest(
  req: any,
  method: string,
  endPoint: string,
  accept: string,
  body: any,
  timeout?: number,
  ontimeout?: any,
  callback?: (response?: any) => void | Promise<void>
): void;

/**
 * Parse JSON response safely
 * @param resp - Response string
 * @returns Parsed JSON object
 */
static parseJSON(resp: string): JSON;

/**
 * Serialize object to query string
 * @param obj - Object to serialize
 * @param parentKey - Parent key for nested serialization
 * @returns URL-encoded query string
 */
static serialize(obj: any, parentKey: string): string;

/**
 * Append parameters to URL
 * @param url - Base URL
 * @param params - Parameters to append
 * @returns URL with appended parameters
 */
static appendParams(url: string, params: any): string;

Usage Example:

import { Ajax } from "phoenix";

// Check request states
console.log("Ajax states:", Ajax.states);

// Basic GET request
Ajax.request(
  "GET",
  "/api/users",
  "application/json",
  null,
  5000,
  () => console.log("Request timed out"),
  (response) => {
    console.log("Users loaded:", response);
  }
);

// POST request with JSON body
Ajax.request(
  "POST",
  "/api/users",
  "application/json",
  { name: "John Doe", email: "john@example.com" },
  10000,
  () => console.log("Create user timed out"),
  (response) => {
    if (response.status === 201) {
      console.log("User created:", response.data);
    } else {
      console.error("Failed to create user:", response.errors);
    }
  }
);

// Serialize object for query parameters
const params = { filter: "active", sort: "name", limit: 10 };
const queryString = Ajax.serialize(params, "");
console.log("Query string:", queryString); // "filter=active&sort=name&limit=10"

// Append parameters to URL
const baseUrl = "/api/posts";
const fullUrl = Ajax.appendParams(baseUrl, params);
console.log("Full URL:", fullUrl); // "/api/posts?filter=active&sort=name&limit=10"

// Parse JSON response
try {
  const data = Ajax.parseJSON('{"success": true, "data": [1,2,3]}');
  console.log("Parsed data:", data);
} catch (error) {
  console.error("JSON parse error:", error);
}

Advanced Usage Patterns

Connection Recovery with Timer

class ConnectionRecovery {
  private reconnectTimer: Timer;
  private maxAttempts: number = 10;
  private attempts: number = 0;

  constructor(private socket: Socket) {
    this.reconnectTimer = new Timer(
      () => this.attemptReconnection(),
      (tries) => this.calculateBackoff(tries)
    );

    this.setupSocketHandlers();
  }

  private setupSocketHandlers() {
    this.socket.onOpen(() => {
      console.log("Connected successfully");
      this.attempts = 0;
      this.reconnectTimer.reset();
    });

    this.socket.onClose(() => {
      console.log("Connection lost, starting recovery");
      this.startRecovery();
    });

    this.socket.onError((error) => {
      console.error("Socket error:", error);
      this.startRecovery();
    });
  }

  private startRecovery() {
    if (this.attempts < this.maxAttempts) {
      this.attempts++;
      console.log(`Recovery attempt ${this.attempts}/${this.maxAttempts}`);
      this.reconnectTimer.scheduleTimeout();
    } else {
      console.error("Max reconnection attempts exceeded");
      this.onMaxAttemptsExceeded();
    }
  }

  private attemptReconnection() {
    console.log("Attempting to reconnect...");
    this.socket.connect();
  }

  private calculateBackoff(tries: number): number {
    // Progressive backoff: 1s, 2s, 5s, 10s, 30s, then 30s
    const delays = [1000, 2000, 5000, 10000, 30000];
    return delays[tries - 1] || 30000;
  }

  private onMaxAttemptsExceeded() {
    // Show user notification or redirect
    console.log("Connection could not be recovered");

    // Optional: Show user a retry button
    this.showRetryDialog();
  }

  private showRetryDialog() {
    const retry = confirm("Connection lost. Would you like to try reconnecting?");
    if (retry) {
      this.attempts = 0;
      this.startRecovery();
    }
  }

  forceReconnect() {
    this.attempts = 0;
    this.reconnectTimer.reset();
    this.attemptReconnection();
  }
}

// Usage
const recovery = new ConnectionRecovery(socket);

HTTP Fallback Service

class HttpFallbackService {
  private isUsingFallback = false;

  constructor(private baseUrl: string) {}

  async request(method: string, endpoint: string, data?: any, timeout = 5000): Promise<any> {
    return new Promise((resolve, reject) => {
      const fullUrl = `${this.baseUrl}${endpoint}`;

      Ajax.request(
        method,
        fullUrl,
        "application/json",
        data ? JSON.stringify(data) : null,
        timeout,
        () => {
          reject(new Error(`Request to ${endpoint} timed out`));
        },
        (response) => {
          try {
            const parsed = typeof response === 'string'
              ? Ajax.parseJSON(response)
              : response;
            resolve(parsed);
          } catch (error) {
            reject(new Error(`Failed to parse response: ${error}`));
          }
        }
      );
    });
  }

  async get(endpoint: string, params?: any, timeout?: number): Promise<any> {
    const url = params ? Ajax.appendParams(endpoint, params) : endpoint;
    return this.request("GET", url, null, timeout);
  }

  async post(endpoint: string, data: any, timeout?: number): Promise<any> {
    return this.request("POST", endpoint, data, timeout);
  }

  async put(endpoint: string, data: any, timeout?: number): Promise<any> {
    return this.request("PUT", endpoint, data, timeout);
  }

  async delete(endpoint: string, timeout?: number): Promise<any> {
    return this.request("DELETE", endpoint, null, timeout);
  }

  enableFallback() {
    this.isUsingFallback = true;
    console.log("HTTP fallback enabled");
  }

  disableFallback() {
    this.isUsingFallback = false;
    console.log("HTTP fallback disabled");
  }
}

// Usage
const httpService = new HttpFallbackService("/api");

// Use when WebSocket is unavailable
socket.onError(() => {
  httpService.enableFallback();
});

socket.onOpen(() => {
  httpService.disableFallback();
});

// Make HTTP requests
httpService.get("/users", { active: true })
  .then(users => console.log("Users:", users))
  .catch(error => console.error("Failed to fetch users:", error));

httpService.post("/messages", {
  channel: "general",
  content: "Hello via HTTP"
})
  .then(message => console.log("Message sent:", message))
  .catch(error => console.error("Failed to send message:", error));

Hybrid Transport Manager

class HybridTransportManager {
  private socket: Socket;
  private longPoll: LongPoll;
  private httpService: HttpFallbackService;
  private currentTransport: 'websocket' | 'longpoll' | 'http' = 'websocket';

  constructor(wsEndpoint: string, httpBaseUrl: string) {
    this.socket = new Socket(wsEndpoint, {
      longPollFallbackMs: 2000,
      transport: (endpoint) => new LongPoll(endpoint)
    });

    this.longPoll = new LongPoll(wsEndpoint + "/longpoll");
    this.httpService = new HttpFallbackService(httpBaseUrl);

    this.setupTransportFallback();
  }

  private setupTransportFallback() {
    // Try WebSocket first
    this.socket.onOpen(() => {
      this.currentTransport = 'websocket';
      console.log("Using WebSocket transport");
    });

    // Fallback to long polling
    this.socket.onError(() => {
      if (this.currentTransport === 'websocket') {
        console.log("WebSocket failed, trying long polling");
        this.currentTransport = 'longpoll';
        this.startLongPolling();
      } else if (this.currentTransport === 'longpoll') {
        console.log("Long polling failed, using HTTP");
        this.currentTransport = 'http';
      }
    });
  }

  private startLongPolling() {
    this.longPoll.poll();

    this.longPoll.ontimeout = () => {
      console.log("Long poll timeout, falling back to HTTP");
      this.currentTransport = 'http';
    };
  }

  async sendMessage(channel: string, event: string, payload: any): Promise<any> {
    switch (this.currentTransport) {
      case 'websocket':
        return this.sendViaWebSocket(channel, event, payload);

      case 'longpoll':
        return this.sendViaLongPoll(event, payload);

      case 'http':
        return this.sendViaHttp(channel, event, payload);
    }
  }

  private sendViaWebSocket(channel: string, event: string, payload: any): Promise<any> {
    return new Promise((resolve, reject) => {
      const ch = this.socket.channel(channel);
      ch.push(event, payload)
        .receive("ok", resolve)
        .receive("error", reject);
    });
  }

  private sendViaLongPoll(event: string, payload: any): Promise<any> {
    return new Promise((resolve, reject) => {
      this.longPoll.send({
        event,
        payload,
        ref: Date.now().toString()
      });

      // Simplified - in reality you'd handle the response
      setTimeout(() => resolve({ status: "sent" }), 100);
    });
  }

  private sendViaHttp(channel: string, event: string, payload: any): Promise<any> {
    return this.httpService.post(`/channels/${channel}/messages`, {
      event,
      payload
    });
  }

  getCurrentTransport(): string {
    return this.currentTransport;
  }

  connect() {
    this.socket.connect();
  }

  disconnect() {
    this.socket.disconnect();
    this.longPoll.close();
  }
}

// Usage
const transportManager = new HybridTransportManager("/socket", "/api");

transportManager.connect();

// Send message - automatically uses best available transport
transportManager.sendMessage("room:lobby", "new_message", {
  body: "Hello from hybrid transport!"
})
  .then(response => console.log("Message sent:", response))
  .catch(error => console.error("Send failed:", error));

console.log("Current transport:", transportManager.getCurrentTransport());

Install with Tessl CLI

npx tessl i tessl/npm-types--phoenix

docs

channel.md

index.md

presence.md

push.md

socket.md

utilities.md

tile.json