+ New cache ipc handlers for adding server caches

Interface for this is fiddly and probably requires real docs to be further developed
without losing my mind doc the whole cache at some point
+ New cache ipc handlers for adding a new open web socket
Web socket comes with some basic listeners, however very litte/nothing
is being done check the health of these connections or to try when possible.

+ Cache now adds actual message objects to its message arrays instead of raw strings (wew)
+ Events module has been added to move the parsing logic/validation away from everything else

+ The basic Event structure has some niftier-than-you-think behavior for data acccess which the cache can leverage for more concise+ correct behavior
This commit is contained in:
shockrah 2021-04-14 22:51:35 -07:00
parent 01c245cbba
commit 40351f934e
3 changed files with 158 additions and 17 deletions

View File

@ -2,6 +2,8 @@ const { ipcMain } = require('electron')
const { app, BrowserWindow } = require('electron') const { app, BrowserWindow } = require('electron')
const { ArgumentParser } = require('argparse') const { ArgumentParser } = require('argparse')
const path = require('path') const path = require('path')
const URL = require('url')
const WebSocket = require('ws')
const { Config, from_cli_parser } = require('./src/config.js') const { Config, from_cli_parser } = require('./src/config.js')
const { Cache } = require('./src/cache') const { Cache } = require('./src/cache')
@ -18,6 +20,8 @@ const arguments = (() => {
let win let win
let config = from_cli_parser(arguments) let config = from_cli_parser(arguments)
let cache = new Cache() let cache = new Cache()
let wsclient = null
// NOTE: this line is only here for demonstration as the current make script exposes this var // NOTE: this line is only here for demonstration as the current make script exposes this var
// so that we can test against self signed certificates but still use ssl // so that we can test against self signed certificates but still use ssl
@ -72,3 +76,54 @@ ipcMain.handle('config-update', async function(event, updated_config) {
await config.write_disk() await config.write_disk()
}) })
ipcMain.handle('open-socket', async function(event, kwargs ) {
const server_conf = kwargs['conf']
const conn_url = kwargs['url']
const cache_target = server_conf['wsurl']
// shutdown the websocket incase there is already one running
if(wsclient != null) {
console.log("Live websocket found, closing for replacement")
wsclient.close()
wsclient = null
}
wsclient = new WebSocket(conn_url)
// Setting some listeners for the websocket
wsclient.on('open', function() {
cache.set_active(cache_target)
})
wsclient.on('close', function(code, reason) {
console.log("Websocket close event: ",code, reason)
})
wsclient.on('error', function(error) {
console.log('Websocket runtime error: ', error)
})
wsclient.on('message', function(message) {
try {
cache.new_message(cache_target, message)
} catch (err) {
console.log(err)
}
})
})
ipcMain.handle('cache-new-server', function(event, kwargs) {
const serv_config = kwargs['cfg']
const channels = kwargs['channels']
const messages = kwargs['messages']
// optional flag which tells the cache to set this as the active server
// NOTE: Must also be the wsurl
const active_target = kwargs['active_target']
cache.add_server(active_target, serv_config, channels, messages)
if(active_target) {
if(active_target != serv_config['wsurl']) {
throw 'Active target must be the same as the target\'s web-socket URL'
}
cache.set_active(active_target)
}
})

View File

@ -1,14 +1,18 @@
import * as EventEmitter from 'events'; // unused for now but maybe useful later
import { ServerConfig, Message, Channel } from './types'; import { ServerConfig, Message, Channel } from './types';
import { Event } from './events';
class ServerCache { class ServerCache {
channels: Array<Channel> channels: Array<Channel>
// Channel id's are used to key into the list of messages for that given channel // Channel-id => Array<Message>
messages: Map<Number, Array<Message>> messages: Map<BigInt, Array<Message>>
config: ServerConfig config: ServerConfig
constructor(config: ServerConfig, channels: Array<Channel>, messages: Array<Message>) { constructor(config: ServerConfig, channels: Array<Channel>, messages: Array<Message>) {
this.config = config this.config = config
this.channels = channels this.channels = channels
this.messages = new Map()
for(const message of messages) { for(const message of messages) {
const ref = this.messages.get(message.cid) const ref = this.messages.get(message.cid)
@ -18,45 +22,45 @@ class ServerCache {
ref.push(message) ref.push(message)
} }
} }
console.log('initial message cache: ', this.messages)
} }
push_message(message: Message) { push_message(message: Message) {
let ref = this.messages.get(message.cid) let ref = this.messages.get(message.cid)
if(ref) { if(ref) {
ref.push(message) ref.push(message) // NOTE: this one here?
console.log('pushed')
} else { } else {
this.messages.set(message.cid, [message]) this.messages.set(message.cid, [message])
console.log('set')
} }
console.log('message cache state: ', this.messages.get(message.cid))
console.log('message: ', message)
} }
} }
export class Cache { export class Cache extends EventEmitter {
// hostname -> Cache // hostname -> Cache
servers: Map<String, ServerCache> private servers: Map<String, ServerCache>
public active_host: String
constructor() { constructor() {
super()
this.servers = new Map() this.servers = new Map()
this.active_host = null
} }
add_server(hostname: String, servercfg: ServerConfig, channels: Array<Channel>, messages: Array<Message>) { add_server(url: String, servercfg: ServerConfig, channels: Array<Channel>, messages: Array<Message>) {
let new_entry = new ServerCache(servercfg, channels, messages) let new_entry = new ServerCache(servercfg, channels, messages)
this.servers.set(servercfg.url, new_entry) this.servers.set(url, new_entry)
} console.log(this.servers)
add_message(hostname: String, message: Message) {
let ref = this.servers.get(hostname)
if(!ref) {
throw `No server config found for ${hostname}`
}
ref.push_message(message)
} }
update_channels(hostname: String, channels: Array<Channel>) { update_channels(hostname: String, channels: Array<Channel>) {
this.servers.get(hostname).channels = channels this.servers.get(hostname).channels = channels
} }
last_id(hostname: String, channel_id: Number) : Number|null { last_id(hostname: String, channel_id: BigInt) : BigInt|null {
try { try {
let ref = this.servers.get(hostname).messages.get(channel_id) let ref = this.servers.get(hostname).messages.get(channel_id)
return ref[ref.length - 1].cid return ref[ref.length - 1].cid
@ -64,5 +68,24 @@ export class Cache {
return null return null
} }
} }
new_message(origin: String, message: string): void {
// we assume at this point that we have the hostname contained smoewhere
let ref = this.servers.get(origin)
if(ref) {
let event = new Event(message)
ref.push_message(event.message)
} else {
console.error(`No server with ${origin} as hostname`)
console.error('Active host: ', this.active_host)
console.error('Servers: ', this.servers)
}
} }
set_active(url: String) {
this.active_host = url
}
}

View File

@ -0,0 +1,63 @@
import * as JSONBig from 'json-bigint';
import { Channel, Message } from './types';
export class Event {
public type: string
public message: Message|null
// NOTE: these two fields may benefit from have a more legible property wrapping
// them, cost would be pretty minimal(probably but idrk)
public d_channel: Channel|null
public c_channel: Channel|null
constructor(raw: string) {
const parsed = JSONBig.parse(raw)
this.type = parsed['type']
switch(this.type) {
case 'new-message' : {
this.message = this._message(parsed[this.type])
this.d_channel = null
this.c_channel = null
break
}
case 'delete-channel' : {
this.d_channel = null
// todo
break
}
case 'create-channel' : {
this.c_channel = null
// todo
break
}
default: {
this.message = null
this.d_channel = null
this.c_channel = null
}
}
}
// Attempts to setup a new Message object in the message field
private _message(obj: Object): Message|null {
// NOTE: this takes data from the network directly so its important
// to keep in mind the "user-friendly" field names
let uid = obj['author_id']
let cid = obj['channel_id']
let content = obj['content']
let content_type = obj['content_type']
let mid = obj['id']
let uname = obj['name']
let time = obj['time']
return new Message(mid, time, content, content_type, uid, uname, cid)
}
// Sets up the inner channel data
private _channel(obj: Object) : Channel|null {
// todo: still require some testing to verify event data is appropriate
return null
}
}