wasdtv
Version:
WASD.tv API client
153 lines (134 loc) • 4.24 kB
text/typescript
import io from 'socket.io-client'
import EventEmitter from 'events'
import { Wasd } from '../types/api'
import { SocketError } from '../core/error'
export default class SocketClient extends EventEmitter {
private heartbeat = 25
public socketio!: SocketIOClient.Socket
private _subs: (keyof Wasd.EventMap)[] = []
private _originalOn = EventEmitter.prototype.on
private _originalOff = EventEmitter.prototype.off
constructor(private jwt: Wasd.JWT) {
super()
try {
this.socketio = io('wss://chat.wasd.tv', {
path: '/socket.io',
transports: ['websocket'],
query: {
EIO: 3,
},
})
} catch (err) {
this.errorHandler(err as Error)
}
}
private connect(cb?: CallableFunction): void {
try {
this.socketio = io('wss://chat.wasd.tv', {
path: '/socket.io',
transports: ['websocket'],
query: {
EIO: 3,
},
})
} catch (err) {
this.errorHandler(err as Error)
}
this.listen(cb)
}
public join(stream_id: number, channel_id: number): void {
if (this.socketio?.connected) {
this.socketio.emit('join', {
streamId: stream_id,
channelId: channel_id,
jwt: this.jwt,
excludeStickers: true,
})
} else {
this.connect(() => {
this.join(stream_id, channel_id)
})
}
}
public leave(stream_id: number): void {
if (this.socketio?.connected) {
this.socketio.emit('leave', {
streamId: stream_id,
})
} else {
this.connect(() => {
this.leave(stream_id)
})
}
}
public sendMessage(stream_id: number, message: string, channel_id: number, streamer_id: number): void {
if (this.socketio?.connected) {
this.socketio.emit('message', {
channelId: channel_id,
hash: this.messageHash(),
jwt: this.jwt,
message: message,
streamId: stream_id,
streamerId: streamer_id,
})
} else {
throw new SocketError('Socket is not connected at this moment. Use this method only after "joined" event.')
}
}
public sendSticker(stream_id: number, sticker_id: number, channel_id: number, streamer_id: number): void {
if (this.socketio?.connected) {
this.socketio.emit('sticker', {
channel_id: channel_id,
hash: this.messageHash(),
sticker_id: sticker_id,
stream_id: stream_id,
streamer_id: streamer_id,
})
} else {
throw new SocketError('Socket is not connected at this moment. Use this method only after "joined" event.')
}
}
private messageHash(length = 25): string {
let result = ''
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'
const charactersLength = characters.length
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
private listen(cb?: CallableFunction): void {
this.socketio.on('connect', () => {
this.ping()
if (cb !== undefined) {
cb()
}
this.emit('connect', null)
console.log('connected')
this._subs.forEach((event) => {
this.socketio.on(event, (ctx: Wasd.EventMap[typeof event]) => {
this.emit(event, ctx)
})
})
})
}
public on<T extends Wasd.EventMap, V extends keyof Wasd.EventMap>(event: V, listener: (ctx: Pick<T, V>[V]) => void): this {
this._subs.push(event)
return this._originalOn(event, listener) as this
}
public off<T extends Wasd.EventMap, V extends keyof Wasd.EventMap>(event: V, listener: (ctx: Pick<T, V>[V]) => void): this {
this._subs = this._subs.filter((e) => e !== event)
return this._originalOff(event, listener) as this
}
private ping(): void {
setTimeout(() => {
if (this.socketio.connected) {
this.socketio.send(2)
this.ping()
}
}, this.heartbeat * 1000)
}
private errorHandler(err: Error): void {
throw new SocketError(`${err.name}: ${err.message}`)
}
}