TronSocket
  • Welcome
  • Auth Token
    • Security
  • SDK (Socket.IO)
  • Pure WebSocket API
  • RPC Endpoints (Upcoming)
  • What is Limits?
Powered by GitBook
On this page

Pure WebSocket API

While developing our WebSocket service, we used Socket.IO and prepared an SDK for it. However, if you are going to use our service with a language other than TypeScript, you should find a Socket.IO interface for the language you will use, or you can learn how to connect to our service via standard WebSocket based on the TypeScript example below.

import TronSocket from './pure-ws'

const token = 'your-token-here'

const socket = new TronSocket(token)

socket
    .connect()
    .then(() => {
        console.log('Connected')
        socket.on('block', (block) => {
            console.log(
                'New block',
                {
                    hash: block.blockHash,
                    number: block.blockNumber
                }
            )
        })
    })
    .catch((error) => {
        console.error('Failed to connect', error)
    })
import WebSocket from 'ws'
import { EventFilters, EventTypes } from '@tronsocket/types'

type EmitError = {
    message: string
    code: number
    key?: string
}

type Events = keyof EventTypes
type Filters<E extends Events> = EventFilters[E]
type Callback<E extends Events> = (data: EventTypes[E]) => void

export default class TronSocket {
    private token: string

    private testnet: boolean

    private socket: WebSocket | null = null

    private socketUrl: string = 'wss://ws.tronsocket.com'

    private connectionUrl: string

    private listeners: { [key: string]: Callback<any>[] } = {}

    constructor(token: string, testnet?: boolean) {
        const api = new URL(this.socketUrl + '/socket.io/?EIO=4&transport=websocket')
        api.searchParams.set('testnet', testnet ? 'true' : 'false')
        api.searchParams.set('token', token)
        this.connectionUrl = api.toString()
        this.testnet = testnet || false
        this.token = token
    }

    private parseData(data: WebSocket.Data): { type: string; payload: any } {
        let message: string

        // Convert different data types to string
        if (typeof data === 'string') {
            message = data
        } else if (data instanceof ArrayBuffer) {
            message = new TextDecoder().decode(data)
        } else if (Array.isArray(data)) {
            message = Buffer.concat(data).toString('utf-8')
        } else if (data instanceof Buffer) {
            message = data.toString('utf-8')
        } else {
            return { type: 'error', payload: { message: 'Unsupported data type', code: 500 } }
        }

        // Socket.IO message format: <packet type>[JSON data]
        const match = message.match(/^(\d+)(.*)$/)

        if (!match) {
            return {
                type: 'error',
                payload: { message: 'Invalid message format', code: 500, rawData: message }
            }
        }

        const [, packetType, packetData] = match

        try {
            let parsedData: any

            // Parse the JSON data if it exists
            if (packetData) {
                parsedData = JSON.parse(packetData)
            }

            // Map Socket.IO packet types to our event types
            switch (packetType) {
                case '0': // Connect
                    return { type: 'connect', payload: parsedData }
                case '1': // Disconnect
                    return { type: 'disconnect', payload: parsedData }
                case '2': // Event
                    if (Array.isArray(parsedData) && parsedData.length > 0) {
                        return { type: parsedData[0], payload: parsedData.slice(1) }
                    }
                    return { type: 'event', payload: parsedData }
                case '3': // Ack
                    return { type: 'ack', payload: parsedData }
                case '4': // Error
                    return { type: 'error', payload: parsedData }
                case '5': // Binary Event
                    return { type: 'binary_event', payload: parsedData }
                case '6': // Binary Ack
                    return { type: 'binary_ack', payload: parsedData }
                default:
                    return { type: 'unknown', payload: { packetType, data: parsedData } }
            }
        } catch (error) {
            console.error('Failed to parse Socket.IO message:', error)
            return {
                type: 'error',
                payload: {
                    message: 'Failed to parse Socket.IO message',
                    code: 500,
                    rawData: message
                }
            }
        }
    }

    private createWsFormat(key: string, event: string, filter?: object): string {
        if (!filter) {
            return `42["${key}","${event}"]`
        } else {
            return `42["${key}","${event}",${JSON.stringify(filter)}]`
        }
    }

    private simpleHash(str: string): string {
        let hash = 0
        for (let i = 0; i < str?.length; i++) {
            const char = str.charCodeAt(i)
            hash = (hash << 5) - hash + char
            hash = hash & hash // Convert to 32-bit integer
        }
        return Math.abs(hash).toString(16)
    }

    private createEventKey<E extends Events>(
        event: E,
        callback: Callback<E>,
        filter?: Filters<E>
    ): string {
        const stringFilter = this.simpleHash(JSON.stringify(filter))
        const stringFunction = this.simpleHash(callback.toString())
        const definer = stringFilter + stringFunction
        return event + `-${definer}`
    }

    private startListener(): void {
        this.socket!.onmessage = (event) => {
            const data = this.parseData(event.data)
            if (data.payload?.packetType === '42') {
                if (data.payload.data[0] === 'error') {
                    this.listeners['error']?.forEach((callback) => {
                        callback(data.payload.data[1])
                    })
                } else {
                    const eventKey = data.payload.data[0]
                    const eventData = data.payload.data[1]
                    this.listeners[eventKey]?.forEach((callback) => {
                        callback(eventData)
                    })
                }
            }
        }
    }

    public async connect(): Promise<boolean> {
        return new Promise((resolve, reject) => {
            this.socket = new WebSocket(this.connectionUrl)

            this.socket.onmessage = (event) => {
                const data = this.parseData(event.data)
                if (data.type === 'error') {
                    reject(data.payload)
                } else {
                    if (data.payload?.packetType === '42') {
                        if (data.payload.data[0] === 'error') {
                            reject(data.payload.data[1])
                        } else if (data.payload.data[0] === 'ready') {
                            this.startListener()
                            resolve(true)
                        } else if (data.payload?.packetType === '41') {
                            this.socket?.close()
                        }
                    }
                }
            }

            this.socket.onopen = () => {
                this.socket?.send('40') // for socket.io connection
            }

            this.socket.onerror = (error) => {
                reject(error.message)
            }

            this.socket.onclose = (error) => {
                reject(error.reason || error.code)
            }
        })
    }

    public async disconnect(): Promise<boolean> {
        return new Promise((resolve, reject) => {
            if (this.socket) {
                this.socket.send('41') // for socket.io disconnection
                this.socket.onclose = () => {
                    this.socket = null
                    resolve(true)
                }
                this.socket.close()
            } else {
                reject('Socket not connected')
            }
        })
    }

    public subscribe<E extends Events>(event: E, callback: Callback<E>, filter?: Filters<E>): void {
        if (this.socket) {
            const eventKey = this.createEventKey(event, callback, filter)
            this.socket.send(this.createWsFormat('subscribe', eventKey, filter))
            this.listeners[eventKey] = this.listeners[eventKey] || []
            this.listeners[eventKey].push(callback)
        } else {
            throw new Error('Socket not connected')
        }
    }

    public on<E extends Events>(event: E, callback: Callback<E>, filter?: Filters<E>): void {
        this.subscribe(event, callback, filter)
    }

    public unsubscribe<E extends Events>(event: E, callback: Callback<E>, filter?: Filters<E>): void {
        if (this.socket) {
            const eventKey = this.createEventKey(event, callback, filter)
            this.socket.send(this.createWsFormat('unsubscribe', eventKey, filter))
            delete this.listeners[eventKey]
        } else {
            throw new Error('Socket not connected')
        }
    }

    public off<E extends Events>(event: E, callback: Callback<E>, filter?: Filters<E>): void {
        this.unsubscribe(event, callback, filter)
    }

    public error(callback: (error: EmitError) => void): void {
        if (this.socket) {
            this.listeners['error'] = this.listeners['error'] || []
            this.listeners['error'].push(callback)
        } else {
            throw new Error('Socket not connected')
        }
    }
}
PreviousSDK (Socket.IO)NextRPC Endpoints (Upcoming)

Last updated 7 months ago