hero page start

This commit is contained in:
Rolands 2022-11-16 11:16:04 +02:00
parent 15127e8aa3
commit 2ab82b60b2
22 changed files with 5897 additions and 126 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)
}

View file

@ -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
View 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
View file

@ -0,0 +1,3 @@
{
"recommendations": ["svelte.svelte-vscode"]
}

1
web/README.md Normal file
View file

@ -0,0 +1 @@
# Official hero web page for Socio made with Svelte + Vite

19
web/index.html Normal file
View 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
View 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

File diff suppressed because it is too large Load diff

22
web/package.json Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

33
web/vite.config.js Normal file
View 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
}
})]
})