# 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.

```typescript
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)
    })
```

```typescript
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')
        }
    }
}

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.tronsocket.com/pure-websocket-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
