import { Injectable } from '@angular/core'; import { DatePipe } from '@angular/common'; import { HermesSocketService } from './hermes-socket.service'; import EventService from './shared/services/EventService'; import { filter, map, Observable } from 'rxjs'; export interface Message { d: object, t: object, o: object } @Injectable({ providedIn: 'root' }) export class HermesClientService { pipe = new DatePipe('en-US'); session_id: string | undefined; connected: boolean; logged_in: boolean; constructor(private socket: HermesSocketService, private events: EventService) { this.connected = false; this.logged_in = false; this.events.listen('tts_login', (payload) => { this.login(payload); }); } public connect() { if (this.connected) return; this.socket.connect(); this.connected = true; return this.listen(); } public disconnect() { if (!this.connected) return; this.connected = false; this.logged_in = false; this.session_id = undefined; this.socket.close(); this.events.emit('tts_logoff', null); } public filter(predicate: (data: any) => boolean): Observable|undefined { return this.socket.get$()?.pipe( filter(predicate), map(d => d.d) ); } public filterByRequestType(requestName: string): Observable|undefined { return this.socket.get$()?.pipe( filter(d => d.op == 4 && d.d.request.type === requestName), map(d => d.d) ); } public first(predicate: (data: any) => boolean): Observable { return this.socket.first(predicate); } private send(op: number, data: any) { if (op != 0) console.log("TX:", data); this.socket.sendMessage({ d: data, op }); } public login(api_key: string) { if (!this.connected) this.connect(); if (this.logged_in) return; this.send(1, { api_key, web_login: true, major_version: 0, minor_version: 1 }); } public createPolicy(groupId: string, path: string, usage: number, timespan: number) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "create_policy", data: { groupId, path, count: usage, span: timespan }, }); } public createRedeemableAction(name: string, type: string, d: { [key: string]: any }) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "create_redeemable_action", data: { name, type, data: d }, nounce: this.session_id, }); } public createRedemption(twitchRedemptionId: string, actionName: string, order: number) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "create_redemption", data: { redemption: twitchRedemptionId, action: actionName, order }, nounce: this.session_id, }); } public createTTSFilter(search: string, replace: string, flag: number) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "create_tts_filter", data: { search, replace, flag }, nounce: this.session_id, }); } public deletePolicy(id: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "delete_policy", data: { id }, }); } public deleteRedeemableAction(name: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "delete_redeemable_action", data: { name }, nounce: this.session_id, }); } public deleteRedemption(id: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "delete_redemption", data: { id }, nounce: this.session_id, }); } public deleteTTSFilter(id: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "delete_tts_filter", data: { id }, nounce: this.session_id, }); } public fetchFilters() { if (!this.logged_in) return; this.send(3, { request_id: null, type: "get_tts_word_filters", data: null, }); } public fetchPermissionsAndGroups() { if (!this.logged_in) return; this.send(3, { request_id: null, type: "get_permissions", data: null, }); } public fetchPolicies() { if (!this.logged_in) return; this.send(3, { request_id: null, type: "get_policies", data: null, }); } public fetchRedeemableActions() { if (!this.logged_in) return; this.send(3, { request_id: null, type: "get_redeemable_actions", data: null, }); } public fetchRedemptions() { if (!this.logged_in) return; this.send(3, { request_id: null, type: "get_redemptions", data: null, }); } public heartbeat() { const date = new Date() this.send(0, { date_time: this.pipe.transform(date.getTime() + date.getUTCDate(), "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'") }); } public subscribe(code: number, next: (data: any) => void) { return this.socket.subscribe({ next: (message: any) => { if (message.op == code) next(message.d); } }); } public subscribeToRequests(requestType: string, action: (data: any) => void) { return this.subscribe(4, (data) => { const type = data.request.type; if (type == requestType) action(data); }); } public updatePolicy(id: string, groupId: string, path: string, usage: number, timespan: number) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "update_policy", data: { id, groupId, path, count: usage, span: timespan }, }); } public updateRedeemableAction(name: string, type: string, d: { [key: string]: any }) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "update_redeemable_action", data: { name, type, data: d }, nounce: this.session_id, }); } public updateRedemption(id: string, twitchRedemptionId: string, actionName: string, order: number) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "update_redemption", data: { id, redemption: twitchRedemptionId, action: actionName, order, state: true }, nounce: this.session_id, }); } public updateTTSFilter(id: string, search: string, replace: string, flag: number) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "update_tts_filter", data: { id, search, replace, flag }, nounce: this.session_id, }); } private listen() { return this.socket.subscribe({ next: (message: any) => { console.log("RX:", message); switch (message.op) { case 0: // Heartbeat console.log("Heartbeat received. Potential connection problem?"); break; case 2: // Login Ack console.log("Login successful."); this.logged_in = true; this.session_id = message.d.session_id; this.events.emit('tts_login_ack', null); break; } }, error: (err: any) => { console.error('Websocket error', err); if (err.type == 'close') { this.disconnect(); } }, complete: () => { console.log('Websocket disconnected.'); this.disconnect(); } }); } }