├── .eslintignore ├── .eslintrc.json ├── .github ├── FUNDING.yml └── dependabot.yml ├── .gitignore ├── LICENSE ├── README.md ├── index.ts ├── jest.config.js ├── media └── blaze.png ├── package-lock.json ├── package.json ├── src ├── data │ ├── interfaces │ │ └── blaze.ts │ └── usecases │ │ ├── blaze-messages-socket.ts │ │ └── blaze-socket.ts ├── domain │ └── usecases │ │ ├── connectionSocket.ts │ │ └── socket.ts ├── infra │ ├── app │ │ └── socket.ts │ └── blaze │ │ └── blaze-url.ts └── main │ └── index.ts ├── tests └── impl-blaze.test.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard-with-typescript", 3 | "parserOptions": { 4 | "project": "./tsconfig.json" 5 | }, 6 | "rules": { 7 | "@typescript-eslint/space-before-blocks": "off", 8 | "@typescript-eslint/strict-boolean-expressions": "off", 9 | "@typescript-eslint/no-misused-promises": "off", 10 | "@typescript-eslint/no-namespace": "off" 11 | } 12 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: viniciusgdr 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: viniciuswadev 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://wa.me/558183064666'] 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Vinicius Silva 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Blaze - TS/JS Blaze API 3 | A websocket connection for Blaze games 4 | Blaze 5 | 6 | ## Install 7 | 8 | Stable version: 9 | ``` 10 | npm i @viniciusgdr/Blaze 11 | ``` 12 | Or use the edge version 13 | ``` 14 | npm i github:viniciusgdr/Blaze 15 | ``` 16 | 17 | ## Handling Events 18 | ```ts 19 | 'close': { code: number; reconnect: boolean; } 20 | 21 | 'crash.tick' 22 | 'double.tick' 23 | 'subscriptions': string[] 24 | 25 | // On enabled cacheIgnoreRepeatedEvents, the event will be sent only once. If you want to receive the event repeatedly, you can use: (or disable the cacheIgnoreRepeatedEvents) 26 | 'CB:crash.tick' 27 | 'CB:double.tick' 28 | ``` 29 | 30 | Example: 31 | ```ts 32 | const socket = makeConnection({ 33 | type: 'crash', // "crash" | "doubles" | "crash_2" 34 | }) 35 | socket.ev.on('crash.tick', (msg) => { 36 | console.log(msg) 37 | }) 38 | ``` 39 | ## Notes 40 | You can set the your token of blaze (Optional) 41 | ```ts 42 | const socket = makeConnection({ 43 | token: string 44 | }) 45 | ``` 46 | 47 | This option declared as "true" limits you from repeating the same event several times in the round. so sending only once. 48 | ```ts 49 | const socket = makeConnection({ 50 | cacheIgnoreRepeatedEvents: false 51 | // the default is true 52 | }) 53 | ``` 54 | ## Licence 55 | 56 | [MIT](https://choosealicense.com/licenses/mit/) 57 | 58 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/main' 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/tests'], 3 | collectCoverageFrom: [ 4 | '/tests/**/**.ts', 5 | '!/tests/**' 6 | ], 7 | coverageDirectory: 'coverage', 8 | testEnvironment: 'node', 9 | transform: { 10 | '.+\\.ts$': 'ts-jest' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /media/blaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viniciusgdr/Blaze/7e67afd2987c190b047acbe3f4f7c4c74db55be5/media/blaze.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@viniciusgdr/blaze", 3 | "version": "3.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "sucrase-node src/main/server.ts", 8 | "test": "jest --passWithNoTests --silent --noStackTrace --runInBand", 9 | "test:verbose": "jest --passWithNoTests --runInBand", 10 | "test:unit": "npm test -- --watch -c jest-unit-config.js", 11 | "test:integration": "npm test -- --watch -c jest-integration-config.js", 12 | "test:staged": "npm test -- --findRelatedTests", 13 | "test:ci": "npm test -- --coverage", 14 | "install": "tsc" 15 | }, 16 | "author": "viniciusgdr", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "@types/jest": "^29.5.12", 20 | "@types/node": "^22.15.3", 21 | "@types/node-fetch": "^2.6.4", 22 | "@types/ws": "^8.5.10", 23 | "@typescript-eslint/eslint-plugin": "^5.52.0", 24 | "eslint": "^8.45.0", 25 | "eslint-config-standard-with-typescript": "^37.0.0", 26 | "eslint-plugin-import": "^2.25.2", 27 | "eslint-plugin-n": "^15.0.0", 28 | "eslint-plugin-promise": "^6.0.0", 29 | "jest": "^29.3.1", 30 | "jest-mock-extended": "^3.0.4", 31 | "prisma": "^5.0.0", 32 | "ts-jest": "^29.0.3", 33 | "typescript": "^5.4.5" 34 | }, 35 | "dependencies": { 36 | "ws": "^8.14.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/data/interfaces/blaze.ts: -------------------------------------------------------------------------------- 1 | export declare interface BlazeEventMap { 2 | 'crash.tick': { 3 | id: string 4 | updated_at: string 5 | status: string 6 | crash_point: number | null 7 | // Only on crash_2 8 | is_bonus_round: boolean 9 | } 10 | // Only on crash_2 11 | 'crash.tick-bets': { 12 | id: string 13 | roomId: number 14 | total_eur_bet: number 15 | total_bets_placed: string 16 | total_eur_won: number 17 | bets: Bet[] 18 | } 19 | 'double.tick': { 20 | id: string 21 | color: string | null 22 | roll: string | null 23 | created_at: string 24 | updated_at: string 25 | status: 'rolling' | 'waiting' | 'complete' 26 | total_red_eur_bet: number 27 | total_red_bets_placed: number 28 | total_white_eur_bet: number 29 | total_white_bets_placed: number 30 | total_black_eur_bet: number 31 | total_black_bets_placed: number 32 | bets: Bet[] 33 | } 34 | 'chat.message': { 35 | id: string 36 | text: string 37 | available: boolean 38 | created_at: string 39 | user: { 40 | id: string 41 | username: string 42 | rank: string 43 | label: string | null 44 | level: number 45 | } 46 | } 47 | 'close': { 48 | code: number 49 | reconnect: boolean 50 | } 51 | 'subscriptions': string[] 52 | } 53 | 54 | export interface Bet { 55 | id: string 56 | cashed_out_at: number | null 57 | amount: number 58 | currency_type: string 59 | user: { 60 | id: string 61 | id_str: string 62 | username: string 63 | rank: string 64 | } 65 | win_amount: string 66 | status: 'win' | 'created' 67 | } 68 | -------------------------------------------------------------------------------- /src/data/usecases/blaze-messages-socket.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events' 2 | import { type ConnectionSocket } from '../../domain/usecases/connectionSocket' 3 | import { type Socket } from '../../domain/usecases/socket' 4 | import { type BlazeEventMap } from '../interfaces/blaze' 5 | 6 | export class BlazeMessageSocket implements Socket { 7 | interval: NodeJS.Timeout | null = null 8 | private readonly ev: EventEmitter = new EventEmitter() 9 | constructor ( 10 | private readonly socket: ConnectionSocket 11 | ) {} 12 | 13 | async connect (options: Socket.Options): Promise { 14 | await this.socket.connect(options) 15 | this.initPing(options.timeoutPing ?? 10000) 16 | this.initOpen(options.token) 17 | this.onMessage() 18 | this.initClose(options) 19 | } 20 | 21 | private initPing (timeoutPing: number): void { 22 | this.interval = setInterval(() => { 23 | this.socket.send('2') 24 | }, timeoutPing) 25 | } 26 | 27 | private onMessage (): void { 28 | this.socket.on('message', (data: any) => { 29 | const msg: string = data.toString() 30 | const regex = /^\d+\["data",\s*({.*})]$/ 31 | 32 | const match = msg.match(regex) 33 | if (!match) { 34 | return 35 | } 36 | 37 | const { payload, id } = JSON.parse(match[1]) ?? {} 38 | if (!payload || !id) { 39 | return 40 | } 41 | void this.ev.emit(id, payload) 42 | }) 43 | } 44 | 45 | private initClose (options: Socket.Options): void { 46 | this.socket.on('close', async (code: number) => { 47 | if (this.interval) { 48 | clearInterval(this.interval) 49 | } 50 | void this.socket.disconnect() 51 | if (options.reconnect) { 52 | await new Promise(resolve => setTimeout(resolve, 100)) 53 | void this.connect(options) 54 | } 55 | void this.ev.emit('close', { 56 | code, 57 | reconnect: options.reconnect 58 | }) 59 | }) 60 | } 61 | 62 | private initOpen (token?: string): void { 63 | this.socket.on('open', () => { 64 | const subscriptions = [] 65 | this.socket.send('420["cmd",{"id":"subscribe","payload":{"room":"chat_room_2"}}]') 66 | subscriptions.push('chat_room_2') 67 | void this.ev.emit('subscriptions', subscriptions) 68 | }) 69 | } 70 | 71 | on(event: T, callback: (data: BlazeEventMap[T]) => void): void { 72 | this.ev.on(event, callback) 73 | } 74 | 75 | emit (event: string, data: any): void { 76 | this.socket.emit(event, data) 77 | } 78 | 79 | async disconnect (): Promise { 80 | await this.socket.disconnect() 81 | } 82 | 83 | async send (data: any): Promise { 84 | this.socket.send(data) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/data/usecases/blaze-socket.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events' 2 | import { type ConnectionSocket } from '../../domain/usecases/connectionSocket' 3 | import { type Socket } from '../../domain/usecases/socket' 4 | import { type BlazeEventMap } from '../interfaces/blaze' 5 | 6 | export class BlazeSocket implements Socket { 7 | interval: NodeJS.Timeout | null = null 8 | private readonly ev: EventEmitter = new EventEmitter() 9 | private cache: Record | null = null 10 | constructor ( 11 | private readonly socket: ConnectionSocket, 12 | cacheIgnoreRepeatedEvents: boolean = true 13 | ) { 14 | if (cacheIgnoreRepeatedEvents) { 15 | this.cache = {} 16 | } 17 | } 18 | 19 | async connect (options: Socket.Options): Promise { 20 | await this.socket.connect(options) 21 | this.initPing(options.timeoutPing ?? 10000) 22 | this.initOpen(options.type ?? 'crash', options.token) 23 | this.onMessage() 24 | this.initClose(options) 25 | } 26 | 27 | private initPing (timeoutPing: number): void { 28 | this.interval = setInterval(() => { 29 | this.socket.send('2') 30 | }, timeoutPing) 31 | } 32 | 33 | private onMessage (): void { 34 | this.socket.on('message', (data: any) => { 35 | const msg: string = data.toString() 36 | const regex = /^\d+\["data",\s*({.*})]$/ 37 | 38 | const match = msg.match(regex) 39 | if (!match) { 40 | return 41 | } 42 | 43 | const { payload, id } = JSON.parse(match[1]) ?? {} 44 | if (!payload || !id || !payload.id || !payload.status) { 45 | return 46 | } 47 | if (this.cache !== null) { 48 | void this.ev.emit(`CB:${id as string}`, payload) 49 | 50 | const cache = this.cache[payload.id] 51 | if ((cache && cache !== payload.status) || !cache) { 52 | void this.ev.emit(id, payload) 53 | this.cache[payload.id] = payload.status 54 | return 55 | } 56 | this.cache[payload.id] = payload.status 57 | return 58 | } 59 | void this.ev.emit(id, payload) 60 | }) 61 | } 62 | 63 | private initClose (options: Socket.Options): void { 64 | this.socket.on('close', async (code: number) => { 65 | if (this.interval) { 66 | clearInterval(this.interval) 67 | } 68 | void this.socket.disconnect() 69 | if (options.reconnect) { 70 | await new Promise(resolve => setTimeout(resolve, 100)) 71 | void this.connect(options) 72 | } 73 | void this.ev.emit('close', { 74 | code, 75 | reconnect: options.reconnect 76 | }) 77 | }) 78 | } 79 | 80 | private initOpen (type: string, token?: string): void { 81 | this.socket.on('open', () => { 82 | const subscriptions = [] 83 | const roomMap: Record = { 84 | crash: 'crash_room_4', 85 | doubles: 'double_room_1', 86 | crash_2: 'crash_room_1', 87 | crash_neymarjr: 'crash_room_3' 88 | } 89 | 90 | const room = roomMap[type] 91 | if (!room) { 92 | throw new Error('Missing type of socket') 93 | } 94 | 95 | this.socket.send(`420["cmd",{"id":"subscribe","payload":{"room":"${room}"}}]`) 96 | subscriptions.push(room) 97 | if (token) { 98 | this.socket.send(`423["cmd",{"id":"authenticate","payload":{"token":"${token}"}}]`) 99 | this.socket.send(`422["cmd",{"id":"authenticate","payload":{"token":"${token}"}}]`) 100 | this.socket.send(`420["cmd",{"id":"authenticate","payload":{"token":"${token}"}}]`) 101 | } 102 | void this.ev.emit('subscriptions', subscriptions) 103 | }) 104 | } 105 | 106 | on(event: T, callback: (data: BlazeEventMap[T]) => void): void { 107 | this.ev.on(event, callback) 108 | } 109 | 110 | emit (event: string, data: any): void { 111 | this.socket.emit(event, data) 112 | } 113 | 114 | async disconnect (): Promise { 115 | await this.socket.disconnect() 116 | } 117 | 118 | async send (data: any): Promise { 119 | this.socket.send(data) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/domain/usecases/connectionSocket.ts: -------------------------------------------------------------------------------- 1 | export interface ConnectionSocket { 2 | connect: (options: ConnectionSocket.Options) => Promise 3 | on: (event: string, callback: (data: any) => void) => void 4 | emit: (event: string, data: any) => void 5 | send: (data: any) => void 6 | disconnect: () => Promise 7 | } 8 | 9 | export namespace ConnectionSocket { 10 | export interface Options { 11 | url?: string 12 | options?: { 13 | host?: string 14 | origin?: string 15 | headers?: Record 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/domain/usecases/socket.ts: -------------------------------------------------------------------------------- 1 | export interface GenericSocket { 2 | on: (event: keyof T, callback: (data: T[keyof T]) => void) => void 3 | emit: (event: string, data: any) => void 4 | } 5 | 6 | export interface Socket extends GenericSocket { 7 | connect: (options: Socket.Options) => Promise 8 | send: (data: any) => void 9 | disconnect: () => Promise 10 | } 11 | 12 | export declare interface SocketEvents { 13 | 'subscriptions': string[] 14 | 'close': { 15 | code: number 16 | } 17 | } 18 | 19 | export namespace Socket { 20 | export interface Options { 21 | url?: string 22 | type?: string 23 | token?: string 24 | reconnect?: boolean 25 | options?: { 26 | host?: string 27 | origin?: string 28 | headers?: Record 29 | } 30 | timeoutPing?: number 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/infra/app/socket.ts: -------------------------------------------------------------------------------- 1 | import { type ConnectionSocket } from '../../domain/usecases/connectionSocket' 2 | import WebSocket from 'ws' 3 | 4 | export class NodeConnectionSocket implements ConnectionSocket { 5 | private socket: WebSocket | null = null 6 | 7 | async connect (options: ConnectionSocket.Options): Promise { 8 | if (!options.url) { 9 | throw new Error('Missing url') 10 | } 11 | this.socket = new WebSocket(options.url, options.options) 12 | } 13 | 14 | async on (event: string, callback: (data: any) => void): Promise { 15 | if (!this.socket) { 16 | throw new Error('Missing socket') 17 | } 18 | this.socket.on(event, callback) 19 | } 20 | 21 | async emit (event: string, data: any): Promise { 22 | if (!this.socket) { 23 | throw new Error('Missing socket') 24 | } 25 | this.socket.emit(event, data) 26 | } 27 | 28 | async disconnect (): Promise { 29 | if (!this.socket) { 30 | throw new Error('Missing socket') 31 | } 32 | this.socket.close() 33 | } 34 | 35 | async send (data: any): Promise { 36 | if (!this.socket) { 37 | throw new Error('Missing socket') 38 | } 39 | this.socket.send(data) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/infra/blaze/blaze-url.ts: -------------------------------------------------------------------------------- 1 | export const getBlazeUrl = (type: 'games' | 'general'): string => { 2 | switch (type) { 3 | case 'games': 4 | return 'wss://api-gaming.blaze.bet.br/replication/?EIO=3&transport=websocket' 5 | case 'general': 6 | return 'wss://api-v2.blaze.bet.br/replication/?EIO=3&transport=websocket' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | /* eslint-disable @typescript-eslint/prefer-ts-expect-error */ 3 | import { BlazeMessageSocket } from '../data/usecases/blaze-messages-socket' 4 | import { BlazeSocket } from '../data/usecases/blaze-socket' 5 | import { NodeConnectionSocket } from '../infra/app/socket' 6 | import { getBlazeUrl } from '../infra/blaze/blaze-url' 7 | 8 | export interface ConnectionBlaze { 9 | web: 'blaze' | 'blaze-chat' 10 | type: 'crash' | 'doubles' | 'crash_2' | 'crash_neymarjr' 11 | } 12 | export type Connection = { 13 | url?: string 14 | type?: string 15 | token?: string 16 | options?: { 17 | host?: string 18 | origin?: string 19 | headers?: Record 20 | } 21 | timeoutPing?: number 22 | cacheIgnoreRepeatedEvents?: boolean 23 | } & (ConnectionBlaze) 24 | 25 | export type ConnectionSocketResponses = BlazeSocket | BlazeMessageSocket 26 | 27 | export const makeConnection = async ({ 28 | type, 29 | web, 30 | options, 31 | token, 32 | timeoutPing, 33 | url, 34 | cacheIgnoreRepeatedEvents = true 35 | }: Connection): Promise => { 36 | switch (web) { 37 | case 'blaze': { 38 | const socketOptions = { 39 | url: url ?? getBlazeUrl('games'), 40 | type, 41 | token, 42 | options: { 43 | headers: options?.headers ?? { 44 | Upgrade: 'websocket', 45 | 'Sec-Webscoket-Extensions': 'permessage-defalte; client_max_window_bits', 46 | Pragma: 'no-cache', 47 | Connection: 'Upgrade', 48 | 'Accept-Encoding': 'gzip, deflate, br', 49 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36' 50 | }, 51 | host: 'api-v2.blaze1.space', 52 | origin: 'https://api-gaming.blaze.com', 53 | ...options 54 | }, 55 | timeoutPing 56 | } 57 | const socket = new NodeConnectionSocket() 58 | const blazeSocket = new BlazeSocket(socket, cacheIgnoreRepeatedEvents) 59 | await blazeSocket.connect(socketOptions) 60 | return blazeSocket 61 | } 62 | case 'blaze-chat': { 63 | const socketOptions = { 64 | url: url ?? getBlazeUrl('general'), 65 | type, 66 | token, 67 | options: { 68 | headers: options?.headers ?? { 69 | Upgrade: 'websocket', 70 | 'Sec-Webscoket-Extensions': 'permessage-defalte; client_max_window_bits', 71 | Pragma: 'no-cache', 72 | Connection: 'Upgrade', 73 | 'Accept-Encoding': 'gzip, deflate, br', 74 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36' 75 | }, 76 | host: 'api-v2.blaze1.space', 77 | origin: 'https://api-gaming.blaze.com', 78 | ...options 79 | }, 80 | timeoutPing 81 | } 82 | 83 | const socketForMessages = new NodeConnectionSocket() 84 | const blazeSocketForMessages = new BlazeMessageSocket(socketForMessages) 85 | await blazeSocketForMessages.connect(socketOptions) 86 | 87 | return blazeSocketForMessages 88 | } 89 | default: 90 | throw new Error('Missing web') 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/impl-blaze.test.ts: -------------------------------------------------------------------------------- 1 | import { BlazeSocket } from '../src/data/usecases/blaze-socket' 2 | import { type ConnectionSocket } from '../src/domain/usecases/connectionSocket' 3 | 4 | export const makeConnectionSocket = (): ConnectionSocket => { 5 | class ConnectionSocketStub implements ConnectionSocket { 6 | async connect (options: ConnectionSocket.Options): Promise { 7 | await new Promise(resolve => { resolve() }) 8 | } 9 | 10 | on (event: string, callback: (data: any) => void): void { 11 | 12 | } 13 | 14 | emit (event: string, data: any): void { 15 | 16 | } 17 | 18 | send (data: any): void { 19 | 20 | } 21 | 22 | async disconnect (): Promise { 23 | await new Promise(resolve => { resolve() }) 24 | } 25 | } 26 | return new ConnectionSocketStub() 27 | } 28 | 29 | interface SutTypes { 30 | sut: BlazeSocket 31 | connectionSocketStub: ConnectionSocket 32 | } 33 | 34 | const makeSut = (cacheIgnoreRepeatedEvents: boolean = true): SutTypes => { 35 | const connectionSocketStub = makeConnectionSocket() 36 | const sut = new BlazeSocket(connectionSocketStub, cacheIgnoreRepeatedEvents) 37 | return { 38 | sut, 39 | connectionSocketStub 40 | } 41 | } 42 | 43 | describe('Implementation test (Crash)', () => { 44 | test('should receive an action waiting (repeat)', async () => { 45 | const { sut } = makeSut() 46 | await sut.connect({}) 47 | sut.on('crash.tick', (data) => { 48 | expect(data.status).toBe('waiting') 49 | }) 50 | sut.emit('message', '42["data", {"payload": {"id": "1", "status": "waiting"}}]') 51 | }) 52 | 53 | test('should receive an action waiting (not repeat)', async () => { 54 | const { sut } = makeSut() 55 | await sut.connect({}) 56 | let count = 0 57 | sut.on('crash.tick', () => { 58 | count++ 59 | expect(count).toBe(1) 60 | }) 61 | sut.emit('message', '42["data", {"payload": {"id": "1", "status": "waiting"}}]') 62 | sut.emit('message', '42["data", {"payload": {"id": "1", "status": "waiting"}}]') 63 | sut.emit('message', '42["data", {"payload": {"id": "1", "status": "waiting"}}]') 64 | }) 65 | test('should on close, emits event', async () => { 66 | const { sut } = makeSut() 67 | await sut.connect({}) 68 | sut.on('close', (data) => { 69 | expect(data).toBe('error') 70 | }) 71 | sut.emit('close', 'error') 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | --------------------------------------------------------------------------------