mirror of
https://github.com/Rolands-Laucis/Socio.git
synced 2026-05-15 14:15:57 -06:00
hero page start
This commit is contained in:
parent
15127e8aa3
commit
2ab82b60b2
22 changed files with 5897 additions and 126 deletions
|
|
@ -1,16 +1,15 @@
|
|||
# Socio.js
|
||||
# Socio.js - WebSocket live/reactive API paradigm with Client-Side SQL queries. (under active early development)
|
||||
|
||||
## Connect frontend to backend DB reactively! (under active early development)
|
||||
## Connecting frontend to backend DB reactively!
|
||||
|
||||
Say goodbye to REST APIs. No more API middleware and DB interfacing functions and wrappers and handlers. Write your SQL queries on the frontend and have their results be automagically refreshed on all clients when a resource is changed on the server DB.
|
||||
|
||||
Check [basic demo](./demos/basic/README.md) to try an interactive bare-bones demonstration.
|
||||
Check [framework demo](./demos/framework/README.md) to try an interactive demonstration on a Svelte-Vite app!
|
||||
Check [secure framework demo](./demos/framework/README.md) to try an interactive demonstration on a Svelte-Vite app!
|
||||
|
||||
Comes with a class for auto securing the SQL, so no worries about injections. And even a simple Vite plugin that wraps it 🥳
|
||||
|
||||
## TODOs 📝
|
||||
* Backend Life-cycle hooks
|
||||
* Session ID sync with backend webserver sessions
|
||||
* Keyed SQL queries
|
||||
* Better SQL dependency distinguisher on queries
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
# Socio.js
|
||||
# Socio.js - WebSocket live/reactive API paradigm with Client-Side SQL queries. (under active early development)
|
||||
|
||||
## Connect frontend to backend DB reactively! (under active early development)
|
||||
## Connecting frontend to backend DB reactively!
|
||||
|
||||
Say goodbye to REST APIs. No more API middleware and DB interfacing functions and wrappers and handlers. Write your SQL queries on the frontend and have their results be automagically refreshed on all clients when a resource is changed on the server DB.
|
||||
|
||||
Check [basic demo](./demos/basic/README.md) to try an interactive bare-bones demonstration.
|
||||
Check [framework demo](./demos/framework/README.md) to try an interactive demonstration on a Svelte-Vite app!
|
||||
Check [secure framework demo](./demos/framework/README.md) to try an interactive demonstration on a Svelte-Vite app!
|
||||
|
||||
Comes with a class for auto securing the SQL, so no worries about injections. And even a simple Vite plugin that wraps it 🥳
|
||||
|
||||
## TODOs 📝
|
||||
* Backend Life-cycle hooks
|
||||
* Session ID sync with backend webserver sessions
|
||||
* Keyed SQL queries
|
||||
* Better SQL dependency distinguisher on queries
|
||||
|
|
|
|||
|
|
@ -16,19 +16,20 @@ export class WSClient {
|
|||
// private:
|
||||
#queries = {} //id:[callback]
|
||||
#is_ready = false
|
||||
#verbose = false
|
||||
#ws=null
|
||||
#ses_id = null
|
||||
static #key = 0 //all instances will share this number, such that they are always kept unique. Tho each of these clients would make a different session on the backend, but still
|
||||
|
||||
constructor(url, {name = '', verbose=false, keep_alive=true, reconnect_tries=1, push_callback=null} = {}) {
|
||||
if (window || undefined && url.startsWith('ws://'))
|
||||
info('UNSECURE WEBSOCKET URL CONNECTION! Please use wss:// and https:// protocols in production to protect against man-in-the-middle attacks.')
|
||||
|
||||
//public:
|
||||
this.name = name
|
||||
this.#verbose = verbose
|
||||
this.push = push_callback
|
||||
// this.push = push_callback
|
||||
this.verbose = verbose
|
||||
|
||||
this.#connect(url, keep_alive, verbose, reconnect_tries)
|
||||
|
||||
this.#ws.addEventListener('message', this.#message.bind(this));
|
||||
}
|
||||
|
||||
|
|
@ -36,50 +37,59 @@ export class WSClient {
|
|||
this.#ws = new WebSocket(url)
|
||||
if (keep_alive && reconnect_tries)
|
||||
this.#ws.addEventListener("close", () => {
|
||||
if (this.#verbose) soft_error(`WebSocket closed. Retrying...`, this.name);
|
||||
if (this.verbose) soft_error(`WebSocket closed. Retrying...`, this.name);
|
||||
this.#connect(url, keep_alive, verbose, reconnect_tries - 1)
|
||||
}); // <- rise from your grave!
|
||||
}
|
||||
|
||||
#message(e) {
|
||||
const [kind, data] = JSON.parse(e.data)
|
||||
if (this.#verbose) info('recv:',kind, data)
|
||||
const { kind, data } = JSON.parse(e.data)
|
||||
if (this.verbose) info('recv:',kind, data)
|
||||
|
||||
switch(kind){
|
||||
case 'CON': this.ses_id = data; this.#is_ready = true; if (this.#verbose) done(`WebSocket connected.`, this.name); break;
|
||||
case 'CON':
|
||||
this.#ses_id = data;
|
||||
this.#is_ready();
|
||||
if (this.verbose) done(`WebSocket connected.`, this.name);
|
||||
delete this.#is_ready; //clear memory
|
||||
break;
|
||||
case 'UPD':
|
||||
if (data.id in this.#queries)
|
||||
if (this.#FindID(kind, data?.id))
|
||||
this.#queries[data.id].f.forEach(f => f(data.result));
|
||||
else if (this.#verbose) soft_error(`${kind} message for unregistered SQL query! [${data.id}] with data:`, data)
|
||||
break;
|
||||
case 'SQL':
|
||||
if (data.id in this.#queries)
|
||||
if (this.#FindID(kind, data?.id)){
|
||||
this.#queries[data.id](data.result);
|
||||
else if (this.#verbose) soft_error(`${kind} message for unregistered SQL query! [${data.id}] with data:`, data)
|
||||
delete this.#queries[data.id] //clear memory
|
||||
}
|
||||
break;
|
||||
case 'PONG': if (this.verbose) info('pong', data?.id); break;
|
||||
case 'AUTH':
|
||||
if (this.#FindID(kind, data?.id)) {
|
||||
if (data?.result === false) { if (this.verbose) soft_error(`AUTH returned FALSE, which means websocket has not authenticated.`); }
|
||||
else this.socio_auth_token = data.result
|
||||
this.#queries[data.id](data?.result);
|
||||
delete this.#queries[data.id] //clear memory
|
||||
}
|
||||
break;
|
||||
case 'PONG': if (this.#verbose) info('pong', data?.id); break;
|
||||
// case 'PUSH': this.push(data); break;
|
||||
// case '': break;
|
||||
default: info(`Unrecognized message kind! [${kind}] with data:`, data);
|
||||
}
|
||||
}
|
||||
|
||||
ready(){
|
||||
//idk a better solution. Checks every n ms if the ready flag is set
|
||||
return new Promise((res) => {
|
||||
const timer = setInterval(() => {
|
||||
if(this.#is_ready){
|
||||
clearInterval(timer)
|
||||
res()
|
||||
}
|
||||
}, 50)
|
||||
})
|
||||
#FindID(kind, id){
|
||||
if (id in this.#queries) return true
|
||||
else if (this.verbose) soft_error(`${kind} message for unregistered SQL query! [${id}] with data:`, data)
|
||||
return false
|
||||
}
|
||||
|
||||
ready(){return new Promise(res => this.#is_ready = res)}
|
||||
|
||||
//private method
|
||||
#send(data=[]){
|
||||
this.#ws.send(JSON.stringify([this.ses_id, ...data]))
|
||||
if (this.#verbose) info('sent:', ...data)
|
||||
#send(data={}){
|
||||
this.#ws.send(JSON.stringify({ client_id: this.#ses_id, ...data }))
|
||||
if (this.verbose) info('sent:', ...data)
|
||||
}
|
||||
|
||||
//subscribe to an sql query. Can add multiple callbacks where ever in your code, if their sql queries are identical
|
||||
|
|
@ -91,9 +101,8 @@ export class WSClient {
|
|||
else{
|
||||
const id = this.#gen_key
|
||||
this.#queries[id] = { sql: sql, f: [t ? callback.bind(t) : callback] }
|
||||
this.#send(['REG', { id: id, sql: sql, params: params }])
|
||||
this.#send({ kind: 'REG', data:{ id: id, sql: sql, params: params }})
|
||||
}
|
||||
// info('Registered', sql)
|
||||
}
|
||||
|
||||
async query(sql='', params=null){
|
||||
|
|
@ -103,13 +112,23 @@ export class WSClient {
|
|||
this.#queries[id] = res
|
||||
})
|
||||
//send off the request, which will be resolved in the message handler
|
||||
this.#send(['SQL', { id:id, sql: sql, params: params }])
|
||||
this.#send({kind:'SQL', data:{ id:id, sql: sql, params: params }})
|
||||
return await prom
|
||||
}
|
||||
|
||||
//sends a ping with either the user provided number or an auto generated number, for keeping track of packets and debugging
|
||||
ping(num=0){
|
||||
this.#send(['PING', { id: num || this.#gen_key }])
|
||||
this.#send({kind:'PING', data:{ id: num || this.#gen_key }})
|
||||
}
|
||||
|
||||
async authenticate(params={}){ //params here can be anything, like username and password stuff etc. The backend server auth function callback will receive this entire object
|
||||
//set up a promise which resolve function is in the queries data structure, such that in the message handler it can be called, therefor the promise resolved, therefor awaited and return from this function
|
||||
const id = this.#gen_key;
|
||||
const prom = new Promise((res) => {
|
||||
this.#queries[id] = res
|
||||
})
|
||||
this.#send({kind:'AUTH', data:{ id:id, params: params}})
|
||||
return await prom
|
||||
}
|
||||
|
||||
//generates a unique key either via static counter or user provided key gen func
|
||||
|
|
|
|||
239
core/core.js
239
core/core.js
|
|
@ -1,153 +1,218 @@
|
|||
//Nullum magnum ingenium sine mixture dementia fuit. - There has been no great wisdom without an element of madness.
|
||||
|
||||
import { log, error, soft_error, info, setPrefix, setShowTime} from '@rolands/log'
|
||||
import { log, soft_error, info, setPrefix, setShowTime } from '@rolands/log'; setPrefix('Socio'); setShowTime(false); //for my logger
|
||||
import { UUID } from './secure.js';
|
||||
import { WebSocketServer } from 'ws'; //https://github.com/websockets/ws https://github.com/websockets/ws/blob/master/doc/ws.md
|
||||
//https://stackoverflow.com/questions/16280747/sending-message-to-a-specific-connected-users-using-websocket
|
||||
|
||||
//NB! some fields in these variables are private for safety reasons, but also bcs u shouldnt be altering them, only if through my defined ways. They are mostly expected to be constants.
|
||||
//whereas public variables are free for you to alter freely at any time during runtime.
|
||||
|
||||
export class SessionManager{
|
||||
// private:
|
||||
#wss=null
|
||||
#sessions = {}//client_id:websocket
|
||||
#secure=null
|
||||
#lifecycleHooks = { con: null, discon: null, msg: null, upd: null }
|
||||
|
||||
constructor(opts = {}, DB_query_function = null, { secure =null, verbose = false } = {}){
|
||||
setPrefix('Socio'); setShowTime(false); //for my logger
|
||||
#sessions = {}//client_id:Session
|
||||
#secure=null //if constructor is given a SocioSecure object, then that will be used to decrypt all incomming messages
|
||||
#lifecycle_hooks = { con: null, discon: null, msg: null, upd: null, auth: null, gen_client_id:null } //call the register function to hook on these. They will be called if they exist
|
||||
|
||||
//public:
|
||||
log_handlers = { error: null, info:null} //register your logger functions here. By default like this it will log to console, if verbose.
|
||||
|
||||
constructor(opts = {}, DB_query_function = null, { secure =null, verbose = false, hard_crash=false } = {}){
|
||||
//private:
|
||||
this.#wss = new WebSocketServer(opts); //take a look at the WebSocketServer docs - the opts can have a server param, that can be your http server
|
||||
this.#secure = secure
|
||||
|
||||
//public:
|
||||
this.Query = DB_query_function
|
||||
this.verbose = verbose
|
||||
this.#secure = secure
|
||||
this.hard_crash = hard_crash
|
||||
|
||||
this.#wss.on('connection', this.#Connect.bind(this)); //https://thenewstack.io/mastering-javascript-callbacks-bind-apply-call/ have to bind 'this' to the function, otherwise it will use the .on()'s 'this', so that this.[prop] are not undefined
|
||||
this.#wss.on('close', (...stuff) => { info('WebSocketServer close event', ...stuff) });
|
||||
this.#wss.on('error', (...stuff) => { error('WebSocketServer error event', ...stuff)});
|
||||
this.#wss.on('error', (...stuff) => { this.#HandleError('WebSocketServer error event', ...stuff)});
|
||||
}
|
||||
|
||||
#HandleError(e){
|
||||
if (this.hard_crash) throw e
|
||||
if (this.#log_handlers.error) this.#log_handlers.error(e)
|
||||
else if (this.verbose) soft_error(e)
|
||||
}
|
||||
|
||||
#HandleInfo(...args){
|
||||
if (this.#log_handlers.info) this.#log_handlers.info(...args)
|
||||
else if (this.verbose) info(...args)
|
||||
}
|
||||
|
||||
#Connect(conn, req){
|
||||
//construct the new session with a unique ID
|
||||
const client_id = UUID()
|
||||
this.#sessions[client_id] = new Session(client_id, conn, this.verbose)
|
||||
try{
|
||||
//construct the new session with a unique client ID
|
||||
const client_id = this.#lifecycle_hooks.gen_client_id ? this.#lifecycle_hooks.gen_client_id() : UUID()
|
||||
this.#sessions[client_id] = new Session(client_id, conn, this.verbose)
|
||||
|
||||
//pass the object to the connection hook, if it exists
|
||||
if (this.#lifecycleHooks.con)
|
||||
this.#lifecycleHooks.con(this.#sessions[client_id])
|
||||
//pass the object to the connection hook, if it exists
|
||||
if (this.#lifecycle_hooks.con) //here you are free to set a session ID as Session.ses_id. Like whatever your web server generates. Then use the ClientIDsOfSession(ses_id) to get the web socket clients using that backend web server session
|
||||
this.#lifecycle_hooks.con(this.#sessions[client_id], req)
|
||||
|
||||
//notify the client of their ID
|
||||
conn.send(JSON.stringify(['CON', client_id]));
|
||||
if (this.verbose) info('CON', client_id)
|
||||
//notify the client of their ID
|
||||
conn.send(JSON.stringify(['CON', client_id]));
|
||||
this.#HandleInfo('CON', client_id)
|
||||
|
||||
//set this client websockets event handlers
|
||||
conn.on('message', this.#Message.bind(this));
|
||||
conn.on('close', () => {
|
||||
//trigger hook
|
||||
if (this.#lifecycleHooks.discon)
|
||||
this.#lifecycleHooks.discon(this.#sessions[client_id])
|
||||
//set this client websockets event handlers
|
||||
conn.on('message', this.#Message.bind(this));
|
||||
conn.on('close', () => {
|
||||
//trigger hook
|
||||
if (this.#lifecycle_hooks.discon)
|
||||
this.#lifecycle_hooks.discon(this.#sessions[client_id])
|
||||
|
||||
//delete the connection object
|
||||
delete this.#sessions[client_id]
|
||||
if (this.verbose) info('DISCON', client_id)
|
||||
});
|
||||
//delete the connection object
|
||||
delete this.#sessions[client_id]
|
||||
this.#HandleInfo('DISCON', client_id)
|
||||
});
|
||||
}catch(e){this.#HandleError(e)}
|
||||
}
|
||||
|
||||
async #Message(req, head){
|
||||
const [client_id, kind, data] = JSON.parse(req.toString())
|
||||
if (this.#secure && data?.sql) data.sql = this.#secure.DecryptString(data.sql) //if this is supposed to be secure and sql was received, then decrypt it before continuing
|
||||
if (this.verbose) info(`received [${kind}] from [${client_id}]`, data);
|
||||
|
||||
switch (kind) {
|
||||
case 'REG':
|
||||
if(client_id in this.#sessions)
|
||||
this.#sessions[client_id].Send(['UPD', { id: data.id, result: await this.Query({ id: data.id, ses_id: this.#sessions[client_id].ses_id, query: data.sql, params: data.params }) }])
|
||||
|
||||
//set up hook
|
||||
if (QueryUtils.QueryIsSelect(data.sql))
|
||||
QueryUtils.ParseSQLForTables(data.sql).forEach(t => this.#sessions[client_id].RegisterHook(t, data.id, data.sql, data.params));
|
||||
|
||||
break;
|
||||
case 'SQL':
|
||||
const is_select = QueryUtils.QueryIsSelect(data.sql)
|
||||
if (client_id in this.#sessions){
|
||||
const res = this.Query({ id: data.id, ses_id: this.#sessions[client_id].ses_id, query: data.sql, params: data.params })
|
||||
if (is_select) //wait for result
|
||||
this.#sessions[client_id].Send(['SQL', { id: data.id, result: await res }])
|
||||
try{
|
||||
const { client_id, kind, data } = JSON.parse(req.toString())
|
||||
if (this.#secure && data?.sql) {//if this is supposed to be secure and sql was received, then decrypt it before continuing
|
||||
data.sql = this.#secure.DecryptString(data.sql)
|
||||
if (!/--socio;?$/mi.test(data.sql)){ //secured sql queries must end with the marker, to validate that they havent been tampered with and are not giberish.
|
||||
this.#HandleError('Decrypted sql string does not end with the --socio marker, therefor is invalid.', client_id, kind, data)
|
||||
return
|
||||
}
|
||||
|
||||
//if the sql wasnt a SELECT, but altered some resource, then need to propogate that to other connection hooks
|
||||
if (!is_select)
|
||||
this.Update(QueryUtils.ParseSQLForTables(data.sql))
|
||||
|
||||
break;
|
||||
case 'PING': this.#sessions[client_id].Send(['PONG', { id: data?.id}]); break;
|
||||
// case '': break;
|
||||
default: if (this.verbose) error(`Unrecognized message kind! [${kind}] with data:`, data);
|
||||
}
|
||||
}
|
||||
this.#HandleInfo(`received [${kind}] from [${client_id}]`, data);
|
||||
|
||||
if (this.#lifecycle_hooks.msg) {
|
||||
this.#lifecycle_hooks.msg(client_id, kind, data)
|
||||
return
|
||||
}
|
||||
|
||||
switch (kind) {
|
||||
case 'REG':
|
||||
if (client_id in this.#sessions)
|
||||
this.#sessions[client_id].Send({
|
||||
kind:'UPD',
|
||||
data:{
|
||||
id: data.id,
|
||||
result: await this.Query({
|
||||
...data,
|
||||
ses_id: this.#sessions[client_id].ses_id
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
//set up hook
|
||||
if (QueryUtils.QueryIsSelect(data.sql))
|
||||
QueryUtils.ParseSQLForTables(data.sql).forEach(t => this.#sessions[client_id].RegisterHook(t, data.id, data.sql, data.params));
|
||||
|
||||
break;
|
||||
case 'SQL':
|
||||
const is_select = QueryUtils.QueryIsSelect(data.sql)
|
||||
if (client_id in this.#sessions) {
|
||||
//have to do the query in every case
|
||||
const res = this.Query({ ...data, ses_id: this.#sessions[client_id].ses_id })
|
||||
if (is_select) //wait for result, if a result is expected, and send it back
|
||||
this.#sessions[client_id].Send({ kind:'SQL', data:{ id: data.id, result: await res } })
|
||||
}
|
||||
|
||||
//if the sql wasnt a SELECT, but altered some resource, then need to propogate that to other connection hooks
|
||||
if (!is_select)
|
||||
this.Update(QueryUtils.ParseSQLForTables(data.sql))
|
||||
|
||||
break;
|
||||
case 'PING': this.#sessions[client_id].Send({kind:'PONG', data:{ id: data?.id }}); break;
|
||||
case 'AUTH':
|
||||
if (this.#lifecycle_hooks.auth)
|
||||
this.#sessions[client_id].Send({ kind:'AUTH', data:{ id: data.id, result: await this.#lifecycle_hooks.auth(client_id, data.params) } })
|
||||
else
|
||||
this.#sessions[client_id].Send({ kind:'AUTH', data:{ id: data.id, result: false } })
|
||||
break;
|
||||
// case '': break;
|
||||
default: throw (`Unrecognized message kind! [${kind}] with data:`, data);
|
||||
}
|
||||
} catch (e) { this.#HandleError(e) }
|
||||
}
|
||||
|
||||
//OPTIMIZATION dont await the query, but queue up all of them on another thread then await and send there
|
||||
async Update(tables=[]){
|
||||
// if (this.#lifecycleHooks.update) this.#lifecycleHooks.update.forEach(f => f(tables)) //call all the lifecycle hooks
|
||||
Object.values(this.#sessions).forEach(async (s) => {
|
||||
tables.forEach(async (t) => {
|
||||
if (s.hook_tables.includes(t)){
|
||||
for await (const hook of s.GetHookObjs(t)) {
|
||||
s.Send(['UPD', { id: hook.id, result: (await this.Query({id:hook.id, ses_id:s.ses_id, query: hook.sql, params:hook.params})) }])
|
||||
if (this.#lifecycle_hooks.upd) {
|
||||
this.#lifecycle_hooks.upd(tables)
|
||||
return
|
||||
}
|
||||
|
||||
try{
|
||||
Object.values(this.#sessions).forEach(async (s) => {
|
||||
tables.forEach(async (t) => {
|
||||
if (s.hook_tables.includes(t)) {
|
||||
for await (const hook of s.GetHookObjs(t)) {
|
||||
s.Send(['UPD', { id: hook.id, result: (await this.Query({ id: hook.id, ses_id: s.ses_id, query: hook.sql, params: hook.params })) }])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
} catch (e) { this.#HandleError(e) }
|
||||
}
|
||||
|
||||
//when the server wants to send some data to a specific session client - can be any raw data
|
||||
SendTo(client_id='', data={}){
|
||||
if (client_id in this.#sessions)
|
||||
this.#sessions[client_id].Send(['PUSH', data])
|
||||
else soft_error(`The provided session ID [${client_id}] was not found in the tracked web socket connections!`)
|
||||
try{
|
||||
if (client_id in this.#sessions)
|
||||
this.#sessions[client_id].Send({ kind:'PUSH', data:data })
|
||||
else throw `The provided session ID [${client_id}] was not found in the tracked web socket connections!`
|
||||
} catch (e) { this.#HandleError(e) }
|
||||
}
|
||||
|
||||
Emit(data=[]){
|
||||
this.#wss.emit(JSON.stringify(['EMIT', ...data]));
|
||||
Emit(data={}){
|
||||
this.#wss.emit(JSON.stringify({ kind:'EMIT', data:data }));
|
||||
}
|
||||
|
||||
RegisterLifecycleHookHandler(name='', handler=null){
|
||||
if(name in this.#lifecycleHooks)
|
||||
this.#lifecycleHooks[name] = handler
|
||||
else
|
||||
error(`Lifecycle hook [${name}] does not exist!`)
|
||||
try{
|
||||
if (name in this.#lifecycle_hooks)
|
||||
this.#lifecycle_hooks[name] = handler
|
||||
else throw `Lifecycle hook [${name}] does not exist!`
|
||||
} catch (e) { this.#HandleError(e) }
|
||||
}
|
||||
|
||||
UnRegisterLifecycleHookHandler(name = '') {
|
||||
if (name in this.#lifecycleHooks)
|
||||
delete this.#lifecycleHooks[name]
|
||||
else
|
||||
error(`Lifecycle hook [${name}] does not exist!`)
|
||||
try{
|
||||
if (name in this.#lifecycle_hooks)
|
||||
delete this.#lifecycle_hooks[name]
|
||||
else throw `Lifecycle hook [${name}] does not exist!`
|
||||
} catch (e) { this.#HandleError(e) }
|
||||
}
|
||||
|
||||
get LifecycleHookNames(){
|
||||
return Object.keys(this.#lifecycleHooks)
|
||||
return Object.keys(this.#lifecycle_hooks)
|
||||
}
|
||||
|
||||
GetClientSession(client_id=''){
|
||||
return this.#sessions[client_id]
|
||||
return this.#sessions[client_id] || null
|
||||
}
|
||||
|
||||
ClientIDsOfSession(ses_id = ''){
|
||||
return this.#sessions.filter(s => s.ses_id === ses_id).map(s => s.id)
|
||||
return this.#sessions?.filter(s => s.ses_id === ses_id)?.map(s => s.id) || []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Homo vitae commodatus non donatus est. - Man's life is lent, not given. /Syrus/
|
||||
class Session{
|
||||
//private:
|
||||
#id = null //unique ID for this session for my own purposes
|
||||
#ws=null
|
||||
#hooks=[]
|
||||
|
||||
constructor(client_id='', browser_ws_conn=null, verbose=false){
|
||||
this.id = client_id
|
||||
this.ses_id = null
|
||||
constructor(client_id = '', browser_ws_conn = null, verbose = false){
|
||||
//private:
|
||||
this.#id = client_id
|
||||
this.#ws = browser_ws_conn
|
||||
this.#hooks = {} //table_name:[sql]
|
||||
|
||||
//public:
|
||||
this.ses_id = null //you are free to set this to whatever, so that you can later identify it by any means. Usually set it to whatever your session cookie is for this client on your web server
|
||||
this.verbose = verbose
|
||||
}
|
||||
|
||||
|
|
@ -156,10 +221,10 @@ class Session{
|
|||
this.#hooks[table].push({ id: id, sql: sql, params: params })
|
||||
else
|
||||
this.#hooks[table] = [{ id: id, sql:sql, params:params}]
|
||||
log('reg hook', table, this.#hooks[table])
|
||||
// log('reg hook', table, this.#hooks[table])
|
||||
}
|
||||
|
||||
Send(data=[]){
|
||||
Send(data={}){
|
||||
this.#ws.send(JSON.stringify(data))
|
||||
if (this.verbose) info('sent:', ...data)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export function SocioSecurityPlugin({ secure_private_key = '', cipther_algorithm
|
|||
|
||||
///(?<pre>\.subscribe\(\s*|\.query\(\s*|sql\s*:\s*)"(?<sql>[^"]+?)(?<post>--socio)"/ig
|
||||
const string_regex = /(?<q>["'])(?<str>[^ ]+? .+?)\1/g // /(?<q>["'])(?<str>.+?)\1/ig // match all strings
|
||||
const sql_string_regex = /(?<sql>.+?)(?<post>--socio;?)$/im //get the sql out of the string
|
||||
const sql_string_regex = /(?<sql>.+?--socio;?)$/im //check that the string ends with --socio
|
||||
|
||||
//The aim of the wise is not to secure pleasure, but to avoid pain. /Aristotle/
|
||||
export class SocioSecurity{
|
||||
|
|
|
|||
198
web/.gitignore
vendored
Normal file
198
web/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
#mine
|
||||
src/boiler.svelte
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,svelte
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=node,visualstudiocode,svelte
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
### Node Patch ###
|
||||
# Serverless Webpack directories
|
||||
.webpack/
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
# SvelteKit build / generate output
|
||||
.svelte-kit
|
||||
|
||||
### Svelte ###
|
||||
# gitignore template for the SvelteKit, frontend web component framework
|
||||
# website: https://kit.svelte.dev/
|
||||
|
||||
.svelte-kit/
|
||||
package
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,svelte
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
web/.vscode/extensions.json
vendored
Normal file
3
web/.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
}
|
||||
1
web/README.md
Normal file
1
web/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Official hero web page for Socio made with Svelte + Vite
|
||||
19
web/index.html
Normal file
19
web/index.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- font -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Jost:wght@200;400;700&display=swap" rel="stylesheet">
|
||||
|
||||
<title>Socio Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
33
web/jsconfig.json
Normal file
33
web/jsconfig.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "Node",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
/**
|
||||
* svelte-preprocess cannot figure out whether you have
|
||||
* a value or a type, so tell TypeScript to enforce using
|
||||
* `import type` instead of `import` for Types.
|
||||
*/
|
||||
"importsNotUsedAsValues": "error",
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* To have warnings / errors of the Svelte compiler at the
|
||||
* correct position, enable source maps by default.
|
||||
*/
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable this if you'd like to use dynamic types.
|
||||
*/
|
||||
"checkJs": true
|
||||
},
|
||||
/**
|
||||
* Use global.d.ts instead of compilerOptions.types
|
||||
* to avoid limiting type declarations.
|
||||
*/
|
||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
5130
web/package-lock.json
generated
Normal file
5130
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
22
web/package.json
Normal file
22
web/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port 5000",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.1.0",
|
||||
"svelte": "^3.52.0",
|
||||
"vite": "^3.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^10.4.13",
|
||||
"node-sass": "^8.0.0",
|
||||
"postcss": "^8.4.19",
|
||||
"svelte-preprocess": "^4.10.7"
|
||||
}
|
||||
}
|
||||
21
web/src/App.svelte
Normal file
21
web/src/App.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<script>
|
||||
import Page from "./page.svelte";
|
||||
import BlobBg from "./blob_bg.svelte";
|
||||
|
||||
</script>
|
||||
<main>
|
||||
|
||||
<Page></Page>
|
||||
|
||||
<BlobBg></BlobBg>
|
||||
</main>
|
||||
<style lang="scss">
|
||||
@import './global.scss';
|
||||
|
||||
main{
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: $radial_bg;
|
||||
}
|
||||
</style>
|
||||
24
web/src/app.css
Normal file
24
web/src/app.css
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
:root {
|
||||
font-family: "Jost", "Open Sans", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: dark;
|
||||
color: white;
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
html, body{
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
|
||||
font-family: "Jost", "Open Sans", Arial, sans-serif;
|
||||
}
|
||||
47
web/src/blob_bg.svelte
Normal file
47
web/src/blob_bg.svelte
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<script>
|
||||
const blobs = [
|
||||
{size:881, x:'-34vw', y:"88vh", color:'--acc_2'},
|
||||
{size:616, x:'79vw', y:"-53vh", color:'--acc_1'},
|
||||
{size:290, x:'102vw', y:"14vh", color:'--acc_2'}
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<section class="blobs_wrap">
|
||||
<div class="blobs">
|
||||
{#each blobs as b}
|
||||
<div class="blob" style="background-color: var({b.color}); left:0; top:0; width:{b.size}px; transform: Translate({b.x},{b.y})"></div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
@import './global.scss';
|
||||
|
||||
:root{
|
||||
--acc_1: #{$acc1};
|
||||
--acc_2: #{$acc2};
|
||||
}
|
||||
|
||||
.blobs_wrap{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
opacity: 0.5;
|
||||
overflow: hidden;
|
||||
}
|
||||
.blobs{
|
||||
position: relative;
|
||||
|
||||
.blob{
|
||||
position: absolute;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 50%;
|
||||
filter: blur(192px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
31
web/src/bloom.svelte
Normal file
31
web/src/bloom.svelte
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<script>
|
||||
export let style='';
|
||||
</script>
|
||||
|
||||
<div {style} class="bloom_wrap">
|
||||
<slot></slot>
|
||||
<div class="bloom">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
// @import './global.scss';
|
||||
|
||||
:root{
|
||||
--b: #{$bloom};
|
||||
}
|
||||
|
||||
.bloom_wrap{
|
||||
position: relative;
|
||||
// display: inline-flex;
|
||||
}
|
||||
.bloom{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
filter: blur(var(--b));
|
||||
}
|
||||
</style>
|
||||
34
web/src/border.svelte
Normal file
34
web/src/border.svelte
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<script>
|
||||
import Bloom from "./bloom.svelte";
|
||||
|
||||
export let color = '--acc_2';
|
||||
</script>
|
||||
|
||||
|
||||
<div class="wrap">
|
||||
<slot></slot>
|
||||
|
||||
<div class="border_overlay">
|
||||
<Bloom>
|
||||
<div class="border" style="border-color: var({color});"></div>
|
||||
</Bloom>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import './global.scss';
|
||||
|
||||
.wrap{
|
||||
position: relative;
|
||||
}
|
||||
.border_overlay{
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.border{
|
||||
border: 1px solid var(--acc_2);
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
}
|
||||
</style>
|
||||
44
web/src/global.scss
Normal file
44
web/src/global.scss
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//GOLBAL VARIABLES
|
||||
|
||||
//color theme
|
||||
$dark: #07070C;
|
||||
$dark_blue: #07071D;
|
||||
$gray1: #0D0D0E;
|
||||
$light: #FFFFFF;
|
||||
|
||||
$acc1: #FCF300;
|
||||
$acc2: #F300FC;
|
||||
|
||||
//color usages
|
||||
$radial_bg: radial-gradient(50% 50% at 50% 50%, $dark_blue 0%, $dark 100%);
|
||||
|
||||
//text sizes
|
||||
$typing_text: 22px;
|
||||
|
||||
//blooms
|
||||
$bloom_text: 4px;
|
||||
$bloom:64px;
|
||||
$text_shadow: 0px 0px 16px rgba(255, 255, 255, 0.16);
|
||||
|
||||
//universal paddings, spaces, gaps, margins for consistent alignment
|
||||
$trans: 140ms ease-in-out;
|
||||
|
||||
@mixin no_spacings {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,a{
|
||||
@include no_spacings;
|
||||
font-weight: 300;
|
||||
}
|
||||
h1 {font-size: 58.59px;
|
||||
font-weight: bold;
|
||||
text-shadow: $text_shadow;}
|
||||
h2 {font-size: 46.88px;}
|
||||
h3 {font-size: 37.50px;}
|
||||
h4 {font-size: 30.00px;}
|
||||
h5 {font-size: 24.00px;}
|
||||
8
web/src/main.js
Normal file
8
web/src/main.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import './app.css'
|
||||
import App from './App.svelte'
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app')
|
||||
})
|
||||
|
||||
export default app
|
||||
39
web/src/page.svelte
Normal file
39
web/src/page.svelte
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<script>
|
||||
import Bloom from "./bloom.svelte";
|
||||
import Border from "./border.svelte";
|
||||
|
||||
</script>
|
||||
|
||||
<section class="content">
|
||||
<h1>Socio.js</h1>
|
||||
<h3 class="sub_title">WebSocket <Bloom style="--b:var(--tb);"><span style="color: var(--acc_1);">live</span></Bloom> / <Bloom style="--b:var(--tb);"><span style="color: var(--acc_2);">reactive</span></Bloom> database connection framework.</h3>
|
||||
<!-- <Border>
|
||||
<h3>sdgfhskdfksdfksd fjgdkjfgkh</h3>
|
||||
</Border> -->
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
@import './global.scss';
|
||||
|
||||
:root{
|
||||
--acc_1: #{$acc1};
|
||||
--acc_2: #{$acc2};
|
||||
--tb: #{$bloom_text};
|
||||
}
|
||||
|
||||
.content{
|
||||
padding: 162px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
h3{
|
||||
font-weight: 200;
|
||||
}
|
||||
.sub_title{
|
||||
display: flex;
|
||||
align-items:center;
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
2
web/src/vite-env.d.ts
vendored
Normal file
2
web/src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
33
web/vite.config.js
Normal file
33
web/vite.config.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
//for SCSS
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
//npm i autoprefixer node-sass postcss svelte-preprocess
|
||||
const production = false;
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [svelte({
|
||||
//FOR SCSS
|
||||
preprocess: sveltePreprocess({
|
||||
sourceMap: !production,
|
||||
defaults: {
|
||||
style: 'scss'
|
||||
},
|
||||
scss: {
|
||||
// We can use a path relative to the root because
|
||||
// svelte-preprocess automatically adds it to `includePaths`
|
||||
// if none is defined.
|
||||
prependData: `@import 'src/global.scss';`
|
||||
},
|
||||
}),
|
||||
//FOR SCSS END
|
||||
|
||||
|
||||
compilerOptions: {
|
||||
// enable run-time checks when not in production
|
||||
dev: !production
|
||||
}
|
||||
})]
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue