Replaced socket code
This commit is contained in:
parent
a9663c8170
commit
4a8a614adb
src/snek/static
@ -7,7 +7,9 @@
|
|||||||
|
|
||||||
// MIT License
|
// 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 {
|
export class RESTClient {
|
||||||
debug = false;
|
debug = false;
|
||||||
@ -23,7 +25,7 @@ export class RESTClient {
|
|||||||
});
|
});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
console.debug({ url, params, result });
|
console.debug({url, params, result});
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -39,27 +41,12 @@ export class RESTClient {
|
|||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
console.debug({ url, data, result });
|
console.debug({url, data, result});
|
||||||
}
|
}
|
||||||
return 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 {
|
export class Chat extends EventHandler {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -100,7 +87,7 @@ export class Chat extends EventHandler {
|
|||||||
call(method, ...args) {
|
call(method, ...args) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const command = { method, args, message_id: this.generateUniqueId() };
|
const command = {method, args, message_id: this.generateUniqueId()};
|
||||||
this._promises[command.message_id] = resolve;
|
this._promises[command.message_id] = resolve;
|
||||||
this._socket.send(JSON.stringify(command));
|
this._socket.send(JSON.stringify(command));
|
||||||
} catch (e) {
|
} 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 {
|
export class NotificationAudio {
|
||||||
constructor(timeout = 500) {
|
constructor(timeout = 500) {
|
||||||
this.schedule = new Schedule(timeout);
|
this.schedule = new Schedule(timeout);
|
||||||
@ -268,9 +128,9 @@ export class NotificationAudio {
|
|||||||
|
|
||||||
sounds = {
|
sounds = {
|
||||||
"message": "/audio/soundfx.d_beep3.mp3",
|
"message": "/audio/soundfx.d_beep3.mp3",
|
||||||
"mention": "/audio/750607__deadrobotmusic__notification-sound-1.wav",
|
"mention": "/audio/750607__deadrobotmusic__notification-sound-1.wav",
|
||||||
"messageOtherChannel": "/audio/750608__deadrobotmusic__notification-sound-2.wav",
|
"messageOtherChannel": "/audio/750608__deadrobotmusic__notification-sound-2.wav",
|
||||||
"ping": "/audio/750609__deadrobotmusic__notification-sound-3.wav",
|
"ping": "/audio/750609__deadrobotmusic__notification-sound-3.wav",
|
||||||
}
|
}
|
||||||
|
|
||||||
play(soundIndex = 0) {
|
play(soundIndex = 0) {
|
||||||
@ -294,11 +154,12 @@ export class App extends EventHandler {
|
|||||||
user = {};
|
user = {};
|
||||||
|
|
||||||
async ping(...args) {
|
async ping(...args) {
|
||||||
if(this.is_pinging)return false
|
if (this.is_pinging) return false
|
||||||
this.is_pinging = true
|
this.is_pinging = true
|
||||||
await this.rpc.ping(...args);
|
await this.rpc.ping(...args);
|
||||||
this.is_pinging = false
|
this.is_pinging = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async forcePing(...arg) {
|
async forcePing(...arg) {
|
||||||
await this.rpc.ping(...args);
|
await this.rpc.ping(...args);
|
||||||
}
|
}
|
||||||
@ -308,14 +169,14 @@ export class App extends EventHandler {
|
|||||||
this.ws = new Socket();
|
this.ws = new Socket();
|
||||||
this.rpc = this.ws.client;
|
this.rpc = this.ws.client;
|
||||||
this.audio = new NotificationAudio(500);
|
this.audio = new NotificationAudio(500);
|
||||||
this.is_pinging = false
|
this.is_pinging = false
|
||||||
this.ping_interval = setInterval(()=>{
|
this.ping_interval = setInterval(() => {
|
||||||
this.ping("active")
|
this.ping("active")
|
||||||
}, 15000)
|
}, 15000)
|
||||||
|
|
||||||
|
|
||||||
const me = this
|
|
||||||
this.ws.addEventListener("connected", (data)=> {
|
const me = this
|
||||||
|
this.ws.addEventListener("connected", (data) => {
|
||||||
this.ping("online")
|
this.ping("online")
|
||||||
})
|
})
|
||||||
this.ws.addEventListener("channel-message", (data) => {
|
this.ws.addEventListener("channel-message", (data) => {
|
||||||
@ -330,6 +191,7 @@ export class App extends EventHandler {
|
|||||||
playSound(index) {
|
playSound(index) {
|
||||||
this.audio.play(index);
|
this.audio.play(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
timeDescription(isoDate) {
|
timeDescription(isoDate) {
|
||||||
const date = new Date(isoDate);
|
const date = new Date(isoDate);
|
||||||
const hours = String(date.getHours()).padStart(2, "0");
|
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())}`;
|
let timeStr = `${hours}:${minutes}, ${this.timeAgo(new Date(isoDate), Date.now())}`;
|
||||||
return timeStr;
|
return timeStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeAgo(date1, date2) {
|
timeAgo(date1, date2) {
|
||||||
const diffMs = Math.abs(date2 - date1);
|
const diffMs = Math.abs(date2 - date1);
|
||||||
const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||||
@ -355,9 +218,10 @@ export class App extends EventHandler {
|
|||||||
}
|
}
|
||||||
return 'just now';
|
return 'just now';
|
||||||
}
|
}
|
||||||
|
|
||||||
async benchMark(times = 100, message = "Benchmark Message") {
|
async benchMark(times = 100, message = "Benchmark Message") {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const me = this;
|
const me = this;
|
||||||
for (let i = 0; i < times; i++) {
|
for (let i = 0; i < times; i++) {
|
||||||
promises.push(this.rpc.getChannels().then(channels => {
|
promises.push(this.rpc.getChannels().then(channels => {
|
||||||
channels.forEach(channel => {
|
channels.forEach(channel => {
|
||||||
@ -369,3 +233,4 @@ export class App extends EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const app = new App();
|
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