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 { private readonly pipe = new DatePipe('en-US'); session_id: string | undefined; connected: boolean; logged_in: boolean; api_key: string | undefined; constructor(private socket: HermesSocketService, private events: EventService) { this.connected = false; this.logged_in = false; } public connect() { if (this.connected) return; this.socket.connect(); this.connected = true; return this.listen(); } public disconnect(impersonated: boolean = false) { if (!this.connected) return; this.connected = false; this.logged_in = false; this.session_id = undefined; this.api_key = undefined; this.socket.close(); this.events.emit('tts_logoff', impersonated); } 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.api_key = api_key; this.send(1, { api_key, web_login: true, major_version: 0, minor_version: 4 }); } public createConnection(name: string, type: string, client_id: string, access_token: string, grant_type: string, scope: string, expiration: Date) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "create_connection", data: { name, type, client_id, access_token, grant_type, scope, expiration }, }); } public createConnectionState(name: string, type: string, client_id: string, grant_type: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "create_connection_state", data: { name, type, client_id, grant_type }, }); } public createGroup(name: string, priority: number) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "create_group", data: { name, priority }, }); } public createGroupChatter(groupId: string, chatterId: string, chatterLabel: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "create_group_chatter", data: { group: groupId, chatter: chatterId, label: chatterLabel }, }); } public createGroupPermission(groupId: string, path: string, allow: boolean | null) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "create_group_permission", data: { group: groupId, path, allow }, }); } 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 deleteConnection(name: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "delete_connection", data: { name }, }); } public deleteConnectionState(name: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "delete_connection_state", data: { name }, }); } public deleteGroup(id: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "delete_group", data: { id }, }); } public deleteGroupChatter(groupId: string, chatterId: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "delete_group_chatter", data: { group: groupId, chatter: chatterId }, }); } public deleteGroupPermission(id: string) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "delete_group_permission", data: { 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 fetchConnections() { if (!this.logged_in) return; this.send(3, { request_id: null, type: "get_connections", data: null, }); } public fetchConnectionStates() { if (!this.logged_in) return; this.send(3, { request_id: null, type: "get_connection_states", data: null, }); } public fetchFilters() { if (!this.logged_in) return; this.send(3, { request_id: null, type: "get_tts_word_filters", data: null, }); } public fetchGroups() { if (!this.logged_in) return; this.send(3, { request_id: null, type: "get_groups", data: null, }); } public fetchPermissions() { if (!this.logged_in) return; this.send(3, { request_id: null, type: "get_group_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 updateGroup(id: string, name: string, priority: number) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "update_group", data: { id, name, priority }, }); } public updateGroupPermission(id: string, groupId: string, path: string, allow: boolean | null) { if (!this.logged_in) return; this.send(3, { request_id: null, type: "update_group_permission", data: { id, group: groupId, path, allow }, }); } 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("TTS Heartbeat received. Potential connection problem?"); break; case 2: // Login Ack if (message.d.another_client) { return; } console.log("TTS Login successful."); this.logged_in = true; this.session_id = message.d.session_id; this.events.emit('tts_login_ack', message.d); break; } }, error: (err: any) => { console.error('Websocket error', err); if (err.type == 'close') { this.disconnect(); } }, complete: () => { console.log('Websocket disconnected.'); this.disconnect(); } }); } }