@t8n/ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,309 @@
1
+ use v8;
2
+ use std::path::PathBuf;
3
+ use std::collections::HashMap;
4
+ use std::fs;
5
+ use std::sync::{Mutex, Arc};
6
+ use walkdir::WalkDir;
7
+ use libloading::Library;
8
+ use crate::utils::{blue, green, red};
9
+ use super::{TitanRuntime, v8_str, throw};
10
+ use serde_json::Value;
11
+
12
+ pub static REGISTRY: Mutex<Option<Registry>> = Mutex::new(None);
13
+
14
+ #[allow(dead_code)]
15
+ pub struct Registry {
16
+ pub _libs: Vec<Library>,
17
+ pub modules: Vec<ModuleDef>,
18
+ pub natives: Vec<NativeFnEntry>,
19
+ }
20
+
21
+ #[derive(Clone)]
22
+ pub struct ModuleDef {
23
+ pub name: String,
24
+ pub js: String,
25
+ pub native_indices: HashMap<String, usize>,
26
+ }
27
+
28
+ #[derive(Clone, Debug, PartialEq)]
29
+ pub enum ParamType {
30
+ String, F64, Bool, Json, Buffer,
31
+ }
32
+
33
+ #[derive(Clone, Debug, PartialEq)]
34
+ pub enum ReturnType {
35
+ String, F64, Bool, Json, Buffer, Void,
36
+ }
37
+
38
+ #[derive(Clone, Debug)]
39
+ pub struct Signature {
40
+ pub params: Vec<ParamType>,
41
+ pub ret: ReturnType,
42
+ }
43
+
44
+ pub struct NativeFnEntry {
45
+ pub symbol_ptr: usize,
46
+ pub sig: Signature,
47
+ }
48
+
49
+ #[derive(serde::Deserialize)]
50
+ struct TitanConfig {
51
+ name: String,
52
+ main: String,
53
+ native: Option<TitanNativeConfig>,
54
+ }
55
+
56
+ #[derive(serde::Deserialize)]
57
+ struct TitanNativeConfig {
58
+ path: String,
59
+ functions: HashMap<String, TitanNativeFunc>,
60
+ }
61
+
62
+ #[derive(serde::Deserialize)]
63
+ struct TitanNativeFunc {
64
+ symbol: String,
65
+ #[serde(default)]
66
+ parameters: Vec<String>,
67
+ #[serde(default)]
68
+ result: String,
69
+ }
70
+
71
+ fn parse_type(s: &str) -> ParamType {
72
+ match s {
73
+ "string" => ParamType::String,
74
+ "f64" => ParamType::F64,
75
+ "bool" => ParamType::Bool,
76
+ "json" => ParamType::Json,
77
+ "buffer" => ParamType::Buffer,
78
+ _ => ParamType::Json,
79
+ }
80
+ }
81
+
82
+ fn parse_return(s: &str) -> ReturnType {
83
+ match s {
84
+ "string" => ReturnType::String,
85
+ "f64" => ReturnType::F64,
86
+ "bool" => ReturnType::Bool,
87
+ "json" => ReturnType::Json,
88
+ "buffer" => ReturnType::Buffer,
89
+ "void" => ReturnType::Void,
90
+ _ => ReturnType::Void,
91
+ }
92
+ }
93
+
94
+ pub fn load_project_extensions(root: PathBuf) {
95
+ let mut modules = Vec::new();
96
+ let mut libs = Vec::new();
97
+ let mut all_natives = Vec::new();
98
+
99
+ let mut node_modules = root.join("node_modules");
100
+ if !node_modules.exists() {
101
+ if let Some(parent) = root.parent() {
102
+ let parent_modules = parent.join("node_modules");
103
+ if parent_modules.exists() { node_modules = parent_modules; }
104
+ }
105
+ }
106
+
107
+ if node_modules.exists() {
108
+ for entry in WalkDir::new(&node_modules).follow_links(true).min_depth(1).max_depth(4) {
109
+ let entry = match entry { Ok(e) => e, Err(_) => continue };
110
+ if entry.file_type().is_file() && entry.file_name() == "titan.json" {
111
+ let dir = entry.path().parent().unwrap();
112
+ let config_content = fs::read_to_string(entry.path()).unwrap_or_default();
113
+ let config: TitanConfig = match serde_json::from_str(&config_content) {
114
+ Ok(c) => c,
115
+ Err(_) => continue,
116
+ };
117
+ let mut mod_natives_map = HashMap::new();
118
+ if let Some(native_conf) = config.native {
119
+ let lib_path = dir.join(&native_conf.path);
120
+ unsafe {
121
+ if let Ok(lib) = Library::new(&lib_path) {
122
+ for (fn_name, fn_conf) in native_conf.functions {
123
+ let params = fn_conf.parameters.iter().map(|p| parse_type(&p.to_lowercase())).collect();
124
+ let ret = parse_return(&fn_conf.result.to_lowercase());
125
+ if let Ok(symbol) = lib.get::<*const ()>(fn_conf.symbol.as_bytes()) {
126
+ let idx = all_natives.len();
127
+ all_natives.push(NativeFnEntry { symbol_ptr: *symbol as usize, sig: Signature { params, ret } });
128
+ mod_natives_map.insert(fn_name, idx);
129
+ }
130
+ }
131
+ libs.push(lib);
132
+ }
133
+ }
134
+ }
135
+ let js_path = dir.join(&config.main);
136
+ modules.push(ModuleDef { name: config.name.clone(), js: fs::read_to_string(js_path).unwrap_or_default(), native_indices: mod_natives_map });
137
+ println!("{} {} {}", blue("[Titan]"), green("Extension loaded:"), config.name);
138
+ }
139
+ }
140
+ }
141
+ *REGISTRY.lock().unwrap() = Some(Registry { _libs: libs, modules, natives: all_natives });
142
+ }
143
+
144
+ pub fn inject_external_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Object>, t_obj: v8::Local<v8::Object>) {
145
+ let invoke_fn = v8::Function::new(scope, native_invoke_extension).unwrap();
146
+ let invoke_key = v8_str(scope, "__titan_invoke_native");
147
+ global.set(scope, invoke_key.into(), invoke_fn.into());
148
+
149
+ let modules = if let Ok(guard) = REGISTRY.lock() {
150
+ guard.as_ref().map(|r| r.modules.clone()).unwrap_or_default()
151
+ } else { vec![] };
152
+
153
+ for module in modules {
154
+ let mod_obj = v8::Object::new(scope);
155
+ for (fn_name, &idx) in &module.native_indices {
156
+ let code = format!("(function(...args) {{ return __titan_invoke_native({}, args); }})", idx);
157
+ let code_str = v8_str(scope, &code);
158
+ if let Some(script) = v8::Script::compile(scope, code_str, None) {
159
+ if let Some(val) = script.run(scope) {
160
+ let key = v8_str(scope, fn_name);
161
+ mod_obj.set(scope, key.into(), val);
162
+ }
163
+ }
164
+ }
165
+ let mod_key = v8_str(scope, &module.name);
166
+ t_obj.set(scope, mod_key.into(), mod_obj.into());
167
+
168
+ let act_key = v8_str(scope, "__titan_action");
169
+ let act_val = v8_str(scope, &module.name);
170
+ global.set(scope, act_key.into(), act_val.into());
171
+
172
+ let wrapped_js = format!("(function(t) {{ {} }})", module.js);
173
+ let wrapped_js_str = v8_str(scope, &wrapped_js);
174
+ let tc = &mut v8::TryCatch::new(scope);
175
+ if let Some(script) = v8::Script::compile(tc, wrapped_js_str, None) {
176
+ if let Some(func_val) = script.run(tc) {
177
+ if let Ok(func) = v8::Local::<v8::Function>::try_from(func_val) {
178
+ let receiver = v8::undefined(&mut *tc).into();
179
+ let args = [t_obj.into()];
180
+ func.call(&mut *tc, receiver, &args);
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
188
+ let fn_idx = args.get(0).to_integer(scope).unwrap().value() as usize;
189
+ let js_args_val = args.get(1);
190
+ let (ptr, sig) = if let Ok(guard) = REGISTRY.lock() {
191
+ if let Some(entry) = guard.as_ref().and_then(|r| r.natives.get(fn_idx)) {
192
+ (entry.symbol_ptr, entry.sig.clone())
193
+ } else { return; }
194
+ } else { return; };
195
+
196
+ if ptr == 0 { throw(scope, "Native function not found"); return; }
197
+
198
+ let js_args = if js_args_val.is_array() {
199
+ v8::Local::<v8::Array>::try_from(js_args_val).unwrap()
200
+ } else { v8::Array::new(scope, 0) };
201
+
202
+ let argc = sig.params.len();
203
+ unsafe {
204
+ let mut vals = Vec::new();
205
+ for (i, param) in sig.params.iter().enumerate() {
206
+ let val = js_args.get_index(scope, i as u32).unwrap_or_else(|| v8::undefined(scope).into());
207
+ vals.push(arg_from_v8(scope, val, param));
208
+ }
209
+
210
+ let res_val: serde_json::Value = match argc {
211
+ 0 => { dispatch_ret!(ptr, sig.ret, (), ()) },
212
+ 1 => {
213
+ let v0 = vals.remove(0);
214
+ match sig.params[0] {
215
+ ParamType::String => {
216
+ let c = std::ffi::CString::new(v0.as_str().unwrap_or("")).unwrap();
217
+ dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
218
+ },
219
+ ParamType::F64 => { dispatch_ret!(ptr, sig.ret, (f64), (v0.as_f64().unwrap_or(0.0))) },
220
+ ParamType::Bool => { dispatch_ret!(ptr, sig.ret, (bool), (v0.as_bool().unwrap_or(false))) },
221
+ ParamType::Json => {
222
+ let c = std::ffi::CString::new(v0.to_string()).unwrap();
223
+ dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
224
+ },
225
+ ParamType::Buffer => {
226
+ let a0: Vec<u8> = v0.as_array().map(|a| a.iter().map(|v| v.as_u64().unwrap_or(0) as u8).collect()).unwrap_or_default();
227
+ dispatch_ret!(ptr, sig.ret, (Vec<u8>), (a0))
228
+ },
229
+ }
230
+ },
231
+ 2 => {
232
+ let v0 = vals.remove(0); let v1 = vals.remove(0);
233
+ match (sig.params[0].clone(), sig.params[1].clone()) {
234
+ (ParamType::String, ParamType::String) => {
235
+ let c0 = std::ffi::CString::new(v0.as_str().unwrap_or("")).unwrap();
236
+ let c1 = std::ffi::CString::new(v1.as_str().unwrap_or("")).unwrap();
237
+ dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, *const std::os::raw::c_char), (c0.as_ptr(), c1.as_ptr()))
238
+ },
239
+ (ParamType::String, ParamType::F64) => {
240
+ let c0 = std::ffi::CString::new(v0.as_str().unwrap_or("")).unwrap();
241
+ dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, f64), (c0.as_ptr(), v1.as_f64().unwrap_or(0.0)))
242
+ },
243
+ _ => serde_json::Value::Null
244
+ }
245
+ },
246
+ _ => serde_json::Value::Null
247
+ };
248
+ retval.set(js_from_value(scope, &sig.ret, res_val));
249
+ }
250
+ }
251
+
252
+ fn arg_from_v8(scope: &mut v8::HandleScope, val: v8::Local<v8::Value>, ty: &ParamType) -> serde_json::Value {
253
+ match ty {
254
+ ParamType::String => serde_json::Value::String(val.to_rust_string_lossy(scope)),
255
+ ParamType::F64 => serde_json::json!(val.to_number(scope).map(|n| n.value()).unwrap_or(0.0)),
256
+ ParamType::Bool => serde_json::json!(val.boolean_value(scope)),
257
+ ParamType::Json => {
258
+ v8::json::stringify(scope, val).map(|s| serde_json::from_str(&s.to_rust_string_lossy(scope)).unwrap_or(Value::Null)).unwrap_or(Value::Null)
259
+ },
260
+ ParamType::Buffer => {
261
+ if let Ok(u8arr) = v8::Local::<v8::Uint8Array>::try_from(val) {
262
+ let store = v8::ArrayBuffer::get_backing_store(&u8arr.buffer(scope).unwrap());
263
+ let offset = usize::from(u8arr.byte_offset());
264
+ let length = usize::from(u8arr.byte_length());
265
+ let vec_u8: Vec<Value> = store[offset..offset+length].iter().map(|b| Value::from(b.get() as u64)).collect();
266
+ Value::Array(vec_u8)
267
+ } else { Value::Array(vec![]) }
268
+ }
269
+ }
270
+ }
271
+
272
+ fn js_from_value<'a>(scope: &mut v8::HandleScope<'a>, ret_type: &ReturnType, val: serde_json::Value) -> v8::Local<'a, v8::Value> {
273
+ match ret_type {
274
+ ReturnType::String => v8_str(scope, val.as_str().unwrap_or("")).into(),
275
+ ReturnType::F64 => v8::Number::new(scope, val.as_f64().unwrap_or(0.0)).into(),
276
+ ReturnType::Bool => v8::Boolean::new(scope, val.as_bool().unwrap_or(false)).into(),
277
+ ReturnType::Json => {
278
+ let s = v8_str(scope, &val.to_string());
279
+ v8::json::parse(scope, s).unwrap_or_else(|| v8::null(scope).into())
280
+ },
281
+ ReturnType::Buffer => v8::undefined(scope).into(),
282
+ ReturnType::Void => v8::undefined(scope).into(),
283
+ }
284
+ }
285
+
286
+ macro_rules! dispatch_ret {
287
+ ($ptr:expr, $ret:expr, ($($arg_ty:ty),*), ($($arg:expr),*)) => {
288
+ match $ret {
289
+ ReturnType::String => {
290
+ let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char = unsafe { std::mem::transmute($ptr) };
291
+ let ptr = f($($arg),*);
292
+ if ptr.is_null() { Value::String(String::new()) } else { Value::String(unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() }) }
293
+ },
294
+ ReturnType::F64 => { let f: extern "C" fn($($arg_ty),*) -> f64 = unsafe { std::mem::transmute($ptr) }; serde_json::json!(f($($arg),*)) },
295
+ ReturnType::Bool => { let f: extern "C" fn($($arg_ty),*) -> bool = unsafe { std::mem::transmute($ptr) }; serde_json::json!(f($($arg),*)) },
296
+ ReturnType::Json => {
297
+ let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char = unsafe { std::mem::transmute($ptr) };
298
+ let ptr = f($($arg),*);
299
+ if ptr.is_null() { Value::Null } else { serde_json::from_str(&unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy() }).unwrap_or(Value::Null) }
300
+ },
301
+ ReturnType::Buffer => {
302
+ let f: extern "C" fn($($arg_ty),*) -> Vec<u8> = unsafe { std::mem::transmute($ptr) };
303
+ Value::Array(f($($arg),*).into_iter().map(Value::from).collect())
304
+ },
305
+ ReturnType::Void => { let f: extern "C" fn($($arg_ty),*) = unsafe { std::mem::transmute($ptr) }; f($($arg),*); Value::Null },
306
+ }
307
+ }
308
+ }
309
+ pub(crate) use dispatch_ret;