Replaced socket code
This commit is contained in:
parent
a9663c8170
commit
4a8a614adb
src/snek/static
@ -7,7 +7,9 @@
|
||||
|
||||
// MIT License
|
||||
|
||||
import { Schedule } from './schedule.js';
|
||||
import {Schedule} from './schedule.js';
|
||||
import {EventHandler} from "./event-handler.js";
|
||||
import {Socket} from "./socket.js";
|
||||
|
||||
export class RESTClient {
|
||||
debug = false;
|
||||
@ -23,7 +25,7 @@ export class RESTClient {
|
||||
});
|
||||
const result = await response.json();
|
||||
if (this.debug) {
|
||||
console.debug({ url, params, result });
|
||||
console.debug({url, params, result});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -39,27 +41,12 @@ export class RESTClient {
|
||||
|
||||
const result = await response.json();
|
||||
if (this.debug) {
|
||||
console.debug({ url, data, result });
|
||||
console.debug({url, data, result});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class EventHandler {
|
||||
constructor() {
|
||||
this.subscribers = {};
|
||||
}
|
||||
|
||||
addEventListener(type, handler) {
|
||||
if (!this.subscribers[type]) this.subscribers[type] = [];
|
||||
this.subscribers[type].push(handler);
|
||||
}
|
||||
|
||||
emit(type, ...data) {
|
||||
if (this.subscribers[type]) this.subscribers[type].forEach(handler => handler(...data));
|
||||
}
|
||||
}
|
||||
|
||||
export class Chat extends EventHandler {
|
||||
constructor() {
|
||||
super();
|
||||
@ -100,7 +87,7 @@ export class Chat extends EventHandler {
|
||||
call(method, ...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const command = { method, args, message_id: this.generateUniqueId() };
|
||||
const command = {method, args, message_id: this.generateUniqueId()};
|
||||
this._promises[command.message_id] = resolve;
|
||||
this._socket.send(JSON.stringify(command));
|
||||
} catch (e) {
|
||||
@ -134,133 +121,6 @@ export class Chat extends EventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
export class Socket extends EventHandler {
|
||||
ws = null;
|
||||
isConnected = null;
|
||||
isConnecting = null;
|
||||
url = null;
|
||||
connectPromises = [];
|
||||
ensureTimer = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.url = window.location.hostname === 'localhost' ? 'ws://localhost:8081/rpc.ws' : 'wss://' + window.location.hostname + '/rpc.ws';
|
||||
this.ensureConnection();
|
||||
}
|
||||
|
||||
_camelToSnake(str) {
|
||||
return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
|
||||
}
|
||||
|
||||
get client() {
|
||||
const me = this;
|
||||
return new Proxy({}, {
|
||||
get(_, prop) {
|
||||
return (...args) => {
|
||||
const functionName = me._camelToSnake(prop);
|
||||
return me.call(functionName, ...args);
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
ensureConnection() {
|
||||
if (this.ensureTimer) {
|
||||
return this.connect();
|
||||
}
|
||||
const me = this;
|
||||
this.ensureTimer = setInterval(() => {
|
||||
if (me.isConnecting) me.isConnecting = false;
|
||||
me.connect();
|
||||
}, 5000);
|
||||
return this.connect();
|
||||
}
|
||||
|
||||
generateUniqueId() {
|
||||
return 'id-' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
connect() {
|
||||
|
||||
const me = this
|
||||
if (this.isConnected || this.isConnecting) {
|
||||
return new Promise((resolve) => {
|
||||
if(me.isConnected)resolve(me)
|
||||
else if(me.isConnecting)
|
||||
me.connectPromises.push(resolve);
|
||||
});
|
||||
}
|
||||
this.isConnecting = true;
|
||||
return new Promise((resolve) => {
|
||||
this.connectPromises.push(resolve);
|
||||
console.debug("Connecting..");
|
||||
|
||||
const ws = new WebSocket(this.url);
|
||||
ws.onopen = () => {
|
||||
this.ws = ws;
|
||||
this.isConnected = true;
|
||||
this.isConnecting = false;
|
||||
ws.onmessage = (event) => {
|
||||
this.onData(JSON.parse(event.data));
|
||||
};
|
||||
ws.onclose = () => {
|
||||
this.onClose();
|
||||
};
|
||||
ws.onerror = () => {
|
||||
this.onClose();
|
||||
};
|
||||
this.onConnect()
|
||||
this.connectPromises.forEach(resolver => resolver(this));
|
||||
};
|
||||
});
|
||||
}
|
||||
onConnect(){
|
||||
this.emit("connected")
|
||||
}
|
||||
onData(data) {
|
||||
if (data.success !== undefined && !data.success) {
|
||||
console.error(data);
|
||||
}
|
||||
if (data.callId) {
|
||||
this.emit(data.callId, data.data);
|
||||
}
|
||||
if (data.channel_uid) {
|
||||
this.emit(data.channel_uid, data.data);
|
||||
this.emit("channel-message", data);
|
||||
}
|
||||
}
|
||||
|
||||
async sendJson(data) {
|
||||
await this.connect().then(api => {
|
||||
api.ws.send(JSON.stringify(data));
|
||||
});
|
||||
}
|
||||
|
||||
async call(method, ...args) {
|
||||
const call = {
|
||||
callId: this.generateUniqueId(),
|
||||
method,
|
||||
args,
|
||||
};
|
||||
const me = this
|
||||
return new Promise((resolve) => {
|
||||
me.addEventListener(call.callId, data => resolve(data));
|
||||
me.sendJson(call);
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
console.info("Connection lost. Reconnecting.");
|
||||
this.isConnected = false;
|
||||
this.isConnecting = false;
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
this.ensureConnection().then(() => {
|
||||
console.info("Reconnected.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class NotificationAudio {
|
||||
constructor(timeout = 500) {
|
||||
this.schedule = new Schedule(timeout);
|
||||
@ -268,9 +128,9 @@ export class NotificationAudio {
|
||||
|
||||
sounds = {
|
||||
"message": "/audio/soundfx.d_beep3.mp3",
|
||||
"mention": "/audio/750607__deadrobotmusic__notification-sound-1.wav",
|
||||
"messageOtherChannel": "/audio/750608__deadrobotmusic__notification-sound-2.wav",
|
||||
"ping": "/audio/750609__deadrobotmusic__notification-sound-3.wav",
|
||||
"mention": "/audio/750607__deadrobotmusic__notification-sound-1.wav",
|
||||
"messageOtherChannel": "/audio/750608__deadrobotmusic__notification-sound-2.wav",
|
||||
"ping": "/audio/750609__deadrobotmusic__notification-sound-3.wav",
|
||||
}
|
||||
|
||||
play(soundIndex = 0) {
|
||||
@ -294,11 +154,12 @@ export class App extends EventHandler {
|
||||
user = {};
|
||||
|
||||
async ping(...args) {
|
||||
if(this.is_pinging)return false
|
||||
if (this.is_pinging) return false
|
||||
this.is_pinging = true
|
||||
await this.rpc.ping(...args);
|
||||
this.is_pinging = false
|
||||
}
|
||||
|
||||
async forcePing(...arg) {
|
||||
await this.rpc.ping(...args);
|
||||
}
|
||||
@ -308,14 +169,14 @@ export class App extends EventHandler {
|
||||
this.ws = new Socket();
|
||||
this.rpc = this.ws.client;
|
||||
this.audio = new NotificationAudio(500);
|
||||
this.is_pinging = false
|
||||
this.ping_interval = setInterval(()=>{
|
||||
this.is_pinging = false
|
||||
this.ping_interval = setInterval(() => {
|
||||
this.ping("active")
|
||||
}, 15000)
|
||||
|
||||
|
||||
const me = this
|
||||
this.ws.addEventListener("connected", (data)=> {
|
||||
|
||||
const me = this
|
||||
this.ws.addEventListener("connected", (data) => {
|
||||
this.ping("online")
|
||||
})
|
||||
this.ws.addEventListener("channel-message", (data) => {
|
||||
@ -330,6 +191,7 @@ export class App extends EventHandler {
|
||||
playSound(index) {
|
||||
this.audio.play(index);
|
||||
}
|
||||
|
||||
timeDescription(isoDate) {
|
||||
const date = new Date(isoDate);
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
@ -337,6 +199,7 @@ export class App extends EventHandler {
|
||||
let timeStr = `${hours}:${minutes}, ${this.timeAgo(new Date(isoDate), Date.now())}`;
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
timeAgo(date1, date2) {
|
||||
const diffMs = Math.abs(date2 - date1);
|
||||
const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
@ -355,9 +218,10 @@ export class App extends EventHandler {
|
||||
}
|
||||
return 'just now';
|
||||
}
|
||||
|
||||
async benchMark(times = 100, message = "Benchmark Message") {
|
||||
const promises = [];
|
||||
const me = this;
|
||||
const me = this;
|
||||
for (let i = 0; i < times; i++) {
|
||||
promises.push(this.rpc.getChannels().then(channels => {
|
||||
channels.forEach(channel => {
|
||||
@ -369,3 +233,4 @@ export class App extends EventHandler {
|
||||
}
|
||||
|
||||
export const app = new App();
|
||||
window.app = app;
|
16
src/snek/static/event-handler.js
Normal file
16
src/snek/static/event-handler.js
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
|
||||
export class EventHandler {
|
||||
constructor() {
|
||||
this.subscribers = {};
|
||||
}
|
||||
|
||||
addEventListener(type, handler) {
|
||||
if (!this.subscribers[type]) this.subscribers[type] = [];
|
||||
this.subscribers[type].push(handler);
|
||||
}
|
||||
|
||||
emit(type, ...data) {
|
||||
if (this.subscribers[type]) this.subscribers[type].forEach(handler => handler(...data));
|
||||
}
|
||||
}
|
137
src/snek/static/socket.js
Normal file
137
src/snek/static/socket.js
Normal file
@ -0,0 +1,137 @@
|
||||
import {EventHandler} from "./event-handler.js";
|
||||
|
||||
export class Socket extends EventHandler {
|
||||
/**
|
||||
* @type {URL}
|
||||
*/
|
||||
url
|
||||
/**
|
||||
* @type {WebSocket|null}
|
||||
*/
|
||||
ws = null
|
||||
|
||||
/**
|
||||
* @type {null|PromiseWithResolvers<Socket>&{resolved?:boolean}}
|
||||
*/
|
||||
connection = null
|
||||
|
||||
shouldReconnect = true;
|
||||
|
||||
get isConnected() {
|
||||
return this.ws && this.ws.readyState === WebSocket.OPEN;
|
||||
}
|
||||
|
||||
get isConnecting() {
|
||||
return this.ws && this.ws.readyState === WebSocket.CONNECTING;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.url = new URL('/rpc.ws', window.location.origin);
|
||||
this.url.protocol = this.url.protocol.replace('http', 'ws');
|
||||
|
||||
this.connect()
|
||||
}
|
||||
|
||||
connect() {
|
||||
if (this.ws) {
|
||||
return this.connection.promise;
|
||||
}
|
||||
|
||||
if (!this.connection || this.connection.resolved) {
|
||||
this.connection = Promise.withResolvers()
|
||||
}
|
||||
|
||||
this.ws = new WebSocket(this.url);
|
||||
this.ws.addEventListener("open", () => {
|
||||
this.connection.resolved = true;
|
||||
this.connection.resolve(this);
|
||||
this.emit("connected");
|
||||
});
|
||||
|
||||
this.ws.addEventListener("close", () => {
|
||||
console.log("Connection closed");
|
||||
this.disconnect()
|
||||
})
|
||||
this.ws.addEventListener("error", (e) => {
|
||||
console.error("Connection error", e);
|
||||
this.disconnect()
|
||||
})
|
||||
this.ws.addEventListener("message", (e) => {
|
||||
if (e.data instanceof Blob || e.data instanceof ArrayBuffer) {
|
||||
console.error("Binary data not supported");
|
||||
} else {
|
||||
try {
|
||||
this.onData(JSON.parse(e.data));
|
||||
} catch (e) {
|
||||
console.error("Failed to parse message", e);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onData(data) {
|
||||
if (data.success !== undefined && !data.success) {
|
||||
console.error(data);
|
||||
}
|
||||
if (data.callId) {
|
||||
this.emit(data.callId, data.data);
|
||||
}
|
||||
if (data.channel_uid) {
|
||||
this.emit(data.channel_uid, data.data);
|
||||
this.emit("channel-message", data);
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.ws?.close();
|
||||
this.ws = null;
|
||||
|
||||
if (this.shouldReconnect) setTimeout(() => {
|
||||
console.log("Reconnecting");
|
||||
return this.connect();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
|
||||
_camelToSnake(str) {
|
||||
return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
|
||||
}
|
||||
|
||||
get client() {
|
||||
const me = this;
|
||||
return new Proxy({}, {
|
||||
get(_, prop) {
|
||||
return (...args) => {
|
||||
const functionName = me._camelToSnake(prop);
|
||||
return me.call(functionName, ...args);
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
generateCallId() {
|
||||
return self.crypto.randomUUID();
|
||||
}
|
||||
|
||||
async sendJson(data) {
|
||||
await this.connect().then(api => {
|
||||
api.ws.send(JSON.stringify(data));
|
||||
});
|
||||
}
|
||||
|
||||
async call(method, ...args) {
|
||||
const call = {
|
||||
callId: this.generateCallId(),
|
||||
method,
|
||||
args,
|
||||
};
|
||||
const me = this
|
||||
return new Promise((resolve) => {
|
||||
me.addEventListener(call.callId, data => resolve(data));
|
||||
me.sendJson(call);
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user