init
This commit is contained in:
commit
f1791b8699
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
*/target/
|
||||
/target
|
||||
pkg/*.wasm
|
||||
pkg/ui
|
||||
*.swp
|
||||
*.swo
|
||||
*/wasi_snapshot_preview1.wasm
|
||||
*/wit/
|
||||
*/process_env
|
3473
Cargo.lock
generated
Normal file
3473
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"twittok",
|
||||
"cookies",
|
||||
"proxy"
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
6
api/tok:sortugdev.os.wit
Normal file
6
api/tok:sortugdev.os.wit
Normal file
@ -0,0 +1,6 @@
|
||||
world tok-sortugdev-dot-os-v0{
|
||||
import all;
|
||||
include process-v0;
|
||||
}
|
||||
|
||||
interface all {}
|
18
cookies/Cargo.toml
Normal file
18
cookies/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "cookies"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
kinode_process_lib = { version = "0.9.2", features = ["logging"] }
|
||||
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = "0.24.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "kinode:process"
|
316
cookies/src/lib.rs
Normal file
316
cookies/src/lib.rs
Normal file
@ -0,0 +1,316 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use kinode_process_lib::{
|
||||
await_message, await_next_message_body, call_init, get_blob, get_typed_state,
|
||||
http::{
|
||||
server::{
|
||||
send_response, HttpBindingConfig, HttpServer, HttpServerRequest, IncomingHttpRequest,
|
||||
WsBindingConfig,
|
||||
},
|
||||
StatusCode,
|
||||
},
|
||||
println, set_state, Address, Message, Request,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "target/wit",
|
||||
world: "tok-sortugdev-dot-os-v0",
|
||||
generate_unused_types: true,
|
||||
additional_derives: [serde::Deserialize, serde::Serialize],
|
||||
});
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
enum UIReq {
|
||||
SetCookie { app: String, cookie: CookieMap },
|
||||
SetAPIKey(APIRes),
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
enum UIRes {
|
||||
All(State),
|
||||
App(AppRes),
|
||||
NotFound,
|
||||
Ack, // ErrorRes,
|
||||
// Ack(Ack)
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
struct ErrorRes {
|
||||
error: ErrorType,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
enum ErrorType {
|
||||
#[serde(rename = "not found")]
|
||||
NotFound,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
struct Ack {
|
||||
ack: String,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
enum AppRes {
|
||||
API(APIRes),
|
||||
Cookie(CookieRes),
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
struct APIRes {
|
||||
app: String,
|
||||
api_key: String,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
struct CookieRes {
|
||||
app: String,
|
||||
cookie: CookieMap,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Cookie {
|
||||
domain: String,
|
||||
path: String,
|
||||
name: String,
|
||||
value: String,
|
||||
#[serde(rename = "sameSite")]
|
||||
same_site: SameSite,
|
||||
#[serde(rename = "httpOnly")]
|
||||
http_only: bool,
|
||||
host_only: bool,
|
||||
secure: bool,
|
||||
session: bool,
|
||||
#[serde(rename = "expirationDate", default = "default_expiration")]
|
||||
expiration: f64,
|
||||
#[serde(rename = "storeId")]
|
||||
store_id: Option<String>,
|
||||
}
|
||||
fn default_expiration() -> f64 {
|
||||
0.0
|
||||
}
|
||||
type CookieMap = HashMap<String, Cookie>;
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum SameSite {
|
||||
Strict,
|
||||
Lax,
|
||||
None,
|
||||
#[default]
|
||||
#[serde(alias = "no_restrictions")]
|
||||
#[serde(other)]
|
||||
Unspecified,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct State {
|
||||
cookies: HashMap<String, CookieMap>,
|
||||
#[serde(rename = "apiKeys")]
|
||||
api_keys: HashMap<String, String>,
|
||||
}
|
||||
impl State {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cookies: HashMap::new(),
|
||||
api_keys: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn save(&self) -> Result<()> {
|
||||
let bytes = serde_json::to_vec(self)?;
|
||||
set_state(&bytes);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub fn load_state() -> State {
|
||||
get_typed_state(|bytes| serde_json::from_slice::<State>(bytes).map_err(Box::new))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn handle_message(our: &Address) -> anyhow::Result<()> {
|
||||
let message = await_message()?;
|
||||
|
||||
if !message.is_request() {
|
||||
return Err(anyhow::anyhow!("unexpected Response: {:?}", message));
|
||||
}
|
||||
// println!("kinode message received {:?}", message);
|
||||
match message {
|
||||
Message::Request {
|
||||
ref source,
|
||||
ref body,
|
||||
..
|
||||
} => {
|
||||
return handle_ui(our, body);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_ui(our: &Address, body: &Vec<u8>) -> Result<()> {
|
||||
let Ok(server_request) = serde_json::from_slice::<HttpServerRequest>(body) else {
|
||||
// Fail silently if we can't parse the request
|
||||
// return Err("parsing error");
|
||||
return Ok(());
|
||||
};
|
||||
match server_request {
|
||||
HttpServerRequest::WebSocketOpen { channel_id, .. } => Ok(()),
|
||||
HttpServerRequest::WebSocketPush { .. } => Ok(()),
|
||||
HttpServerRequest::WebSocketClose(_channel_id) => Ok(()),
|
||||
HttpServerRequest::Http(request) => handle_http(our, request),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_http(our: &Address, request: IncomingHttpRequest) -> Result<()> {
|
||||
let mut state = load_state();
|
||||
match request.method()?.as_str() {
|
||||
"GET" => handle_get(our, request, &mut state),
|
||||
"POST" => handle_post(our, request, &mut state),
|
||||
_ => {
|
||||
println!("cookies - got weird req - {:?}", request);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn handle_get(our: &Address, req: IncomingHttpRequest, state: &mut State) -> anyhow::Result<()> {
|
||||
// println!("got GET req - {:?}", req);
|
||||
let conc = format!("{}:{}{}", our.process(), our.package_id(), "/api");
|
||||
let pat = req.bound_path(Some(conc.as_str()));
|
||||
// let pats = req.path()?;
|
||||
// let pat = pats.as_str();
|
||||
let uparams = req.url_params();
|
||||
let qparams = req.query_params();
|
||||
|
||||
// println!("request path {}", pat);
|
||||
// println!("request up {:?}", uparams);
|
||||
// println!("request qp {:?}", qparams);
|
||||
match pat {
|
||||
"/debug" => {
|
||||
println!("current state {:?}", state);
|
||||
}
|
||||
"/all" => {
|
||||
let res = UIRes::All(state.to_owned());
|
||||
send_json(res)?;
|
||||
}
|
||||
"/app" => {
|
||||
let app_name = qparams.get("name").unwrap().as_str();
|
||||
let api_param = qparams.get("api").unwrap().as_str();
|
||||
let is_api = api_param == "true";
|
||||
if is_api {
|
||||
let value = state.api_keys.get(app_name);
|
||||
match value {
|
||||
None => {
|
||||
send_json(UIRes::NotFound)?;
|
||||
}
|
||||
Some(v) => {
|
||||
let res = APIRes {
|
||||
app: app_name.to_owned(),
|
||||
api_key: v.to_string(),
|
||||
};
|
||||
send_json(UIRes::App(AppRes::API(res)))?;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
let value = state.cookies.get(app_name);
|
||||
match value {
|
||||
None => {
|
||||
send_json(UIRes::NotFound)?;
|
||||
}
|
||||
Some(v) => {
|
||||
let res = CookieRes {
|
||||
app: app_name.to_owned(),
|
||||
cookie: v.to_owned(),
|
||||
};
|
||||
send_json(UIRes::App(AppRes::Cookie(res)))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
send_response(StatusCode::CREATED, None, vec![]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_json(res: UIRes) -> Result<()> {
|
||||
let body = serde_json::to_vec(&res)?;
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
||||
send_response(StatusCode::OK, Some(headers), body);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_test() {
|
||||
let str = r#"
|
||||
{"auth_multi":{"domain":".x.com","expirationDate":1736167622.15024,"hostOnly":false,"httpOnly":true,"name":"auth_multi","path":"/","sameSite":"lax","secure":true,"session":false,"storeId":"0","value":"\"1710606417324015616:604ed700cfb5c8a5d2665086503f1ec8b6032ef4\"","id":1},"auth_token":{"domain":".x.com","expirationDate":1736097942.573061,"hostOnly":false,"httpOnly":true,"name":"auth_token","path":"/","sameSite":"no_restriction","secure":true,"session":false,"storeId":"0","value":"2527de3d3719d900cd5658525e559d0966d86662","id":2},"ct0":{"domain":".x.com","expirationDate":1736097942.861119,"hostOnly":false,"httpOnly":false,"name":"ct0","path":"/","sameSite":"lax","secure":true,"session":false,"storeId":"0","value":"1461eb581cb824ea00b652d27a735f1abebe2b47de834cddd624afdbee20c4f033972cb63fd298d0db8cded9a75429df3907ced5bb375f12cf02100825b16f7c02a00253ca1e8883b60ace17ccea1622","id":3},"dnt":{"domain":".x.com","expirationDate":1736097942.572255,"hostOnly":false,"httpOnly":false,"name":"dnt","path":"/","sameSite":"no_restriction","secure":true,"session":false,"storeId":"0","value":"1","id":4},"external_referer":{"domain":".x.com","expirationDate":1721160063.509523,"hostOnly":false,"httpOnly":false,"name":"external_referer","path":"/","sameSite":"unspecified","secure":true,"session":false,"storeId":"0","value":"padhuUp37zjgzgv1mFWxJ12Ozwit7owX|0|8e8t2xd8A2w%3D","id":5},"guest_id":{"domain":".x.com","expirationDate":1736097942.861021,"hostOnly":false,"httpOnly":false,"name":"guest_id","path":"/","sameSite":"no_restriction","secure":true,"session":false,"storeId":"0","value":"v1%3A172054594269674803","id":6},"guest_id_ads":{"domain":".x.com","expirationDate":1736167616.625858,"hostOnly":false,"httpOnly":false,"name":"guest_id_ads","path":"/","sameSite":"no_restriction","secure":true,"session":false,"storeId":"0","value":"v1%3A172054594269674803","id":7},"guest_id_marketing":{"domain":".x.com","expirationDate":1736167616.625932,"hostOnly":false,"httpOnly":false,"name":"guest_id_marketing","path":"/","sameSite":"no_restriction","secure":true,"session":false,"storeId":"0","value":"v1%3A172054594269674803","id":8},"kdt":{"domain":".x.com","expirationDate":1736097942.572385,"hostOnly":false,"httpOnly":true,"name":"kdt","path":"/","sameSite":"unspecified","secure":true,"session":false,"storeId":"0","value":"iWdBqeAH3UcgpwzPxi6CZ2lRTk4Fqia3OR5VbiSo","id":9},"personalization_id":{"domain":".x.com","expirationDate":1736167616.625994,"hostOnly":false,"httpOnly":false,"name":"personalization_id","path":"/","sameSite":"no_restriction","secure":true,"session":false,"storeId":"0","value":"\"v1_O5kcdlrANZbUakXhbmlSTw==\"","id":10},"twid":{"domain":".x.com","expirationDate":1736167616.773335,"hostOnly":false,"httpOnly":false,"name":"twid","path":"/","sameSite":"no_restriction","secure":true,"session":false,"storeId":"0","value":"u%3D1809740330922831872","id":11},"d_prefs":{"domain":"x.com","hostOnly":true,"httpOnly":false,"name":"d_prefs","path":"/","sameSite":"unspecified","secure":true,"session":true,"storeId":"0","value":"MjoxLGNvbnNlbnRfdmVyc2lvbjoyLHRleHRfdmVyc2lvbjoxMDAw","id":12},"lang":{"domain":"x.com","hostOnly":true,"httpOnly":false,"name":"lang","path":"/","sameSite":"unspecified","secure":false,"session":true,"storeId":"0","value":"en","id":13}}
|
||||
"#;
|
||||
let pj = serde_json::from_str::<CookieMap>(str);
|
||||
println!("json parsing test {:?}", pj);
|
||||
// let jason = r#"
|
||||
// {
|
||||
// "domain": ".x.com",
|
||||
// "expirationDate": 1736167622.15024,
|
||||
// "hostOnly": false,
|
||||
// "httpOnly": true,
|
||||
// "name": "auth_multi",
|
||||
// "path": "/",
|
||||
// "sameSite": "lax",
|
||||
// "secure": true,
|
||||
// "session": false,
|
||||
// "storeId": "0",
|
||||
// "value": "\"1710606417324015616:604ed700cfb5c8a5d2665086503f1ec8b6032ef4\"",
|
||||
// "id": 1
|
||||
// }
|
||||
// "#;
|
||||
// let pj = serde_json::from_str::<Cookie>(jason);
|
||||
// println!("json parsing test {:?}", pj);
|
||||
// // let example: UIReq = UIReq::SetAPIKey(APIRes {
|
||||
// // app: "foo".to_string(),
|
||||
// // api_key: "bar".to_string(),
|
||||
// // });
|
||||
// // let value = serde_json::to_string(&example)?;
|
||||
// // println!("expected json {}", value);
|
||||
}
|
||||
fn handle_post(our: &Address, req: IncomingHttpRequest, state: &mut State) -> anyhow::Result<()> {
|
||||
println!("cokies post request!");
|
||||
// parse_test();
|
||||
|
||||
// let conc = format!("{}:{}{}", our.process(), our.package_id(), "/api");
|
||||
let Some(blob) = get_blob() else {
|
||||
return Ok(());
|
||||
};
|
||||
let Ok(post_request) = serde_json::from_slice::<UIReq>(&blob.bytes) else {
|
||||
// Fail silently if we can't parse the request
|
||||
return Ok(());
|
||||
};
|
||||
println!("post body {:?}", post_request);
|
||||
|
||||
match post_request {
|
||||
UIReq::SetCookie { app, cookie } => {
|
||||
state.cookies.insert(app, cookie);
|
||||
state.save()?;
|
||||
send_json(UIRes::Ack)?;
|
||||
}
|
||||
UIReq::SetAPIKey(ar) => {
|
||||
state.api_keys.insert(ar.app, ar.api_key);
|
||||
state.save()?;
|
||||
send_json(UIRes::Ack)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
call_init!(init);
|
||||
fn init(our: Address) {
|
||||
let mut http_server = HttpServer::new(5);
|
||||
let http_config = HttpBindingConfig::default();
|
||||
|
||||
let ws_config = WsBindingConfig::default();
|
||||
http_server.bind_ws_path("/", ws_config.clone());
|
||||
// REST API
|
||||
http_server.bind_http_path("/", http_config.clone());
|
||||
http_server.bind_http_path("/api", http_config.clone());
|
||||
http_server.bind_http_path("/api/all", http_config.clone());
|
||||
http_server.bind_http_path("/api/app", http_config.clone());
|
||||
loop {
|
||||
match handle_message(&our) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
println!("error: {:?}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
18
metadata.json
Normal file
18
metadata.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "tok",
|
||||
"description": "Tweet Tok",
|
||||
"image": "",
|
||||
"properties": {
|
||||
"package_name": "tok",
|
||||
"current_version": "0.1.0",
|
||||
"publisher": "sortugdev.os",
|
||||
"mirrors": [],
|
||||
"code_hashes": {
|
||||
"0.1.0": ""
|
||||
},
|
||||
"wit_version": 0,
|
||||
"dependencies": []
|
||||
},
|
||||
"external_url": "",
|
||||
"animation_url": ""
|
||||
}
|
BIN
pkg/api.zip
Normal file
BIN
pkg/api.zip
Normal file
Binary file not shown.
44
pkg/manifest.json
Normal file
44
pkg/manifest.json
Normal file
@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"process_name": "cookies",
|
||||
"process_wasm_path": "/cookies.wasm",
|
||||
"on_exit": "Restart",
|
||||
"request_networking": true,
|
||||
"request_capabilities": [
|
||||
"http_server:distro:sys"
|
||||
],
|
||||
"grant_capabilities": [],
|
||||
"public": true
|
||||
},
|
||||
{
|
||||
"process_name": "proxy",
|
||||
"process_wasm_path": "/proxy.wasm",
|
||||
"on_exit": "Restart",
|
||||
"request_networking": true,
|
||||
"request_capabilities": [
|
||||
"http_server:distro:sys",
|
||||
"http_client:distro:sys"
|
||||
],
|
||||
"grant_capabilities": [
|
||||
"http_server:distro:sys",
|
||||
"http_client:distro:sys"
|
||||
],
|
||||
"public": true
|
||||
},
|
||||
{
|
||||
"process_name": "twittok",
|
||||
"process_wasm_path": "/twittok.wasm",
|
||||
"on_exit": "Restart",
|
||||
"request_networking": true,
|
||||
"request_capabilities": [
|
||||
"homepage:homepage:sys",
|
||||
"http_server:distro:sys",
|
||||
"http_client:distro:sys"
|
||||
],
|
||||
"grant_capabilities": [
|
||||
"http_server:distro:sys",
|
||||
"http_client:distro:sys"
|
||||
],
|
||||
"public": true
|
||||
}
|
||||
]
|
21
proxy/Cargo.toml
Normal file
21
proxy/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "proxy"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
kinode_process_lib = { version = "0.9.2", features = ["logging"] }
|
||||
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
|
||||
mime = "0.3.17"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
strsim = "0.10.0"
|
||||
wit-bindgen = "0.24.0"
|
||||
url = "2.5.2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "kinode:process"
|
141
proxy/src/lib.rs
Normal file
141
proxy/src/lib.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use kinode_process_lib::{
|
||||
await_message, call_init, get_blob,
|
||||
http::{
|
||||
server::{
|
||||
send_response, HttpBindingConfig, HttpServer, HttpServerRequest, IncomingHttpRequest,
|
||||
WsBindingConfig,
|
||||
},
|
||||
StatusCode,
|
||||
},
|
||||
println, Address, Message, ProcessId, Request, Response,
|
||||
};
|
||||
|
||||
mod types;
|
||||
use types::*;
|
||||
mod proxy;
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "target/wit",
|
||||
world: "tok-sortugdev-dot-os-v0",
|
||||
generate_unused_types: true,
|
||||
additional_derives: [serde::Deserialize, serde::Serialize],
|
||||
});
|
||||
|
||||
fn handle_message(our: &Address) -> anyhow::Result<()> {
|
||||
let message = await_message()?;
|
||||
if !message.is_request() {
|
||||
return Err(anyhow::anyhow!("unexpected Response: {:?}", message));
|
||||
}
|
||||
match message {
|
||||
Message::Request {
|
||||
ref source,
|
||||
ref body,
|
||||
..
|
||||
} => {
|
||||
return handle_ui(our, body);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn handle_ui(our: &Address, body: &Vec<u8>) -> Result<()> {
|
||||
let Ok(server_request) = serde_json::from_slice::<HttpServerRequest>(body) else {
|
||||
// Fail silently if we can't parse the request
|
||||
// return Err("parsing error");
|
||||
return Ok(());
|
||||
};
|
||||
match server_request {
|
||||
HttpServerRequest::WebSocketOpen { channel_id, .. } => Ok(()),
|
||||
HttpServerRequest::WebSocketPush { .. } => Ok(()),
|
||||
HttpServerRequest::WebSocketClose(_channel_id) => Ok(()),
|
||||
HttpServerRequest::Http(request) => handle_http(our, request),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_http(our: &Address, request: IncomingHttpRequest) -> Result<()> {
|
||||
match request.method()?.as_str() {
|
||||
"POST" => handle_post(our, request),
|
||||
_ => {
|
||||
println!("got weird req - {:?}", request);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn handle_post(our: &Address, req: IncomingHttpRequest) -> anyhow::Result<()> {
|
||||
println!("post request!");
|
||||
// parse_test();
|
||||
|
||||
// let conc = format!("{}:{}{}", our.process(), our.package_id(), "/api");
|
||||
let Some(blob) = get_blob() else {
|
||||
return Ok(());
|
||||
};
|
||||
let Ok(post_request) = serde_json::from_slice::<UIReq>(&blob.bytes) else {
|
||||
// Fail silently if we can't parse the request
|
||||
return Ok(());
|
||||
};
|
||||
let res = proxy::run(post_request);
|
||||
match res {
|
||||
Ok((mime, body)) => {
|
||||
handle_body(mime, body);
|
||||
}
|
||||
Err(e) => {
|
||||
let s = e.to_string();
|
||||
send_json(UIRes::Err { error: s })?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn handle_body(mt: mime::Mime, body: Vec<u8>) -> Result<()> {
|
||||
match (mt.type_(), mt.subtype()) {
|
||||
(mime::APPLICATION, mime::JSON) => {
|
||||
let s = String::from_utf8(body)?;
|
||||
send_json(UIRes::Ok { result: s })?;
|
||||
}
|
||||
(mime::AUDIO, mime::MPEG) => {
|
||||
send_mime(mt, body)?;
|
||||
}
|
||||
_ => {
|
||||
println!("unhandled mime type {:?}", mt);
|
||||
// Err(anyhow!(""))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn send_mime(mt: mime::Mime, body: Vec<u8>) -> Result<()> {
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Content-Type".to_string(), mt.to_string());
|
||||
send_response(StatusCode::OK, Some(headers), body);
|
||||
Ok(())
|
||||
}
|
||||
fn send_json(res: UIRes) -> Result<()> {
|
||||
let body = serde_json::to_vec(&res)?;
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
||||
send_response(StatusCode::OK, Some(headers), body);
|
||||
Ok(())
|
||||
}
|
||||
call_init!(init);
|
||||
fn init(our: Address) {
|
||||
println!("begin proxy");
|
||||
println!("our {:?}", our);
|
||||
println!("our {:?}", our.process());
|
||||
let mut http_server = HttpServer::new(5);
|
||||
let http_config = HttpBindingConfig::default();
|
||||
|
||||
let ws_config = WsBindingConfig::default();
|
||||
http_server.bind_ws_path("/", ws_config.clone());
|
||||
// REST API
|
||||
http_server.bind_http_path("/api", http_config.clone());
|
||||
loop {
|
||||
match handle_message(&our) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
println!("error: {:?}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
92
proxy/src/proxy.rs
Normal file
92
proxy/src/proxy.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::io::BufRead;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use kinode_process_lib::{
|
||||
http::{client::send_request_await_response, Method},
|
||||
println,
|
||||
};
|
||||
use mime::Mime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::UIReq;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub enum ScrapeRes {
|
||||
Image(String),
|
||||
HTML(String),
|
||||
}
|
||||
pub fn scrape(url: &str) -> Result<ScrapeRes> {
|
||||
let url = Url::parse(url)?;
|
||||
let mut headers = std::collections::HashMap::new();
|
||||
headers.insert(
|
||||
"User-Agent".to_string(),
|
||||
"facebookexternalhit/1.1".to_string(),
|
||||
// "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36".to_string(),
|
||||
);
|
||||
|
||||
let res = send_request_await_response(Method::GET, url.clone(), Some(headers), 5000, vec![])?;
|
||||
let h = res.headers().get("content-type");
|
||||
match h {
|
||||
None => {
|
||||
let b = res.body().to_vec();
|
||||
let text = String::from_utf8(b)?;
|
||||
Ok(ScrapeRes::HTML(text))
|
||||
}
|
||||
Some(val) => {
|
||||
let str = val.to_str()?;
|
||||
if str.starts_with("image") {
|
||||
Ok(ScrapeRes::Image(url.to_string()))
|
||||
} else {
|
||||
let b = res.body().to_vec();
|
||||
let text = String::from_utf8(b)?;
|
||||
Ok(ScrapeRes::HTML(text))
|
||||
}
|
||||
}
|
||||
}
|
||||
// let body = get_blob().ok_or(anyhow::anyhow!("no blob"))?;
|
||||
}
|
||||
pub fn proxy(url: &str) -> Result<Vec<u8>> {
|
||||
let url = Url::parse(url)?;
|
||||
let mut headers = std::collections::HashMap::new();
|
||||
headers.insert(
|
||||
"User-Agent".to_string(),
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36".to_string(),
|
||||
);
|
||||
|
||||
let res = send_request_await_response(Method::GET, url.clone(), Some(headers), 5000, vec![])?;
|
||||
let b = res.body().to_vec();
|
||||
Ok(b)
|
||||
// let body = get_blob().ok_or(anyhow::anyhow!("no blob"))?;
|
||||
}
|
||||
pub fn run(req: UIReq) -> Result<(Mime, Vec<u8>)> {
|
||||
let url = Url::parse(&req.url)?;
|
||||
let body: Vec<u8> = match req.body {
|
||||
None => vec![],
|
||||
Some(s) => s.as_bytes().to_vec(),
|
||||
};
|
||||
let mut headers = req.headers.clone();
|
||||
headers.insert(
|
||||
"User-Agent".to_string(),
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36".to_string(),
|
||||
);
|
||||
// println!("running req {:?} {:?}", url, headers);
|
||||
|
||||
let res = send_request_await_response(req.method, url.clone(), Some(req.headers), 5000, body)?;
|
||||
let h = res.headers();
|
||||
println!("res headers {:?}", h);
|
||||
let content_type: Mime = res
|
||||
.headers()
|
||||
.get("content-type")
|
||||
.and_then(|ct| ct.to_str().ok())
|
||||
.and_then(|ct| ct.parse::<Mime>().ok())
|
||||
.ok_or_else(|| anyhow!("invalid content type"))?;
|
||||
println!(
|
||||
"fucking mime {:?} {:?}",
|
||||
content_type.type_(),
|
||||
content_type.subtype()
|
||||
);
|
||||
let b = res.body().to_vec();
|
||||
Ok((content_type, b))
|
||||
// let body = get_blob().ok_or(anyhow::anyhow!("no blob"))?;
|
||||
}
|
39
proxy/src/types.rs
Normal file
39
proxy/src/types.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use kinode_process_lib::http::Method;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct UIReq {
|
||||
pub url: String,
|
||||
#[serde(deserialize_with = "deserialize_method")]
|
||||
pub method: Method,
|
||||
pub headers: HashMap<String, String>,
|
||||
pub body: Option<String>,
|
||||
}
|
||||
fn deserialize_method<'de, D>(deserializer: D) -> Result<Method, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = String::deserialize(deserializer)?;
|
||||
match s.to_uppercase().as_str() {
|
||||
"GET" => Ok(Method::GET),
|
||||
"POST" => Ok(Method::POST),
|
||||
"PUT" => Ok(Method::PUT),
|
||||
"DELETE" => Ok(Method::DELETE),
|
||||
"HEAD" => Ok(Method::HEAD),
|
||||
"OPTIONS" => Ok(Method::OPTIONS),
|
||||
"CONNECT" => Ok(Method::CONNECT),
|
||||
"PATCH" => Ok(Method::PATCH),
|
||||
"TRACE" => Ok(Method::TRACE),
|
||||
_ => Err(serde::de::Error::custom(format!(
|
||||
"Unknown HTTP method: {}",
|
||||
s
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub enum UIRes {
|
||||
Ok { result: String },
|
||||
Err { error: String },
|
||||
}
|
18
twittok/Cargo.toml
Normal file
18
twittok/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "twittok"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
kinode_process_lib = { version = "0.9.2", features = ["logging"] }
|
||||
process_macros = { git = "https://github.com/kinode-dao/process_macros", rev = "626e501" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = "0.24.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "kinode:process"
|
288
twittok/src/lib.rs
Normal file
288
twittok/src/lib.rs
Normal file
@ -0,0 +1,288 @@
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use kinode_process_lib::{
|
||||
await_message, call_init, get_blob,
|
||||
http::{
|
||||
server::{
|
||||
send_response, send_ws_push, HttpBindingConfig, HttpServer, HttpServerRequest,
|
||||
WsBindingConfig,
|
||||
},
|
||||
StatusCode,
|
||||
},
|
||||
println, Address, LazyLoadBlob, Message, ProcessId, Request, Response,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "target/wit",
|
||||
world: "tok-sortugdev-dot-os-v0",
|
||||
generate_unused_types: true,
|
||||
additional_derives: [serde::Deserialize, serde::Serialize]
|
||||
});
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum TwittokRequest {
|
||||
Send { target: String, message: String },
|
||||
History,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum TwittokResponse {
|
||||
Ack,
|
||||
History { messages: MessageArchive },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
struct TwittokMessage {
|
||||
author: String,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct NewMessage {
|
||||
twittok: String,
|
||||
author: String,
|
||||
content: String,
|
||||
}
|
||||
|
||||
type MessageArchive = HashMap<String, Vec<TwittokMessage>>;
|
||||
|
||||
fn handle_http_server_request(
|
||||
our: &Address,
|
||||
message_archive: &mut MessageArchive,
|
||||
our_channel_id: &mut u32,
|
||||
source: &Address,
|
||||
body: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
let Ok(server_request) = serde_json::from_slice::<HttpServerRequest>(body) else {
|
||||
// Fail silently if we can't parse the request
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match server_request {
|
||||
HttpServerRequest::WebSocketOpen { channel_id, .. } => {
|
||||
// Set our channel_id to the newly opened channel
|
||||
// Note: this code could be improved to support multiple channels
|
||||
*our_channel_id = channel_id;
|
||||
}
|
||||
HttpServerRequest::WebSocketPush { .. } => {
|
||||
let Some(blob) = get_blob() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
handle_twittok_request(
|
||||
our,
|
||||
message_archive,
|
||||
our_channel_id,
|
||||
source,
|
||||
&blob.bytes,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
HttpServerRequest::WebSocketClose(_channel_id) => {}
|
||||
HttpServerRequest::Http(request) => {
|
||||
match request.method()?.as_str() {
|
||||
// Get all messages
|
||||
"GET" => {
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
||||
|
||||
send_response(
|
||||
StatusCode::OK,
|
||||
Some(headers),
|
||||
serde_json::to_vec(&TwittokResponse::History {
|
||||
messages: message_archive.clone(),
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
// Send a message
|
||||
"POST" => {
|
||||
let Some(blob) = get_blob() else {
|
||||
return Ok(());
|
||||
};
|
||||
handle_twittok_request(
|
||||
our,
|
||||
message_archive,
|
||||
our_channel_id,
|
||||
source,
|
||||
&blob.bytes,
|
||||
true,
|
||||
)?;
|
||||
|
||||
// Send an http response via the http server
|
||||
send_response(StatusCode::CREATED, None, vec![]);
|
||||
}
|
||||
_ => {
|
||||
// Method not allowed
|
||||
send_response(StatusCode::METHOD_NOT_ALLOWED, None, vec![]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_twittok_request(
|
||||
our: &Address,
|
||||
message_archive: &mut MessageArchive,
|
||||
channel_id: &mut u32,
|
||||
source: &Address,
|
||||
body: &[u8],
|
||||
is_http: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let Ok(twittok_request) = serde_json::from_slice::<TwittokRequest>(body) else {
|
||||
// Fail silently if we can't parse the request
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match twittok_request {
|
||||
TwittokRequest::Send {
|
||||
ref target,
|
||||
ref message,
|
||||
} => {
|
||||
// counterparty will be the other node in the twittok with us
|
||||
let (counterparty, author) = if target == &our.node {
|
||||
(&source.node, source.node.clone())
|
||||
} else {
|
||||
(target, our.node.clone())
|
||||
};
|
||||
|
||||
// If the target is not us, send a request to the target
|
||||
if target == &our.node {
|
||||
println!("{}: {}", source.node, message);
|
||||
} else {
|
||||
Request::new()
|
||||
.target(Address {
|
||||
node: target.clone(),
|
||||
process: ProcessId::from_str("twittok:twittok:template.os")?,
|
||||
})
|
||||
.body(body)
|
||||
.send_and_await_response(5)??;
|
||||
}
|
||||
|
||||
// Retreive the message archive for the counterparty, or create a new one if it doesn't exist
|
||||
let messages = match message_archive.get_mut(counterparty) {
|
||||
Some(messages) => messages,
|
||||
None => {
|
||||
message_archive.insert(counterparty.clone(), Vec::new());
|
||||
message_archive.get_mut(counterparty).unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
let new_message = TwittokMessage {
|
||||
author: author.clone(),
|
||||
content: message.clone(),
|
||||
};
|
||||
|
||||
// If this is an HTTP request, handle the response in the calling function
|
||||
if is_http {
|
||||
// Add the new message to the archive
|
||||
messages.push(new_message);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// If this is not an HTTP request, send a response to the other node
|
||||
Response::new()
|
||||
.body(serde_json::to_vec(&TwittokResponse::Ack).unwrap())
|
||||
.send()
|
||||
.unwrap();
|
||||
|
||||
// Add the new message to the archive
|
||||
messages.push(new_message);
|
||||
|
||||
// Generate a blob for the new message
|
||||
let blob = LazyLoadBlob {
|
||||
mime: Some("application/json".to_string()),
|
||||
bytes: serde_json::json!({
|
||||
"NewMessage": NewMessage {
|
||||
twittok: counterparty.clone(),
|
||||
author,
|
||||
content: message.clone(),
|
||||
}
|
||||
})
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
};
|
||||
|
||||
// Send a WebSocket message to the http server in order to update the UI
|
||||
send_ws_push(
|
||||
channel_id.clone(),
|
||||
kinode_process_lib::http::server::WsMessageType::Text,
|
||||
blob,
|
||||
);
|
||||
}
|
||||
TwittokRequest::History => {
|
||||
// If this is an HTTP request, send a response to the http server
|
||||
|
||||
Response::new()
|
||||
.body(
|
||||
serde_json::to_vec(&TwittokResponse::History {
|
||||
messages: message_archive.clone(),
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.send()
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_message(
|
||||
our: &Address,
|
||||
message_archive: &mut MessageArchive,
|
||||
channel_id: &mut u32,
|
||||
) -> anyhow::Result<()> {
|
||||
let message = await_message().unwrap();
|
||||
|
||||
match message {
|
||||
Message::Response { .. } => {
|
||||
println!("got response - {:?}", message);
|
||||
return Ok(());
|
||||
}
|
||||
Message::Request {
|
||||
ref source,
|
||||
ref body,
|
||||
..
|
||||
} => {
|
||||
// Requests that come from other nodes running this app
|
||||
handle_twittok_request(our, message_archive, channel_id, source, body, false)?;
|
||||
// Requests that come from our http server
|
||||
handle_http_server_request(our, message_archive, channel_id, source, body)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
call_init!(init);
|
||||
fn init(our: Address) {
|
||||
println!("begin");
|
||||
|
||||
let mut message_archive: MessageArchive = HashMap::new();
|
||||
let mut channel_id = 0;
|
||||
|
||||
let mut http_server = HttpServer::new(5);
|
||||
let http_config = HttpBindingConfig::default();
|
||||
|
||||
let ws_config = WsBindingConfig::default();
|
||||
|
||||
http_server.bind_ws_path("/", ws_config.clone());
|
||||
// REST API
|
||||
http_server.bind_http_path("/api", http_config.clone());
|
||||
http_server.serve_ui(&our, "ui", vec!["/"], http_config.clone());
|
||||
|
||||
loop {
|
||||
match handle_message(&our, &mut message_archive, &mut channel_id) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
println!("error: {:?}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
18
ui/.eslintrc.cjs
Normal file
18
ui/.eslintrc.cjs
Normal file
@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
24
ui/.gitignore
vendored
Normal file
24
ui/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# 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?
|
56
ui/README.md
Normal file
56
ui/README.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Kinode UI Template
|
||||
|
||||
Based on the Vite React Typescript template.
|
||||
|
||||
## Setup
|
||||
|
||||
When using `kit new`, the `BASE_URL` on line 9 of `vite.config.ts` will be set automatically.
|
||||
The `BASE_URL` will be the first process in `manifest.json`, the `package` from `metadata.json`, and `publisher` from `metadata.json`.
|
||||
If you have multiple processes in `manifest.json`, make sure the first process will be the one serving the UI.
|
||||
|
||||
## Development
|
||||
|
||||
Run `npm i` and then `npm run dev` to start working on the UI.
|
||||
|
||||
You may see an error:
|
||||
|
||||
```
|
||||
[vite] Pre-transform error: Failed to load url /our.js (resolved id: /our.js). Does the file exist?
|
||||
```
|
||||
|
||||
You can safely ignore this error. The file will be served by the node via the proxy.
|
||||
|
||||
## public vs assets
|
||||
|
||||
The `public/assets` folder contains files that are referenced in `index.html`, `src/assets` is for asset files that are only referenced in `src` code.
|
||||
|
||||
## About Vite + React
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default {
|
||||
// other rules...
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
|
||||
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
|
16
ui/index.html
Normal file
16
ui/index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- This sets window.our.node -->
|
||||
<script src="/our.js"></script>
|
||||
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>抖推</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
3482
ui/package-lock.json
generated
Normal file
3482
ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
ui/package.json
Normal file
37
ui/package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "ui-template",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"copy": "mkdir -p ../pkg/ui && rm -rf ../pkg/ui/* && cp -r dist/* ../pkg/ui/",
|
||||
"build:copy": "npm run build && npm run copy",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kinode/client-api": "^0.1.0",
|
||||
"@tanstack/react-query": "^5.50.1",
|
||||
"hls.js": "^1.5.13",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-swipeable": "^7.0.1",
|
||||
"zustand": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.4",
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.3.1"
|
||||
}
|
||||
}
|
1
ui/public/assets/vite.svg
Normal file
1
ui/public/assets/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
BIN
ui/public/fonts/Charm-Bold.ttf
Executable file
BIN
ui/public/fonts/Charm-Bold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Charm-Regular.ttf
Executable file
BIN
ui/public/fonts/Charm-Regular.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Chonburi-Regular.ttf
Executable file
BIN
ui/public/fonts/Chonburi-Regular.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/IBMPlexSansThai-Bold.ttf
Executable file
BIN
ui/public/fonts/IBMPlexSansThai-Bold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/IBMPlexSansThai-ExtraLight.ttf
Executable file
BIN
ui/public/fonts/IBMPlexSansThai-ExtraLight.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/IBMPlexSansThai-Light.ttf
Executable file
BIN
ui/public/fonts/IBMPlexSansThai-Light.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/IBMPlexSansThai-Medium.ttf
Executable file
BIN
ui/public/fonts/IBMPlexSansThai-Medium.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/IBMPlexSansThai-Regular.ttf
Executable file
BIN
ui/public/fonts/IBMPlexSansThai-Regular.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/IBMPlexSansThai-SemiBold.ttf
Executable file
BIN
ui/public/fonts/IBMPlexSansThai-SemiBold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/IBMPlexSansThai-Thin.ttf
Executable file
BIN
ui/public/fonts/IBMPlexSansThai-Thin.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Judson-Bold.ttf
Executable file
BIN
ui/public/fonts/Judson-Bold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Judson-Italic.ttf
Executable file
BIN
ui/public/fonts/Judson-Italic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Judson-Regular.ttf
Executable file
BIN
ui/public/fonts/Judson-Regular.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-Black.ttf
Executable file
BIN
ui/public/fonts/Kanit-Black.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-BlackItalic.ttf
Executable file
BIN
ui/public/fonts/Kanit-BlackItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-Bold.ttf
Executable file
BIN
ui/public/fonts/Kanit-Bold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-BoldItalic.ttf
Executable file
BIN
ui/public/fonts/Kanit-BoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-ExtraBold.ttf
Executable file
BIN
ui/public/fonts/Kanit-ExtraBold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-ExtraBoldItalic.ttf
Executable file
BIN
ui/public/fonts/Kanit-ExtraBoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-ExtraLight.ttf
Executable file
BIN
ui/public/fonts/Kanit-ExtraLight.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-ExtraLightItalic.ttf
Executable file
BIN
ui/public/fonts/Kanit-ExtraLightItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-Italic.ttf
Executable file
BIN
ui/public/fonts/Kanit-Italic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-Light.ttf
Executable file
BIN
ui/public/fonts/Kanit-Light.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-LightItalic.ttf
Executable file
BIN
ui/public/fonts/Kanit-LightItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-Medium.ttf
Executable file
BIN
ui/public/fonts/Kanit-Medium.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-MediumItalic.ttf
Executable file
BIN
ui/public/fonts/Kanit-MediumItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-Regular.ttf
Executable file
BIN
ui/public/fonts/Kanit-Regular.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-SemiBold.ttf
Executable file
BIN
ui/public/fonts/Kanit-SemiBold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-SemiBoldItalic.ttf
Executable file
BIN
ui/public/fonts/Kanit-SemiBoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-Thin.ttf
Executable file
BIN
ui/public/fonts/Kanit-Thin.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kanit-ThinItalic.ttf
Executable file
BIN
ui/public/fonts/Kanit-ThinItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-Bold.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-Bold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-BoldItalic.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-BoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-ExtraLight.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-ExtraLight.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-ExtraLightItalic.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-ExtraLightItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-Italic.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-Italic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-Light.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-Light.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-LightItalic.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-LightItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-Medium.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-Medium.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-MediumItalic.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-MediumItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-Regular.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-Regular.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-SemiBold.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-SemiBold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Kodchasan-SemiBoldItalic.ttf
Executable file
BIN
ui/public/fonts/Kodchasan-SemiBoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-Bold.ttf
Executable file
BIN
ui/public/fonts/Mali-Bold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-BoldItalic.ttf
Executable file
BIN
ui/public/fonts/Mali-BoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-ExtraLight.ttf
Executable file
BIN
ui/public/fonts/Mali-ExtraLight.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-ExtraLightItalic.ttf
Executable file
BIN
ui/public/fonts/Mali-ExtraLightItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-Italic.ttf
Executable file
BIN
ui/public/fonts/Mali-Italic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-Light.ttf
Executable file
BIN
ui/public/fonts/Mali-Light.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-LightItalic.ttf
Executable file
BIN
ui/public/fonts/Mali-LightItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-Medium.ttf
Executable file
BIN
ui/public/fonts/Mali-Medium.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-MediumItalic.ttf
Executable file
BIN
ui/public/fonts/Mali-MediumItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-Regular.ttf
Executable file
BIN
ui/public/fonts/Mali-Regular.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-SemiBold.ttf
Executable file
BIN
ui/public/fonts/Mali-SemiBold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Mali-SemiBoldItalic.ttf
Executable file
BIN
ui/public/fonts/Mali-SemiBoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/NotoSans-Italic-VariableFont_wdth,wght.ttf
Executable file
BIN
ui/public/fonts/NotoSans-Italic-VariableFont_wdth,wght.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/NotoSans-VariableFont_wdth,wght.ttf
Executable file
BIN
ui/public/fonts/NotoSans-VariableFont_wdth,wght.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-Bold.ttf
Executable file
BIN
ui/public/fonts/Sarabun-Bold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-BoldItalic.ttf
Executable file
BIN
ui/public/fonts/Sarabun-BoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-ExtraBold.ttf
Executable file
BIN
ui/public/fonts/Sarabun-ExtraBold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-ExtraBoldItalic.ttf
Executable file
BIN
ui/public/fonts/Sarabun-ExtraBoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-ExtraLight.ttf
Executable file
BIN
ui/public/fonts/Sarabun-ExtraLight.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-ExtraLightItalic.ttf
Executable file
BIN
ui/public/fonts/Sarabun-ExtraLightItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-Italic.ttf
Executable file
BIN
ui/public/fonts/Sarabun-Italic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-Light.ttf
Executable file
BIN
ui/public/fonts/Sarabun-Light.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-LightItalic.ttf
Executable file
BIN
ui/public/fonts/Sarabun-LightItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-Medium.ttf
Executable file
BIN
ui/public/fonts/Sarabun-Medium.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-MediumItalic.ttf
Executable file
BIN
ui/public/fonts/Sarabun-MediumItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-Regular.ttf
Executable file
BIN
ui/public/fonts/Sarabun-Regular.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-SemiBold.ttf
Executable file
BIN
ui/public/fonts/Sarabun-SemiBold.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-SemiBoldItalic.ttf
Executable file
BIN
ui/public/fonts/Sarabun-SemiBoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-Thin.ttf
Executable file
BIN
ui/public/fonts/Sarabun-Thin.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Sarabun-ThinItalic.ttf
Executable file
BIN
ui/public/fonts/Sarabun-ThinItalic.ttf
Executable file
Binary file not shown.
BIN
ui/public/fonts/Voces-Regular.ttf
Executable file
BIN
ui/public/fonts/Voces-Regular.ttf
Executable file
Binary file not shown.
BIN
ui/public/icon-192-maskable.png
Executable file
BIN
ui/public/icon-192-maskable.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
ui/public/icon-192.png
Executable file
BIN
ui/public/icon-192.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
ui/public/icon-512-maskable.png
Executable file
BIN
ui/public/icon-512-maskable.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
ui/public/icon-512.png
Executable file
BIN
ui/public/icon-512.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user