titan-sdk 0.0.3 → 0.0.5
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.
- package/README.md +52 -57
- package/bin/run.js +25 -9
- package/package.json +10 -2
- package/templates/.dockerignore +3 -0
- package/templates/Dockerfile +53 -0
- package/templates/app/actions/hello.js +5 -0
- package/templates/app/app.js +10 -0
- package/templates/app/titan.d.ts +87 -0
- package/templates/jsconfig.json +19 -0
- package/templates/server/Cargo.lock +2839 -0
- package/templates/server/Cargo.toml +27 -0
- package/templates/server/src/action_management.rs +131 -0
- package/templates/server/src/errors.rs +10 -0
- package/templates/server/src/extensions.rs +640 -0
- package/templates/server/src/main.rs +345 -0
- package/templates/server/src/utils.rs +33 -0
- package/templates/titan/bundle.js +65 -0
- package/templates/titan/dev.js +110 -0
- package/templates/titan/titan.js +82 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
#![allow(unused)]
|
|
2
|
+
use v8;
|
|
3
|
+
use reqwest::{
|
|
4
|
+
blocking::Client,
|
|
5
|
+
header::{HeaderMap, HeaderName, HeaderValue},
|
|
6
|
+
};
|
|
7
|
+
use std::sync::Once;
|
|
8
|
+
use std::path::PathBuf;
|
|
9
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
10
|
+
use serde_json::Value;
|
|
11
|
+
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
|
|
12
|
+
use bcrypt::{hash, verify, DEFAULT_COST};
|
|
13
|
+
|
|
14
|
+
use crate::utils::{blue, gray, green, parse_expires_in};
|
|
15
|
+
use libloading::{Library};
|
|
16
|
+
use walkdir::WalkDir;
|
|
17
|
+
use std::sync::Mutex;
|
|
18
|
+
use std::collections::HashMap;
|
|
19
|
+
use std::fs;
|
|
20
|
+
|
|
21
|
+
// ----------------------------------------------------------------------------
|
|
22
|
+
// GLOBAL REGISTRY
|
|
23
|
+
// ----------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
static REGISTRY: Mutex<Option<Registry>> = Mutex::new(None);
|
|
26
|
+
#[allow(dead_code)]
|
|
27
|
+
struct Registry {
|
|
28
|
+
_libs: Vec<Library>,
|
|
29
|
+
modules: Vec<ModuleDef>,
|
|
30
|
+
natives: Vec<NativeFnEntry>, // Flattened list of all native functions
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[derive(Clone)]
|
|
34
|
+
struct ModuleDef {
|
|
35
|
+
name: String,
|
|
36
|
+
js: String,
|
|
37
|
+
native_indices: HashMap<String, usize>, // Function Name -> Index in REGISTRY.natives
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
struct NativeFnEntry {
|
|
41
|
+
ptr: usize,
|
|
42
|
+
sig: Signature,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[derive(Clone, Copy)]
|
|
46
|
+
enum Signature {
|
|
47
|
+
F64TwoArgsRetF64,
|
|
48
|
+
Unknown,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[derive(serde::Deserialize)]
|
|
52
|
+
struct TitanConfig {
|
|
53
|
+
name: String,
|
|
54
|
+
main: String,
|
|
55
|
+
native: Option<TitanNativeConfig>,
|
|
56
|
+
}
|
|
57
|
+
#[derive(serde::Deserialize)]
|
|
58
|
+
struct TitanNativeConfig {
|
|
59
|
+
path: String,
|
|
60
|
+
functions: HashMap<String, TitanNativeFunc>,
|
|
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
|
+
pub fn load_project_extensions(root: PathBuf) {
|
|
72
|
+
let mut modules = Vec::new();
|
|
73
|
+
let mut libs = Vec::new();
|
|
74
|
+
let mut all_natives = Vec::new();
|
|
75
|
+
|
|
76
|
+
let mut node_modules = root.join("node_modules");
|
|
77
|
+
if !node_modules.exists() {
|
|
78
|
+
if let Some(parent) = root.parent() {
|
|
79
|
+
let parent_modules = parent.join("node_modules");
|
|
80
|
+
if parent_modules.exists() {
|
|
81
|
+
node_modules = parent_modules;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if node_modules.exists() {
|
|
87
|
+
for entry in WalkDir::new(&node_modules).min_depth(1).max_depth(2) {
|
|
88
|
+
let entry = match entry { Ok(e) => e, Err(_) => continue };
|
|
89
|
+
if entry.file_type().is_file() && entry.file_name() == "titan.json" {
|
|
90
|
+
let dir = entry.path().parent().unwrap();
|
|
91
|
+
let config_content = match fs::read_to_string(entry.path()) {
|
|
92
|
+
Ok(c) => c,
|
|
93
|
+
Err(_) => continue,
|
|
94
|
+
};
|
|
95
|
+
let config: TitanConfig = match serde_json::from_str(&config_content) {
|
|
96
|
+
Ok(c) => c,
|
|
97
|
+
Err(_) => continue,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
let mut mod_natives_map = HashMap::new();
|
|
101
|
+
|
|
102
|
+
if let Some(native_conf) = config.native {
|
|
103
|
+
let lib_path = dir.join(&native_conf.path);
|
|
104
|
+
unsafe {
|
|
105
|
+
match Library::new(&lib_path) {
|
|
106
|
+
Ok(lib) => {
|
|
107
|
+
for (fn_name, fn_conf) in native_conf.functions {
|
|
108
|
+
let sig = if fn_conf.parameters.len() == 2
|
|
109
|
+
&& fn_conf.parameters[0] == "f64"
|
|
110
|
+
&& fn_conf.parameters[1] == "f64"
|
|
111
|
+
&& fn_conf.result == "f64" {
|
|
112
|
+
Signature::F64TwoArgsRetF64
|
|
113
|
+
} else {
|
|
114
|
+
Signature::Unknown
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if let Ok(symbol) = lib.get::<*const ()>(fn_conf.symbol.as_bytes()) {
|
|
118
|
+
let idx = all_natives.len();
|
|
119
|
+
all_natives.push(NativeFnEntry {
|
|
120
|
+
ptr: *symbol as usize,
|
|
121
|
+
sig
|
|
122
|
+
});
|
|
123
|
+
mod_natives_map.insert(fn_name, idx);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
libs.push(lib);
|
|
127
|
+
},
|
|
128
|
+
Err(e) => println!("Failed to load extension library {}: {}", lib_path.display(), e),
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let js_path = dir.join(&config.main);
|
|
134
|
+
let js_content = fs::read_to_string(js_path).unwrap_or_default();
|
|
135
|
+
|
|
136
|
+
modules.push(ModuleDef {
|
|
137
|
+
name: config.name.clone(),
|
|
138
|
+
js: js_content,
|
|
139
|
+
native_indices: mod_natives_map,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
println!("{} {} {}", blue("[Titan]"), green("Extension loaded:"), config.name);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
*REGISTRY.lock().unwrap() = Some(Registry { _libs: libs, modules, natives: all_natives });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
static V8_INIT: Once = Once::new();
|
|
152
|
+
|
|
153
|
+
pub fn init_v8() {
|
|
154
|
+
V8_INIT.call_once(|| {
|
|
155
|
+
let platform = v8::new_default_platform(0, false).make_shared();
|
|
156
|
+
v8::V8::initialize_platform(platform);
|
|
157
|
+
v8::V8::initialize();
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn v8_str<'s>(scope: &mut v8::HandleScope<'s>, s: &str) -> v8::Local<'s, v8::String> {
|
|
162
|
+
v8::String::new(scope, s).unwrap()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fn v8_to_string(scope: &mut v8::HandleScope, value: v8::Local<v8::Value>) -> String {
|
|
166
|
+
value.to_string(scope).unwrap().to_rust_string_lossy(scope)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn throw(scope: &mut v8::HandleScope, msg: &str) {
|
|
170
|
+
let message = v8_str(scope, msg);
|
|
171
|
+
let exception = v8::Exception::error(scope, message);
|
|
172
|
+
scope.throw_exception(exception);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ----------------------------------------------------------------------------
|
|
176
|
+
// NATIVE CALLBACKS
|
|
177
|
+
// ----------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
fn native_read(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
180
|
+
let path_val = args.get(0);
|
|
181
|
+
// 1. Read argument
|
|
182
|
+
if !path_val.is_string() {
|
|
183
|
+
throw(scope, "t.read(path): path is required");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
let path_str = v8_to_string(scope, path_val);
|
|
187
|
+
|
|
188
|
+
// 2. Check if absolute
|
|
189
|
+
if std::path::Path::new(&path_str).is_absolute() {
|
|
190
|
+
throw(scope, "t.read expects a relative path like 'db/file.sql'");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let context = scope.get_current_context();
|
|
195
|
+
let global = context.global(scope);
|
|
196
|
+
let root_key = v8_str(scope, "__titan_root");
|
|
197
|
+
let root_val = global.get(scope, root_key.into()).unwrap();
|
|
198
|
+
|
|
199
|
+
let root_str = if root_val.is_string() {
|
|
200
|
+
v8_to_string(scope, root_val)
|
|
201
|
+
} else {
|
|
202
|
+
throw(scope, "Internal Error: __titan_root not set");
|
|
203
|
+
return;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
let root_path = PathBuf::from(root_str);
|
|
207
|
+
let root_path = root_path.canonicalize().unwrap_or(root_path);
|
|
208
|
+
let joined = root_path.join(&path_str);
|
|
209
|
+
|
|
210
|
+
// 3. Canonicalize (resolves ../)
|
|
211
|
+
let target = match joined.canonicalize() {
|
|
212
|
+
Ok(t) => t,
|
|
213
|
+
Err(_) => {
|
|
214
|
+
throw(scope, &format!("t.read: file not found: {}", path_str));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// 4. Enforce root boundary
|
|
220
|
+
if !target.starts_with(&root_path) {
|
|
221
|
+
throw(scope, "t.read: path escapes allowed root");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 5. Read file
|
|
226
|
+
match std::fs::read_to_string(&target) {
|
|
227
|
+
Ok(content) => {
|
|
228
|
+
retval.set(v8_str(scope, &content).into());
|
|
229
|
+
},
|
|
230
|
+
Err(e) => {
|
|
231
|
+
throw(scope, &format!("t.read failed: {}", e));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fn native_log(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut _retval: v8::ReturnValue) {
|
|
237
|
+
let context = scope.get_current_context();
|
|
238
|
+
let global = context.global(scope);
|
|
239
|
+
let action_key = v8_str(scope, "__titan_action");
|
|
240
|
+
let action_val = global.get(scope, action_key.into()).unwrap();
|
|
241
|
+
let action_name = v8_to_string(scope, action_val);
|
|
242
|
+
|
|
243
|
+
let mut parts = Vec::new();
|
|
244
|
+
for i in 0..args.length() {
|
|
245
|
+
let val = args.get(i);
|
|
246
|
+
let mut appended = false;
|
|
247
|
+
|
|
248
|
+
// Try to JSON stringify objects so they are readable in logs
|
|
249
|
+
if val.is_object() && !val.is_function() {
|
|
250
|
+
if let Some(json) = v8::json::stringify(scope, val) {
|
|
251
|
+
parts.push(json.to_rust_string_lossy(scope));
|
|
252
|
+
appended = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if !appended {
|
|
257
|
+
parts.push(v8_to_string(scope, val));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
println!(
|
|
262
|
+
"{} {}",
|
|
263
|
+
blue("[Titan]"),
|
|
264
|
+
gray(&format!("\x1b[90mlog({})\x1b[0m\x1b[97m: {}\x1b[0m", action_name, parts.join(" ")))
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
fn native_fetch(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
269
|
+
let url = v8_to_string(scope, args.get(0));
|
|
270
|
+
|
|
271
|
+
// Check for options (method, headers, body)
|
|
272
|
+
let mut method = "GET".to_string();
|
|
273
|
+
let mut body_str = None;
|
|
274
|
+
let mut headers_vec = Vec::new();
|
|
275
|
+
|
|
276
|
+
let opts_val = args.get(1);
|
|
277
|
+
if opts_val.is_object() {
|
|
278
|
+
let opts_obj = opts_val.to_object(scope).unwrap();
|
|
279
|
+
|
|
280
|
+
// method
|
|
281
|
+
let m_key = v8_str(scope, "method");
|
|
282
|
+
if let Some(m_val) = opts_obj.get(scope, m_key.into()) {
|
|
283
|
+
if m_val.is_string() {
|
|
284
|
+
method = v8_to_string(scope, m_val);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// body
|
|
289
|
+
let b_key = v8_str(scope, "body");
|
|
290
|
+
if let Some(b_val) = opts_obj.get(scope, b_key.into()) {
|
|
291
|
+
if b_val.is_string() {
|
|
292
|
+
body_str = Some(v8_to_string(scope, b_val));
|
|
293
|
+
} else if b_val.is_object() {
|
|
294
|
+
let json_obj = v8::json::stringify(scope, b_val).unwrap();
|
|
295
|
+
body_str = Some(json_obj.to_rust_string_lossy(scope));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// headers
|
|
300
|
+
let h_key = v8_str(scope, "headers");
|
|
301
|
+
if let Some(h_val) = opts_obj.get(scope, h_key.into()) {
|
|
302
|
+
if h_val.is_object() {
|
|
303
|
+
let h_obj = h_val.to_object(scope).unwrap();
|
|
304
|
+
if let Some(keys) = h_obj.get_own_property_names(scope, Default::default()) {
|
|
305
|
+
for i in 0..keys.length() {
|
|
306
|
+
let key = keys.get_index(scope, i).unwrap();
|
|
307
|
+
let val = h_obj.get(scope, key).unwrap();
|
|
308
|
+
headers_vec.push((
|
|
309
|
+
v8_to_string(scope, key),
|
|
310
|
+
v8_to_string(scope, val),
|
|
311
|
+
));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let client = Client::builder().use_rustls_tls().tcp_nodelay(true).build().unwrap_or(Client::new());
|
|
319
|
+
|
|
320
|
+
let mut req = client.request(method.parse().unwrap_or(reqwest::Method::GET), &url);
|
|
321
|
+
|
|
322
|
+
for (k, v) in headers_vec {
|
|
323
|
+
if let (Ok(name), Ok(val)) = (HeaderName::from_bytes(k.as_bytes()), HeaderValue::from_str(&v)) {
|
|
324
|
+
let mut map = HeaderMap::new();
|
|
325
|
+
map.insert(name, val);
|
|
326
|
+
req = req.headers(map);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if let Some(b) = body_str {
|
|
331
|
+
req = req.body(b);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let res = req.send();
|
|
335
|
+
|
|
336
|
+
let obj = v8::Object::new(scope);
|
|
337
|
+
match res {
|
|
338
|
+
Ok(r) => {
|
|
339
|
+
let status = r.status().as_u16();
|
|
340
|
+
let text = r.text().unwrap_or_default();
|
|
341
|
+
|
|
342
|
+
let status_key = v8_str(scope, "status");
|
|
343
|
+
let status_val = v8::Number::new(scope, status as f64);
|
|
344
|
+
obj.set(scope, status_key.into(), status_val.into());
|
|
345
|
+
|
|
346
|
+
let body_key = v8_str(scope, "body");
|
|
347
|
+
let body_val = v8_str(scope, &text);
|
|
348
|
+
obj.set(scope, body_key.into(), body_val.into());
|
|
349
|
+
|
|
350
|
+
let ok_key = v8_str(scope, "ok");
|
|
351
|
+
let ok_val = v8::Boolean::new(scope, true);
|
|
352
|
+
obj.set(scope, ok_key.into(), ok_val.into());
|
|
353
|
+
},
|
|
354
|
+
Err(e) => {
|
|
355
|
+
let ok_key = v8_str(scope, "ok");
|
|
356
|
+
let ok_val = v8::Boolean::new(scope, false);
|
|
357
|
+
obj.set(scope, ok_key.into(), ok_val.into());
|
|
358
|
+
|
|
359
|
+
let err_key = v8_str(scope, "error");
|
|
360
|
+
let err_val = v8_str(scope, &e.to_string());
|
|
361
|
+
obj.set(scope, err_key.into(), err_val.into());
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
retval.set(obj.into());
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
fn native_jwt_sign(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
368
|
+
// payload, secret, options
|
|
369
|
+
let payload_val = args.get(0);
|
|
370
|
+
// Parse payload to serde_json::Map
|
|
371
|
+
let json_str = v8::json::stringify(scope, payload_val).unwrap().to_rust_string_lossy(scope);
|
|
372
|
+
let mut payload: serde_json::Map<String, Value> = serde_json::from_str(&json_str).unwrap_or_default();
|
|
373
|
+
|
|
374
|
+
let secret = v8_to_string(scope, args.get(1));
|
|
375
|
+
|
|
376
|
+
let opts_val = args.get(2);
|
|
377
|
+
if opts_val.is_object() {
|
|
378
|
+
let opts_obj = opts_val.to_object(scope).unwrap();
|
|
379
|
+
let exp_key = v8_str(scope, "expiresIn");
|
|
380
|
+
|
|
381
|
+
if let Some(val) = opts_obj.get(scope, exp_key.into()) {
|
|
382
|
+
let seconds = if val.is_number() {
|
|
383
|
+
Some(val.to_number(scope).unwrap().value() as u64)
|
|
384
|
+
} else if val.is_string() {
|
|
385
|
+
parse_expires_in(&v8_to_string(scope, val))
|
|
386
|
+
} else {
|
|
387
|
+
None
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
if let Some(sec) = seconds {
|
|
391
|
+
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
|
392
|
+
payload.insert("exp".to_string(), Value::Number(serde_json::Number::from(now + sec)));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let token = encode(
|
|
398
|
+
&Header::default(),
|
|
399
|
+
&Value::Object(payload),
|
|
400
|
+
&EncodingKey::from_secret(secret.as_bytes()),
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
match token {
|
|
404
|
+
Ok(t) => retval.set(v8_str(scope, &t).into()),
|
|
405
|
+
Err(e) => throw(scope, &e.to_string()),
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
fn native_jwt_verify(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
410
|
+
let token = v8_to_string(scope, args.get(0));
|
|
411
|
+
let secret = v8_to_string(scope, args.get(1));
|
|
412
|
+
|
|
413
|
+
let mut validation = Validation::default();
|
|
414
|
+
validation.validate_exp = true;
|
|
415
|
+
|
|
416
|
+
let data = decode::<Value>(
|
|
417
|
+
&token,
|
|
418
|
+
&DecodingKey::from_secret(secret.as_bytes()),
|
|
419
|
+
&validation,
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
match data {
|
|
423
|
+
Ok(d) => {
|
|
424
|
+
// Convert claim back to V8 object via JSON
|
|
425
|
+
let json_str = serde_json::to_string(&d.claims).unwrap();
|
|
426
|
+
let v8_json_str = v8_str(scope, &json_str);
|
|
427
|
+
if let Some(val) = v8::json::parse(scope, v8_json_str) {
|
|
428
|
+
retval.set(val);
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
Err(e) => throw(scope, &format!("Invalid or expired JWT: {}", e)),
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
fn native_password_hash(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
436
|
+
let pw = v8_to_string(scope, args.get(0));
|
|
437
|
+
match hash(pw, DEFAULT_COST) {
|
|
438
|
+
Ok(h) => retval.set(v8_str(scope, &h).into()),
|
|
439
|
+
Err(e) => throw(scope, &e.to_string()),
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
fn native_password_verify(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
444
|
+
let pw = v8_to_string(scope, args.get(0));
|
|
445
|
+
let hash_str = v8_to_string(scope, args.get(1));
|
|
446
|
+
|
|
447
|
+
let ok = verify(pw, &hash_str).unwrap_or(false);
|
|
448
|
+
retval.set(v8::Boolean::new(scope, ok).into());
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
fn native_define_action(_scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
452
|
+
retval.set(args.get(0));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ----------------------------------------------------------------------------
|
|
456
|
+
// NATIVE CALLBACKS (EXTENSIONS)
|
|
457
|
+
// ----------------------------------------------------------------------------
|
|
458
|
+
|
|
459
|
+
// generic wrappers could go here if needed
|
|
460
|
+
|
|
461
|
+
fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
462
|
+
let fn_idx = args.get(0).to_integer(scope).unwrap().value() as usize;
|
|
463
|
+
|
|
464
|
+
// Get pointer from registry
|
|
465
|
+
let mut ptr = 0;
|
|
466
|
+
let mut sig = Signature::Unknown;
|
|
467
|
+
|
|
468
|
+
if let Ok(guard) = REGISTRY.lock() {
|
|
469
|
+
if let Some(registry) = &*guard {
|
|
470
|
+
if let Some(entry) = registry.natives.get(fn_idx) {
|
|
471
|
+
ptr = entry.ptr;
|
|
472
|
+
sig = entry.sig;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if ptr == 0 {
|
|
478
|
+
throw(scope, "Native function not found");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
match sig {
|
|
483
|
+
Signature::F64TwoArgsRetF64 => {
|
|
484
|
+
let a = args.get(1).to_number(scope).unwrap_or(v8::Number::new(scope, 0.0)).value();
|
|
485
|
+
let b = args.get(2).to_number(scope).unwrap_or(v8::Number::new(scope, 0.0)).value();
|
|
486
|
+
|
|
487
|
+
unsafe {
|
|
488
|
+
let func: extern "C" fn(f64, f64) -> f64 = std::mem::transmute(ptr);
|
|
489
|
+
let res = func(a, b);
|
|
490
|
+
retval.set(v8::Number::new(scope, res).into());
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
_ => throw(scope, "Unsupported signature"),
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
// ----------------------------------------------------------------------------
|
|
499
|
+
// INJECTOR
|
|
500
|
+
// ----------------------------------------------------------------------------
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Object>) {
|
|
504
|
+
// Ensure globalThis reference
|
|
505
|
+
let gt_key = v8_str(scope, "globalThis");
|
|
506
|
+
global.set(scope, gt_key.into(), global.into());
|
|
507
|
+
|
|
508
|
+
let t_obj = v8::Object::new(scope);
|
|
509
|
+
let t_key = v8_str(scope, "t");
|
|
510
|
+
// Use create_data_property to guarantee definition
|
|
511
|
+
global.create_data_property(scope, t_key.into(), t_obj.into()).unwrap();
|
|
512
|
+
|
|
513
|
+
// defineAction (identity function for clean typing)
|
|
514
|
+
let def_fn = v8::Function::new(scope, native_define_action).unwrap();
|
|
515
|
+
let def_key = v8_str(scope, "defineAction");
|
|
516
|
+
global.set(scope, def_key.into(), def_fn.into());
|
|
517
|
+
|
|
518
|
+
// t.read
|
|
519
|
+
let read_fn = v8::Function::new(scope, native_read).unwrap();
|
|
520
|
+
let read_key = v8_str(scope, "read");
|
|
521
|
+
t_obj.set(scope, read_key.into(), read_fn.into());
|
|
522
|
+
|
|
523
|
+
// t.log
|
|
524
|
+
let log_fn = v8::Function::new(scope, native_log).unwrap();
|
|
525
|
+
let log_key = v8_str(scope, "log");
|
|
526
|
+
t_obj.set(scope, log_key.into(), log_fn.into());
|
|
527
|
+
|
|
528
|
+
// t.fetch
|
|
529
|
+
let fetch_fn = v8::Function::new(scope, native_fetch).unwrap();
|
|
530
|
+
let fetch_key = v8_str(scope, "fetch");
|
|
531
|
+
t_obj.set(scope, fetch_key.into(), fetch_fn.into());
|
|
532
|
+
|
|
533
|
+
// t.jwt
|
|
534
|
+
let jwt_obj = v8::Object::new(scope);
|
|
535
|
+
let sign_fn = v8::Function::new(scope, native_jwt_sign).unwrap();
|
|
536
|
+
let verify_fn = v8::Function::new(scope, native_jwt_verify).unwrap();
|
|
537
|
+
|
|
538
|
+
let sign_key = v8_str(scope, "sign");
|
|
539
|
+
jwt_obj.set(scope, sign_key.into(), sign_fn.into());
|
|
540
|
+
let verify_key = v8_str(scope, "verify");
|
|
541
|
+
jwt_obj.set(scope, verify_key.into(), verify_fn.into());
|
|
542
|
+
|
|
543
|
+
let jwt_key = v8_str(scope, "jwt");
|
|
544
|
+
t_obj.set(scope, jwt_key.into(), jwt_obj.into());
|
|
545
|
+
|
|
546
|
+
// t.password
|
|
547
|
+
let pw_obj = v8::Object::new(scope);
|
|
548
|
+
let hash_fn = v8::Function::new(scope, native_password_hash).unwrap();
|
|
549
|
+
let pw_verify_fn = v8::Function::new(scope, native_password_verify).unwrap();
|
|
550
|
+
|
|
551
|
+
let hash_key = v8_str(scope, "hash");
|
|
552
|
+
pw_obj.set(scope, hash_key.into(), hash_fn.into());
|
|
553
|
+
let pw_verify_key = v8_str(scope, "verify");
|
|
554
|
+
pw_obj.set(scope, pw_verify_key.into(), pw_verify_fn.into());
|
|
555
|
+
|
|
556
|
+
let pw_key = v8_str(scope, "password");
|
|
557
|
+
t_obj.set(scope, pw_key.into(), pw_obj.into());
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
// Inject __titan_invoke_native
|
|
561
|
+
let invoke_fn = v8::Function::new(scope, native_invoke_extension).unwrap();
|
|
562
|
+
let invoke_key = v8_str(scope, "__titan_invoke_native");
|
|
563
|
+
global.set(scope, invoke_key.into(), invoke_fn.into());
|
|
564
|
+
|
|
565
|
+
// Inject Loaded Extensions
|
|
566
|
+
let modules = if let Ok(guard) = REGISTRY.lock() {
|
|
567
|
+
if let Some(registry) = &*guard {
|
|
568
|
+
registry.modules.clone()
|
|
569
|
+
} else {
|
|
570
|
+
Vec::new()
|
|
571
|
+
}
|
|
572
|
+
} else {
|
|
573
|
+
Vec::new()
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
for module in modules {
|
|
577
|
+
let mod_obj = v8::Object::new(scope);
|
|
578
|
+
|
|
579
|
+
// Generate JS wrappers
|
|
580
|
+
for (fn_name, &idx) in &module.native_indices {
|
|
581
|
+
let code = format!("(function(a, b) {{ return __titan_invoke_native({}, a, b); }})", idx);
|
|
582
|
+
let source = v8_str(scope, &code);
|
|
583
|
+
if let Some(script) = v8::Script::compile(scope, source, None) {
|
|
584
|
+
if let Some(val) = script.run(scope) {
|
|
585
|
+
let key = v8_str(scope, fn_name);
|
|
586
|
+
mod_obj.set(scope, key.into(), val);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Inject t.<module_name>
|
|
592
|
+
let mod_key = v8_str(scope, &module.name);
|
|
593
|
+
t_obj.set(scope, mod_key.into(), mod_obj.into());
|
|
594
|
+
|
|
595
|
+
// Set context for logging
|
|
596
|
+
let action_key = v8_str(scope, "__titan_action");
|
|
597
|
+
let action_val = v8_str(scope, &module.name);
|
|
598
|
+
global.set(scope, action_key.into(), action_val.into());
|
|
599
|
+
|
|
600
|
+
// Execute JS
|
|
601
|
+
// Wrap in IIFE passing 't' to ensure visibility
|
|
602
|
+
let wrapped_js = format!("(function(t) {{ {} }})", module.js);
|
|
603
|
+
let source = v8_str(scope, &wrapped_js);
|
|
604
|
+
let tc = &mut v8::TryCatch::new(scope);
|
|
605
|
+
|
|
606
|
+
if let Some(script) = v8::Script::compile(tc, source, None) {
|
|
607
|
+
if let Some(func_val) = script.run(tc) {
|
|
608
|
+
// func_val is the function. Call it with [t_obj]
|
|
609
|
+
if let Ok(func) = v8::Local::<v8::Function>::try_from(func_val) {
|
|
610
|
+
let receiver = v8::undefined(&mut *tc).into();
|
|
611
|
+
let args = [t_obj.into()];
|
|
612
|
+
// Pass tc (which is a scope)
|
|
613
|
+
if func.call(&mut *tc, receiver, &args).is_none() {
|
|
614
|
+
println!("{} {}", crate::utils::blue("[Titan]"), crate::utils::red("Extension Execution Failed"));
|
|
615
|
+
if let Some(msg) = tc.message() {
|
|
616
|
+
let text = msg.get(&mut *tc).to_rust_string_lossy(&mut *tc);
|
|
617
|
+
println!("{} {}", crate::utils::red("Error details:"), text);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
let msg = tc.message().unwrap();
|
|
623
|
+
let text = msg.get(&mut *tc).to_rust_string_lossy(&mut *tc);
|
|
624
|
+
println!("{} {} {}", crate::utils::blue("[Titan]"), crate::utils::red("Extension JS Error:"), text);
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
let msg = tc.message().unwrap();
|
|
628
|
+
let text = msg.get(&mut *tc).to_rust_string_lossy(&mut *tc);
|
|
629
|
+
println!("{} {} {}", crate::utils::blue("[Titan]"), crate::utils::red("Extension Compile Error:"), text);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// t.db (Stub for now)
|
|
634
|
+
let db_obj = v8::Object::new(scope);
|
|
635
|
+
let db_key = v8_str(scope, "db");
|
|
636
|
+
t_obj.set(scope, db_key.into(), db_obj.into());
|
|
637
|
+
|
|
638
|
+
let t_key = v8_str(scope, "t");
|
|
639
|
+
global.set(scope, t_key.into(), t_obj.into());
|
|
640
|
+
}
|