mirror of
https://github.com/stoatchat/javascript-client-sdk.git
synced 2026-07-19 17:13:31 -04:00
refactor: formalise event client options
This commit is contained in:
Generated
+12
@@ -13,7 +13,9 @@ specifiers:
|
||||
eslint-config-prettier: ^8.8.0
|
||||
eslint-plugin-solid: ^0.12.0
|
||||
eslint-plugin-spellcheck: ^0.0.20
|
||||
eventemitter3: ^5.0.0
|
||||
isomorphic-ws: ^5.0.0
|
||||
long: ^5.2.1
|
||||
prettier: ^2.8.7
|
||||
revolt-api: ^0.5.17
|
||||
solid-js: ^1.7.2
|
||||
@@ -26,7 +28,9 @@ specifiers:
|
||||
dependencies:
|
||||
'@solid-primitives/map': 0.4.3_solid-js@1.7.2
|
||||
'@solid-primitives/set': 0.4.3_solid-js@1.7.2
|
||||
eventemitter3: 5.0.0
|
||||
isomorphic-ws: 5.0.0_ws@8.13.0
|
||||
long: 5.2.1
|
||||
revolt-api: 0.5.17
|
||||
solid-js: 1.7.2
|
||||
ulid: 2.3.0
|
||||
@@ -904,6 +908,10 @@ packages:
|
||||
through: 2.3.8
|
||||
dev: true
|
||||
|
||||
/eventemitter3/5.0.0:
|
||||
resolution: {integrity: sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==}
|
||||
dev: false
|
||||
|
||||
/fast-deep-equal/3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
dev: true
|
||||
@@ -1404,6 +1412,10 @@ packages:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: true
|
||||
|
||||
/long/5.2.1:
|
||||
resolution: {integrity: sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==}
|
||||
dev: false
|
||||
|
||||
/lru-cache/6.0.0:
|
||||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
+19
-9
@@ -1,7 +1,7 @@
|
||||
import { Accessor, Setter, createSignal } from "solid-js";
|
||||
|
||||
import EventEmitter from "eventemitter3";
|
||||
import { API, Metadata, RelationshipStatus, Role } from "revolt-api";
|
||||
import { API, Metadata, Role } from "revolt-api";
|
||||
import type { DataLogin, RevoltConfig } from "revolt-api";
|
||||
|
||||
import { Channel, Emoji, Message, Server, ServerMember, User } from "./classes";
|
||||
@@ -87,9 +87,16 @@ export interface ClientOptions {
|
||||
baseURL: string;
|
||||
|
||||
/**
|
||||
* Whether to allow partial objects to emit from events.
|
||||
* Whether to allow partial objects to emit from events
|
||||
* @default false
|
||||
*/
|
||||
partials: boolean;
|
||||
|
||||
/**
|
||||
* Whether to reconnect when disconnected
|
||||
* @default true
|
||||
*/
|
||||
autoReconnect: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +115,7 @@ export class Client extends EventEmitter<Events> {
|
||||
readonly events: EventClient<1>;
|
||||
|
||||
configuration: RevoltConfig | undefined;
|
||||
session: Session | undefined;
|
||||
#session: Session | undefined;
|
||||
user: User | undefined;
|
||||
|
||||
readonly ready: Accessor<boolean>;
|
||||
@@ -123,6 +130,7 @@ export class Client extends EventEmitter<Events> {
|
||||
this.options = {
|
||||
baseURL: "https://api.revolt.chat",
|
||||
partials: false,
|
||||
autoReconnect: true,
|
||||
...options,
|
||||
};
|
||||
|
||||
@@ -154,7 +162,9 @@ export class Client extends EventEmitter<Events> {
|
||||
break;
|
||||
case ConnectionState.Disconnected:
|
||||
this.emit("disconnected");
|
||||
setTimeout(() => this.connect(), 10000);
|
||||
if (this.options.autoReconnect) {
|
||||
setTimeout(() => this.connect(), 500);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -172,7 +182,7 @@ export class Client extends EventEmitter<Events> {
|
||||
this.#setReady(false);
|
||||
this.events.connect(
|
||||
"wss://ws.revolt.chat",
|
||||
typeof this.session === "string" ? this.session : this.session!.token
|
||||
typeof this.#session === "string" ? this.#session : this.#session!.token
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,7 +202,7 @@ export class Client extends EventEmitter<Events> {
|
||||
(this.api as API) = new API({
|
||||
baseURL: this.options.baseURL,
|
||||
authentication: {
|
||||
revolt: this.session,
|
||||
revolt: this.#session,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -206,7 +216,7 @@ export class Client extends EventEmitter<Events> {
|
||||
await this.#fetchConfiguration();
|
||||
const data = await this.api.post("/auth/session/login", details);
|
||||
if (data.result === "Success") {
|
||||
this.session = data;
|
||||
this.#session = data;
|
||||
// TODO: return await this.connect();
|
||||
} else {
|
||||
throw "MFA not implemented!";
|
||||
@@ -220,7 +230,7 @@ export class Client extends EventEmitter<Events> {
|
||||
*/
|
||||
async useExistingSession(session: Session) {
|
||||
await this.#fetchConfiguration();
|
||||
this.session = session;
|
||||
this.#session = session;
|
||||
this.#updateHeaders();
|
||||
this.connect();
|
||||
}
|
||||
@@ -231,7 +241,7 @@ export class Client extends EventEmitter<Events> {
|
||||
*/
|
||||
async loginBot(token: string) {
|
||||
await this.#fetchConfiguration();
|
||||
this.session = token;
|
||||
this.#session = token;
|
||||
this.#updateHeaders();
|
||||
this.connect();
|
||||
}
|
||||
|
||||
+57
-21
@@ -15,6 +15,29 @@ export enum ConnectionState {
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
/**
|
||||
* Event client options object
|
||||
*/
|
||||
export interface EventClientOptions {
|
||||
/**
|
||||
* Whether to log events
|
||||
* @default false
|
||||
*/
|
||||
debug: boolean;
|
||||
|
||||
/**
|
||||
* Time in seconds between Ping packets sent to the server
|
||||
* @default 30
|
||||
*/
|
||||
heartbeatInterval: number;
|
||||
|
||||
/**
|
||||
* Maximum time in seconds between Ping and corresponding Pong
|
||||
* @default 10
|
||||
*/
|
||||
pongTimeout: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Events provided by the client.
|
||||
*/
|
||||
@@ -30,10 +53,13 @@ type Events<T extends AvailableProtocols, P extends EventProtocol<T>> = {
|
||||
export class EventClient<T extends AvailableProtocols> extends EventEmitter<
|
||||
Events<T, EventProtocol<T>>
|
||||
> {
|
||||
readonly options: EventClientOptions;
|
||||
|
||||
#protocolVersion: T;
|
||||
#transportFormat: "json" | "msgpack";
|
||||
#heartbeatInterval: number;
|
||||
#pongTimeout: number;
|
||||
|
||||
readonly ping: Accessor<number>;
|
||||
#setPing: Setter<number>;
|
||||
|
||||
readonly state: Accessor<ConnectionState>;
|
||||
#setStateSetter: Setter<ConnectionState>;
|
||||
@@ -46,26 +72,33 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
|
||||
* Create a new event client.
|
||||
* @param protocolVersion Target protocol version
|
||||
* @param transportFormat Communication format
|
||||
* @param heartbeatInterval Interval in seconds to send ping
|
||||
* @param pongTimeout Time in seconds until heartbeat times out
|
||||
* @param options Configuration options
|
||||
*/
|
||||
constructor(
|
||||
protocolVersion: T,
|
||||
transportFormat: "json" = "json",
|
||||
heartbeatInterval = 30,
|
||||
pongTimeout = 10
|
||||
options?: EventClientOptions
|
||||
) {
|
||||
super();
|
||||
|
||||
this.#protocolVersion = protocolVersion;
|
||||
this.#transportFormat = transportFormat;
|
||||
this.#heartbeatInterval = heartbeatInterval;
|
||||
this.#pongTimeout = pongTimeout;
|
||||
|
||||
this.options = {
|
||||
heartbeatInterval: 30,
|
||||
pongTimeout: 10,
|
||||
debug: false,
|
||||
...options,
|
||||
};
|
||||
|
||||
const [state, setState] = createSignal(ConnectionState.Idle);
|
||||
this.state = state;
|
||||
this.#setStateSetter = setState;
|
||||
|
||||
const [ping, setPing] = createSignal(-1);
|
||||
this.ping = ping;
|
||||
this.#setPing = setPing;
|
||||
|
||||
this.disconnect = this.disconnect.bind(this);
|
||||
}
|
||||
|
||||
@@ -94,14 +127,13 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
|
||||
);
|
||||
|
||||
this.#socket.onopen = () => {
|
||||
this.#heartbeatIntervalReference = setInterval(
|
||||
() =>
|
||||
(this.#pongTimeoutReference = setTimeout(
|
||||
this.disconnect,
|
||||
this.#pongTimeout * 1e3
|
||||
) as never),
|
||||
this.#heartbeatInterval & 1e3
|
||||
) as never;
|
||||
this.#heartbeatIntervalReference = setInterval(() => {
|
||||
this.send({ type: "Ping", data: +new Date() });
|
||||
this.#pongTimeoutReference = setTimeout(
|
||||
() => this.disconnect(),
|
||||
this.options.pongTimeout * 1e3
|
||||
) as never;
|
||||
}, this.options.heartbeatInterval * 1e3) as never;
|
||||
};
|
||||
|
||||
this.#socket.onerror = (error) => {
|
||||
@@ -120,8 +152,6 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
|
||||
this.#socket.onclose = () => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
|
||||
clearInterval(this.#heartbeatIntervalReference);
|
||||
this.disconnect();
|
||||
};
|
||||
}
|
||||
@@ -131,7 +161,8 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
|
||||
*/
|
||||
disconnect() {
|
||||
if (!this.#socket) return;
|
||||
let socket = this.#socket;
|
||||
clearInterval(this.#heartbeatIntervalReference);
|
||||
const socket = this.#socket;
|
||||
this.#socket = undefined;
|
||||
socket.close();
|
||||
this.setState(ConnectionState.Disconnected);
|
||||
@@ -142,7 +173,9 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
|
||||
* @param event Event
|
||||
*/
|
||||
send(event: EventProtocol<T>["client"]) {
|
||||
console.info(event);
|
||||
this.options.debug && console.info("[C->S]", event);
|
||||
if (!this.#socket) throw "Socket closed, trying to send.";
|
||||
this.#socket.send(JSON.stringify(event));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +183,7 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
|
||||
* @param event Event
|
||||
*/
|
||||
handle(event: EventProtocol<T>["server"]) {
|
||||
this.options.debug && console.info("[S->C]", event);
|
||||
switch (event.type) {
|
||||
case "Ping":
|
||||
this.send({
|
||||
@@ -159,6 +193,8 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
|
||||
return;
|
||||
case "Pong":
|
||||
clearTimeout(this.#pongTimeoutReference);
|
||||
this.#setPing(+new Date() - event.data);
|
||||
this.options.debug && console.info(`[ping] ${this.ping()}ms`);
|
||||
return;
|
||||
case "Error":
|
||||
this.emit("error", event as never);
|
||||
@@ -174,7 +210,7 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
|
||||
this.emit("event", event);
|
||||
this.setState(ConnectionState.Connected);
|
||||
} else {
|
||||
console.error("WE ARE IN WRONG STATE");
|
||||
throw `Unreachable code. Received ${event.type} in Connecting state.`;
|
||||
}
|
||||
break;
|
||||
case ConnectionState.Connected:
|
||||
|
||||
+5
-6
@@ -201,7 +201,6 @@ export async function handleEvent(
|
||||
event: ServerMessage,
|
||||
setReady: Setter<boolean>
|
||||
) {
|
||||
console.info("[EVENT]", JSON.stringify(event).substring(0, 64));
|
||||
switch (event.type) {
|
||||
case "Bulk": {
|
||||
for (const item of event.v) {
|
||||
@@ -252,7 +251,7 @@ export async function handleEvent(
|
||||
};
|
||||
|
||||
client.messages.updateUnderlyingObject(event.id, {
|
||||
...(event.data as {}),
|
||||
...(event.data as object),
|
||||
editedAt: new Date(),
|
||||
});
|
||||
|
||||
@@ -760,20 +759,20 @@ export async function handleEvent(
|
||||
break;
|
||||
}
|
||||
case "Auth": {
|
||||
if (typeof client.session === "string") throw "Unreachable code";
|
||||
/*if (typeof client.#session === "string") throw "Unreachable code";
|
||||
|
||||
switch (event.event_type) {
|
||||
case "DeleteSession":
|
||||
if (event.session_id === client.session!._id) {
|
||||
if (event.session_id === client.#session!._id) {
|
||||
// TODO: logout
|
||||
}
|
||||
break;
|
||||
case "DeleteAllSessions":
|
||||
if (event.exclude_session_id !== client.session!._id) {
|
||||
if (event.exclude_session_id !== client.#session!._id) {
|
||||
// TODO: logout
|
||||
}
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
require("dotenv").config();
|
||||
|
||||
const { Client } = require(".");
|
||||
|
||||
const client = new Client();
|
||||
|
||||
client.on("ready", () => console.info(`Logged in as ${client.user.username}!`));
|
||||
client.on("disconnected", () => console.info("Disconnected."));
|
||||
|
||||
client.on("messageCreate", (message) => console.info(message.content));
|
||||
|
||||
client.loginBot(process.env.TOKEN);
|
||||
Reference in New Issue
Block a user