import files to git
This commit is contained in:
parent
a2273e4899
commit
9e9b1556d8
|
@ -0,0 +1,310 @@
|
|||
import $http from "./httpClient";
|
||||
import logger from "./log";
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
const $log = new logger("[bitwave.tv API]");
|
||||
|
||||
export interface Message {
|
||||
avatar: string;
|
||||
badge: string;
|
||||
color: string;
|
||||
unum: number;
|
||||
message: string;
|
||||
timestamp: number;
|
||||
username: string;
|
||||
channel: string;
|
||||
global: boolean;
|
||||
type: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface OutgoingMessage {
|
||||
message: string;
|
||||
channel: string;
|
||||
global: boolean;
|
||||
showBadge: boolean;
|
||||
}
|
||||
|
||||
export type Credentials = string | Token | void;
|
||||
|
||||
const apiPrefix = "https://api.bitwave.tv/api/";
|
||||
const chatServer = "https://chat.bitwave.tv";
|
||||
const whisperEndpoint: [string, string] = [
|
||||
"api.bitwave.tv",
|
||||
"/v1/whispers/send",
|
||||
];
|
||||
|
||||
export interface Token {
|
||||
recaptcha: any;
|
||||
page: string;
|
||||
jwt: string;
|
||||
}
|
||||
|
||||
/* ========================================= */
|
||||
|
||||
export class FedwaveChat {
|
||||
private _socket = null;
|
||||
|
||||
private _chatServer = "https://fw.rnih.org";
|
||||
private _apiPrefix = "https://fw.rnih.org";
|
||||
private _whisperEndpoint = "/v1/whispers/send";
|
||||
|
||||
public async getTrollToken() {
|
||||
try {
|
||||
return await $http.get(this._apiPrefix + "/mktroll");
|
||||
} catch (e) {
|
||||
$log.error(`Couldn't get troll token!`, e);
|
||||
}
|
||||
}
|
||||
|
||||
private userProfile: Token = {
|
||||
recaptcha: null,
|
||||
page: "global", // room name
|
||||
jwt: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses `credentials` to get a token from the server.
|
||||
*
|
||||
* @return JWT token as string
|
||||
*/
|
||||
public async initToken(credentials: Token | void) {
|
||||
if (credentials) {
|
||||
this.userProfile = credentials;
|
||||
} else {
|
||||
this.userProfile.jwt = await this.getTrollToken();
|
||||
this.onUpdateCredentials(this.userProfile.jwt);
|
||||
}
|
||||
}
|
||||
|
||||
public global: boolean | any = true; /**< Global chat mode flag */
|
||||
|
||||
/**
|
||||
* Callback function that receives messages (in bulk)
|
||||
* @param ms Message object array
|
||||
*/
|
||||
public rcvMessageBulk(ms: Message[]): void {
|
||||
for (const m of ms) console.log(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function that receives paid chat alert objects
|
||||
* @param message Alert object
|
||||
*/
|
||||
public alert(message: Object): void {
|
||||
$log.warn(`Received alert: `, message);
|
||||
}
|
||||
|
||||
public channelViewers = []; /**< Array of channel viewers. */
|
||||
|
||||
/**
|
||||
* Callback function that gets called when the list of usernames gets updated.
|
||||
*/
|
||||
public async onUpdateUsernames(newViewers: any[]) {}
|
||||
|
||||
/**
|
||||
* Callback function that gets called when current credentials change.
|
||||
*/
|
||||
public async onUpdateCredentials(newCredentials: Credentials) {}
|
||||
|
||||
/**
|
||||
* Gets an array of usernames from the server and puts it in channelViewers
|
||||
* It is called automatically at request from the server, but can be called manually
|
||||
* @see channelViewers
|
||||
*/
|
||||
public async updateUsernames(): Promise<void> {
|
||||
try {
|
||||
const data = await $http.get(this._apiPrefix + "/v1/chat/channels");
|
||||
if (data && data.success) {
|
||||
await this.onUpdateUsernames(data.data);
|
||||
this.channelViewers = data.data;
|
||||
}
|
||||
} catch (e) {
|
||||
$log.error(`Couldn't update usernames!`);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public onHydrate(data: Message[]) {
|
||||
this.rcvMessageBulk(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests messages from the server (called hydration)
|
||||
* It is called automatically when reconnecting.
|
||||
* @see socketError()
|
||||
* @return False if unsuccessful or empty
|
||||
*/
|
||||
public async hydrate(): Promise<boolean> {
|
||||
try {
|
||||
const url: string =
|
||||
this._apiPrefix +
|
||||
"/v1/messages/" +
|
||||
(!this.global && this.userProfile.page ? this.userProfile.page : "");
|
||||
const data = JSON.parse(await $http.get(url));
|
||||
if (!data.size)
|
||||
return $log.warn("Hydration data was empty") === undefined && false;
|
||||
|
||||
this.onHydrate(data.data);
|
||||
return true;
|
||||
} catch (e) {
|
||||
$log.error(`Couldn't get chat hydration data!`);
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when connecting to the server
|
||||
*/
|
||||
public socketConnect() {
|
||||
this._socket.emit("new user", this.userProfile);
|
||||
$log.info(`Connected to chat! (${this.userProfile.page})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the server issues a reconnect.
|
||||
* It force hydrates chat to catch up.
|
||||
*/
|
||||
public async socketReconnect() {
|
||||
$log.info("Socket issued 'reconnect'. Forcing hydration...");
|
||||
await this.hydrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when there's a socket error.
|
||||
*/
|
||||
public async socketError(message: string, error) {
|
||||
$log.error(`Socket error: ${message}`, error);
|
||||
// TODO: handle error
|
||||
}
|
||||
|
||||
public blocked(data) {
|
||||
$log.info("TODO: handle blocked event", data);
|
||||
}
|
||||
|
||||
public pollstate(data) {
|
||||
$log.info("TODO: handle pollstate event", data);
|
||||
}
|
||||
|
||||
public constructor(doLogging?: boolean) {
|
||||
$log.doOutput = doLogging;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits data and starts connection to server
|
||||
* @param room is a string for the channel you wish to connect to
|
||||
* @param credentials User credentials if falsy, gets a new troll token. If a string, it's taken as the JWT chat token
|
||||
* @param specificServer URI to a specific chat server
|
||||
*/
|
||||
async connect(
|
||||
room: string,
|
||||
credentials: string | Token | void,
|
||||
specificServer?: string
|
||||
) {
|
||||
if (typeof credentials === "string") this.userProfile.jwt = credentials;
|
||||
else this.initToken(credentials);
|
||||
|
||||
this.userProfile.page = room;
|
||||
|
||||
const socketOptions = { transports: ["websocket"] };
|
||||
this._socket = io(specificServer || this._chatServer, socketOptions);
|
||||
|
||||
const sockSetup = new Map([
|
||||
[
|
||||
"connect",
|
||||
async () => {
|
||||
this._socket.emit("new user", this.userProfile);
|
||||
$log.info(`Connected to chat! (${this.userProfile.page})`);
|
||||
await this.socketConnect.call(this);
|
||||
},
|
||||
],
|
||||
[
|
||||
"reconnect",
|
||||
async () => {
|
||||
$log.info("Socket issued 'reconnect'. Forcing hydration...");
|
||||
await this.hydrate();
|
||||
await this.socketReconnect.call(this);
|
||||
},
|
||||
],
|
||||
[
|
||||
"error",
|
||||
async (error: Object) => {
|
||||
// TODO: handle error
|
||||
$log.error(`Socket error: Connection Failed`, error);
|
||||
await this.socketError.call(this, `Connection Failed`, error);
|
||||
},
|
||||
],
|
||||
[
|
||||
"disconnect",
|
||||
async (data: Object) =>
|
||||
await $log.error(`Socket error: Connection Lost`, data),
|
||||
],
|
||||
["update usernames", async () => await this.updateUsernames()],
|
||||
[
|
||||
"bulkmessage",
|
||||
async (data: Message[]) => await this.rcvMessageBulk(data),
|
||||
],
|
||||
["alert", async (data) => await this.alert(data)],
|
||||
]);
|
||||
|
||||
sockSetup.forEach((cb, event) => {
|
||||
this._socket.on(event, cb);
|
||||
});
|
||||
}
|
||||
|
||||
get room() {
|
||||
return this.userProfile.page;
|
||||
} /**< Current room */
|
||||
set room(r) {
|
||||
this.userProfile.page = r;
|
||||
$log.info(`Changed to room ${r}`);
|
||||
}
|
||||
|
||||
get doLogging() {
|
||||
return $log.doOutput;
|
||||
} /**< Enable log output */
|
||||
set doLogging(r) {
|
||||
$log.doOutput = r;
|
||||
}
|
||||
|
||||
get socket() {
|
||||
return this._socket;
|
||||
} /**< Deprecated, but allows access to underlying socket */
|
||||
set socket(s) {
|
||||
this._socket = s;
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.socket?.off();
|
||||
this.socket?.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message with current config (this.userProfile)
|
||||
* @param msg Message to be sent. Can be an object: { message, channel, global, showBadge }, or just a string (in which case channel/global use current values)
|
||||
*/
|
||||
sendMessage(msg: OutgoingMessage | string): void {
|
||||
switch (typeof msg) {
|
||||
case "object":
|
||||
this._socket.emit("message", msg);
|
||||
break;
|
||||
case "string":
|
||||
this._socket.emit("message", {
|
||||
message: msg,
|
||||
channel: this.userProfile.page,
|
||||
global: this.global,
|
||||
showBadge: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async sendWhisper(recipient: string, msg: string): Promise<void> {
|
||||
await $http.post(this._whisperEndpoint[0], this._whisperEndpoint[1], {
|
||||
chatToken: this.userProfile.jwt,
|
||||
receiver: recipient,
|
||||
message: msg,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
const isNode = typeof process === "object";
|
||||
|
||||
const $get = (url: string, cb: Function): void => {
|
||||
const req = new XMLHttpRequest();
|
||||
req.onreadystatechange = () => {
|
||||
if (req.readyState === 4 && req.status === 200) {
|
||||
cb(req.responseText);
|
||||
}
|
||||
};
|
||||
req.open("GET", url, true);
|
||||
req.send(null);
|
||||
};
|
||||
|
||||
const $post = (
|
||||
url: string,
|
||||
path: string,
|
||||
data: any,
|
||||
cb: (...args: any[]) => void
|
||||
): void => {
|
||||
const req = new XMLHttpRequest();
|
||||
req.onreadystatechange = () => {
|
||||
if (req.readyState === 4 && req.status === 200) {
|
||||
cb(JSON.parse(req.responseText));
|
||||
}
|
||||
};
|
||||
req.open("POST", url + path);
|
||||
|
||||
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||||
|
||||
req.send(JSON.stringify(data));
|
||||
};
|
||||
|
||||
export default {
|
||||
get: (url: string): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
$get(url, (response) => resolve(response));
|
||||
});
|
||||
},
|
||||
post: (url: string, path: string, data: Object): Promise<any> => {
|
||||
return new Promise((resolve) => {
|
||||
$post(url, path, data, (response) => resolve(response));
|
||||
});
|
||||
},
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from "./api";
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
/**
|
||||
* Represents a logger
|
||||
* @constructor
|
||||
* @param {string} prefix - logger prefix
|
||||
*/
|
||||
|
||||
export default class {
|
||||
public readonly prefix: string;
|
||||
public doOutput: boolean = true;
|
||||
|
||||
/** Creates new logger */
|
||||
constructor( prefix?: string, doOutput?: boolean ) {
|
||||
this.doOutput = doOutput;
|
||||
this.prefix = (prefix ?? '[bitwave.tv API]') + ' ';
|
||||
}
|
||||
|
||||
/** Creates logger info */
|
||||
info( message: String, ...args ): void {
|
||||
this.doOutput && console.log( this.prefix + message, ...args );
|
||||
}
|
||||
|
||||
/** Creates logger warn */
|
||||
warn( message: String, ...args ): void {
|
||||
this.doOutput && console.warn( this.prefix + '[WARN] ' + message, ...args );
|
||||
}
|
||||
|
||||
/** Creates logger error */
|
||||
error( message: String, ...args ): void {
|
||||
this.doOutput && console.error( this.prefix + '[ERROR] ' + message, ...args );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue