@ezetgalaxy/titan 26.7.2 → 26.7.4
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 +3 -1
- package/package.json +1 -1
- package/templates/Dockerfile +17 -4
- package/templates/server/src/extensions.rs +433 -216
- package/templates/server/src/main.rs +134 -68
- package/titanpl-sdk/README.md +14 -6
- package/titanpl-sdk/assets/titanpl-sdk.png +0 -0
- package/titanpl-sdk/package.json +2 -1
- package/titanpl-sdk/templates/Dockerfile +17 -4
- package/titanpl-sdk/templates/server/src/extensions.rs +423 -218
- package/titanpl-sdk/templates/server/src/main.rs +134 -68
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
#![allow(unused)]
|
|
2
|
-
use
|
|
2
|
+
use bcrypt::{DEFAULT_COST, hash, verify};
|
|
3
|
+
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode};
|
|
3
4
|
use reqwest::{
|
|
4
5
|
blocking::Client,
|
|
5
6
|
header::{HeaderMap, HeaderName, HeaderValue},
|
|
6
7
|
};
|
|
7
|
-
use
|
|
8
|
+
use serde_json::Value;
|
|
8
9
|
use std::path::PathBuf;
|
|
10
|
+
use std::sync::Once;
|
|
9
11
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
10
|
-
use
|
|
11
|
-
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
|
|
12
|
-
use bcrypt::{hash, verify, DEFAULT_COST};
|
|
12
|
+
use v8;
|
|
13
13
|
|
|
14
14
|
use crate::utils::{blue, gray, green, parse_expires_in};
|
|
15
|
-
use libloading::
|
|
16
|
-
use walkdir::WalkDir;
|
|
17
|
-
use std::sync::Mutex;
|
|
15
|
+
use libloading::Library;
|
|
18
16
|
use std::collections::HashMap;
|
|
19
17
|
use std::fs;
|
|
18
|
+
use std::sync::Mutex;
|
|
19
|
+
use walkdir::WalkDir;
|
|
20
20
|
|
|
21
21
|
// ----------------------------------------------------------------------------
|
|
22
22
|
// GLOBAL REGISTRY
|
|
@@ -25,7 +25,7 @@ use std::fs;
|
|
|
25
25
|
static REGISTRY: Mutex<Option<Registry>> = Mutex::new(None);
|
|
26
26
|
#[allow(dead_code)]
|
|
27
27
|
struct Registry {
|
|
28
|
-
_libs: Vec<Library>,
|
|
28
|
+
_libs: Vec<Library>,
|
|
29
29
|
modules: Vec<ModuleDef>,
|
|
30
30
|
natives: Vec<NativeFnEntry>, // Flattened list of all native functions
|
|
31
31
|
}
|
|
@@ -73,80 +73,188 @@ pub fn load_project_extensions(root: PathBuf) {
|
|
|
73
73
|
let mut libs = Vec::new();
|
|
74
74
|
let mut all_natives = Vec::new();
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
// =====================================================
|
|
77
|
+
// 1. Resolve all extension search directories
|
|
78
|
+
// =====================================================
|
|
79
|
+
|
|
80
|
+
let mut search_dirs = Vec::new();
|
|
81
|
+
|
|
82
|
+
let ext_dir = root.join(".ext"); // Production
|
|
83
|
+
let nm_root = root.join("node_modules"); // Dev
|
|
84
|
+
let nm_parent = root.parent().map(|p| p.join("node_modules")); // Monorepo
|
|
85
|
+
|
|
86
|
+
// 1) Production
|
|
87
|
+
if ext_dir.exists() {
|
|
88
|
+
search_dirs.push(ext_dir);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 2) Dev: project node_modules
|
|
92
|
+
if nm_root.exists() {
|
|
93
|
+
search_dirs.push(nm_root.clone());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 3) Dev monorepo: parent/node_modules
|
|
97
|
+
if let Some(nmp) = &nm_parent {
|
|
98
|
+
if nmp.exists() {
|
|
99
|
+
search_dirs.push(nmp.clone());
|
|
83
100
|
}
|
|
84
101
|
}
|
|
85
|
-
|
|
86
|
-
if node_modules.exists() {
|
|
87
|
-
for entry in WalkDir::new(&node_modules).follow_links(true).min_depth(1).max_depth(4) {
|
|
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
102
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
}
|
|
103
|
+
// 4) Never return empty — add root/node_modules even if missing
|
|
104
|
+
if search_dirs.is_empty() {
|
|
105
|
+
search_dirs.push(nm_root);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Normalize and dedupe
|
|
109
|
+
search_dirs.sort();
|
|
110
|
+
search_dirs.dedup();
|
|
132
111
|
|
|
133
|
-
|
|
134
|
-
|
|
112
|
+
println!("{} Scanning extension directories:", blue("[Titan]"));
|
|
113
|
+
for d in &search_dirs {
|
|
114
|
+
println!(" • {}", d.display());
|
|
115
|
+
}
|
|
135
116
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
117
|
+
// =====================================================
|
|
118
|
+
// 2. Walk and locate titan.json inside search paths
|
|
119
|
+
// =====================================================
|
|
120
|
+
for dir in &search_dirs {
|
|
121
|
+
if !dir.exists() {
|
|
122
|
+
println!(" {} Skipping non-existent directory: {}", crate::utils::yellow("⚠"), dir.display());
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for entry in WalkDir::new(&dir)
|
|
127
|
+
.min_depth(1)
|
|
128
|
+
.max_depth(5) // Increased depth
|
|
129
|
+
.follow_links(true)
|
|
130
|
+
{
|
|
131
|
+
let entry = match entry {
|
|
132
|
+
Ok(e) => e,
|
|
133
|
+
Err(_) => continue,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Only accept titan.json files
|
|
137
|
+
if entry.file_type().is_file() && entry.file_name() == "titan.json" {
|
|
138
|
+
let path = entry.path();
|
|
139
|
+
// Load config file
|
|
140
|
+
let config_content = match fs::read_to_string(path) {
|
|
141
|
+
Ok(c) => c,
|
|
142
|
+
Err(e) => {
|
|
143
|
+
println!("{} Failed to read {}: {}", crate::utils::red("[Titan]"), path.display(), e);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
let config: TitanConfig = match serde_json::from_str(&config_content) {
|
|
149
|
+
Ok(c) => c,
|
|
150
|
+
Err(e) => {
|
|
151
|
+
println!("{} Failed to parse {}: {}", crate::utils::red("[Titan]"), path.display(), e);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
let pkg_dir = path.parent().unwrap();
|
|
157
|
+
let mut mod_natives_map = HashMap::new();
|
|
158
|
+
|
|
159
|
+
// =====================================================
|
|
160
|
+
// 3. Load native extension (optional)
|
|
161
|
+
// =====================================================
|
|
162
|
+
if let Some(native_conf) = config.native {
|
|
163
|
+
let lib_path = pkg_dir.join(&native_conf.path);
|
|
164
|
+
|
|
165
|
+
unsafe {
|
|
166
|
+
match Library::new(&lib_path) {
|
|
167
|
+
Ok(lib) => {
|
|
168
|
+
for (fn_name, fn_conf) in native_conf.functions {
|
|
169
|
+
let sig = if fn_conf.parameters == ["f64", "f64"]
|
|
170
|
+
&& fn_conf.result == "f64"
|
|
171
|
+
{
|
|
172
|
+
Signature::F64TwoArgsRetF64
|
|
173
|
+
} else {
|
|
174
|
+
Signature::Unknown
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if let Ok(symbol) = lib.get::<*const ()>(fn_conf.symbol.as_bytes())
|
|
178
|
+
{
|
|
179
|
+
let idx = all_natives.len();
|
|
180
|
+
all_natives.push(NativeFnEntry {
|
|
181
|
+
ptr: *symbol as usize,
|
|
182
|
+
sig,
|
|
183
|
+
});
|
|
184
|
+
mod_natives_map.insert(fn_name, idx);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
libs.push(lib);
|
|
188
|
+
}
|
|
189
|
+
Err(e) => println!(
|
|
190
|
+
"{} Failed to load native library {} ({})",
|
|
191
|
+
blue("[Titan]"),
|
|
192
|
+
lib_path.display(),
|
|
193
|
+
e
|
|
194
|
+
),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// =====================================================
|
|
200
|
+
// 4. Load JS module file
|
|
201
|
+
// =====================================================
|
|
202
|
+
let js_path = pkg_dir.join(&config.main);
|
|
203
|
+
let js_content = match fs::read_to_string(&js_path) {
|
|
204
|
+
Ok(c) => c,
|
|
205
|
+
Err(e) => {
|
|
206
|
+
println!("{} Failed to read JS main {} for extension {}: {}",
|
|
207
|
+
crate::utils::red("[Titan]"),
|
|
208
|
+
js_path.display(),
|
|
209
|
+
config.name,
|
|
210
|
+
e
|
|
211
|
+
);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
modules.push(ModuleDef {
|
|
217
|
+
name: config.name.clone(),
|
|
218
|
+
js: js_content,
|
|
219
|
+
native_indices: mod_natives_map,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
println!(
|
|
223
|
+
"{} {} {}",
|
|
224
|
+
blue("[Titan]"),
|
|
225
|
+
green("Extension loaded:"),
|
|
226
|
+
config.name
|
|
227
|
+
);
|
|
143
228
|
}
|
|
144
229
|
}
|
|
145
230
|
}
|
|
146
231
|
|
|
147
|
-
|
|
148
|
-
|
|
232
|
+
// =====================================================
|
|
233
|
+
// 5. Store registry globally
|
|
234
|
+
// =====================================================
|
|
235
|
+
if modules.is_empty() {
|
|
236
|
+
println!("{} {}", blue("[Titan]"), crate::utils::yellow("No extensions loaded."));
|
|
237
|
+
// Debug: list files in search dirs to assist debugging
|
|
238
|
+
for dir in &search_dirs {
|
|
239
|
+
if dir.exists() {
|
|
240
|
+
println!("{} Listing contents of {}:", blue("[Titan]"), dir.display());
|
|
241
|
+
for entry in WalkDir::new(dir).max_depth(5) {
|
|
242
|
+
if let Ok(e) = entry {
|
|
243
|
+
println!(" - {}", e.path().display());
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
println!("{} Directory not found: {}", blue("[Titan]"), dir.display());
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
149
251
|
|
|
252
|
+
*REGISTRY.lock().unwrap() = Some(Registry {
|
|
253
|
+
_libs: libs,
|
|
254
|
+
modules,
|
|
255
|
+
natives: all_natives,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
150
258
|
|
|
151
259
|
static V8_INIT: Once = Once::new();
|
|
152
260
|
|
|
@@ -176,7 +284,11 @@ fn throw(scope: &mut v8::HandleScope, msg: &str) {
|
|
|
176
284
|
// NATIVE CALLBACKS
|
|
177
285
|
// ----------------------------------------------------------------------------
|
|
178
286
|
|
|
179
|
-
fn native_read(
|
|
287
|
+
fn native_read(
|
|
288
|
+
scope: &mut v8::HandleScope,
|
|
289
|
+
args: v8::FunctionCallbackArguments,
|
|
290
|
+
mut retval: v8::ReturnValue,
|
|
291
|
+
) {
|
|
180
292
|
let path_val = args.get(0);
|
|
181
293
|
// 1. Read argument
|
|
182
294
|
if !path_val.is_string() {
|
|
@@ -195,7 +307,7 @@ fn native_read(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments,
|
|
|
195
307
|
let global = context.global(scope);
|
|
196
308
|
let root_key = v8_str(scope, "__titan_root");
|
|
197
309
|
let root_val = global.get(scope, root_key.into()).unwrap();
|
|
198
|
-
|
|
310
|
+
|
|
199
311
|
let root_str = if root_val.is_string() {
|
|
200
312
|
v8_to_string(scope, root_val)
|
|
201
313
|
} else {
|
|
@@ -226,14 +338,18 @@ fn native_read(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments,
|
|
|
226
338
|
match std::fs::read_to_string(&target) {
|
|
227
339
|
Ok(content) => {
|
|
228
340
|
retval.set(v8_str(scope, &content).into());
|
|
229
|
-
}
|
|
341
|
+
}
|
|
230
342
|
Err(e) => {
|
|
231
343
|
throw(scope, &format!("t.read failed: {}", e));
|
|
232
344
|
}
|
|
233
345
|
}
|
|
234
346
|
}
|
|
235
347
|
|
|
236
|
-
fn native_log(
|
|
348
|
+
fn native_log(
|
|
349
|
+
scope: &mut v8::HandleScope,
|
|
350
|
+
args: v8::FunctionCallbackArguments,
|
|
351
|
+
mut _retval: v8::ReturnValue,
|
|
352
|
+
) {
|
|
237
353
|
let context = scope.get_current_context();
|
|
238
354
|
let global = context.global(scope);
|
|
239
355
|
let action_key = v8_str(scope, "__titan_action");
|
|
@@ -244,30 +360,38 @@ fn native_log(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments,
|
|
|
244
360
|
for i in 0..args.length() {
|
|
245
361
|
let val = args.get(i);
|
|
246
362
|
let mut appended = false;
|
|
247
|
-
|
|
363
|
+
|
|
248
364
|
// Try to JSON stringify objects so they are readable in logs
|
|
249
365
|
if val.is_object() && !val.is_function() {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
366
|
+
if let Some(json) = v8::json::stringify(scope, val) {
|
|
367
|
+
parts.push(json.to_rust_string_lossy(scope));
|
|
368
|
+
appended = true;
|
|
369
|
+
}
|
|
254
370
|
}
|
|
255
|
-
|
|
371
|
+
|
|
256
372
|
if !appended {
|
|
257
373
|
parts.push(v8_to_string(scope, val));
|
|
258
374
|
}
|
|
259
375
|
}
|
|
260
|
-
|
|
376
|
+
|
|
261
377
|
println!(
|
|
262
378
|
"{} {}",
|
|
263
379
|
blue("[Titan]"),
|
|
264
|
-
gray(&format!(
|
|
380
|
+
gray(&format!(
|
|
381
|
+
"\x1b[90mlog({})\x1b[0m\x1b[97m: {}\x1b[0m",
|
|
382
|
+
action_name,
|
|
383
|
+
parts.join(" ")
|
|
384
|
+
))
|
|
265
385
|
);
|
|
266
386
|
}
|
|
267
387
|
|
|
268
|
-
fn native_fetch(
|
|
388
|
+
fn native_fetch(
|
|
389
|
+
scope: &mut v8::HandleScope,
|
|
390
|
+
args: v8::FunctionCallbackArguments,
|
|
391
|
+
mut retval: v8::ReturnValue,
|
|
392
|
+
) {
|
|
269
393
|
let url = v8_to_string(scope, args.get(0));
|
|
270
|
-
|
|
394
|
+
|
|
271
395
|
// Check for options (method, headers, body)
|
|
272
396
|
let mut method = "GET".to_string();
|
|
273
397
|
let mut body_str = None;
|
|
@@ -276,7 +400,7 @@ fn native_fetch(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments
|
|
|
276
400
|
let opts_val = args.get(1);
|
|
277
401
|
if opts_val.is_object() {
|
|
278
402
|
let opts_obj = opts_val.to_object(scope).unwrap();
|
|
279
|
-
|
|
403
|
+
|
|
280
404
|
// method
|
|
281
405
|
let m_key = v8_str(scope, "method");
|
|
282
406
|
if let Some(m_val) = opts_obj.get(scope, m_key.into()) {
|
|
@@ -284,18 +408,18 @@ fn native_fetch(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments
|
|
|
284
408
|
method = v8_to_string(scope, m_val);
|
|
285
409
|
}
|
|
286
410
|
}
|
|
287
|
-
|
|
411
|
+
|
|
288
412
|
// body
|
|
289
413
|
let b_key = v8_str(scope, "body");
|
|
290
414
|
if let Some(b_val) = opts_obj.get(scope, b_key.into()) {
|
|
291
415
|
if b_val.is_string() {
|
|
292
416
|
body_str = Some(v8_to_string(scope, b_val));
|
|
293
417
|
} else if b_val.is_object() {
|
|
294
|
-
|
|
295
|
-
|
|
418
|
+
let json_obj = v8::json::stringify(scope, b_val).unwrap();
|
|
419
|
+
body_str = Some(json_obj.to_rust_string_lossy(scope));
|
|
296
420
|
}
|
|
297
421
|
}
|
|
298
|
-
|
|
422
|
+
|
|
299
423
|
// headers
|
|
300
424
|
let h_key = v8_str(scope, "headers");
|
|
301
425
|
if let Some(h_val) = opts_obj.get(scope, h_key.into()) {
|
|
@@ -305,57 +429,61 @@ fn native_fetch(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments
|
|
|
305
429
|
for i in 0..keys.length() {
|
|
306
430
|
let key = keys.get_index(scope, i).unwrap();
|
|
307
431
|
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
|
-
));
|
|
432
|
+
headers_vec.push((v8_to_string(scope, key), v8_to_string(scope, val)));
|
|
312
433
|
}
|
|
313
434
|
}
|
|
314
435
|
}
|
|
315
436
|
}
|
|
316
437
|
}
|
|
317
438
|
|
|
318
|
-
let client = Client::builder()
|
|
319
|
-
|
|
439
|
+
let client = Client::builder()
|
|
440
|
+
.use_rustls_tls()
|
|
441
|
+
.tcp_nodelay(true)
|
|
442
|
+
.build()
|
|
443
|
+
.unwrap_or(Client::new());
|
|
444
|
+
|
|
320
445
|
let mut req = client.request(method.parse().unwrap_or(reqwest::Method::GET), &url);
|
|
321
|
-
|
|
446
|
+
|
|
322
447
|
for (k, v) in headers_vec {
|
|
323
|
-
if let (Ok(name), Ok(val)) = (
|
|
448
|
+
if let (Ok(name), Ok(val)) = (
|
|
449
|
+
HeaderName::from_bytes(k.as_bytes()),
|
|
450
|
+
HeaderValue::from_str(&v),
|
|
451
|
+
) {
|
|
324
452
|
let mut map = HeaderMap::new();
|
|
325
453
|
map.insert(name, val);
|
|
326
454
|
req = req.headers(map);
|
|
327
455
|
}
|
|
328
456
|
}
|
|
329
|
-
|
|
457
|
+
|
|
330
458
|
if let Some(b) = body_str {
|
|
331
459
|
req = req.body(b);
|
|
332
460
|
}
|
|
333
|
-
|
|
461
|
+
|
|
334
462
|
let res = req.send();
|
|
335
|
-
|
|
463
|
+
|
|
336
464
|
let obj = v8::Object::new(scope);
|
|
337
465
|
match res {
|
|
338
466
|
Ok(r) => {
|
|
339
467
|
let status = r.status().as_u16();
|
|
340
468
|
let text = r.text().unwrap_or_default();
|
|
341
|
-
|
|
469
|
+
|
|
342
470
|
let status_key = v8_str(scope, "status");
|
|
343
471
|
let status_val = v8::Number::new(scope, status as f64);
|
|
344
472
|
obj.set(scope, status_key.into(), status_val.into());
|
|
345
|
-
|
|
473
|
+
|
|
346
474
|
let body_key = v8_str(scope, "body");
|
|
347
475
|
let body_val = v8_str(scope, &text);
|
|
348
476
|
obj.set(scope, body_key.into(), body_val.into());
|
|
349
|
-
|
|
477
|
+
|
|
350
478
|
let ok_key = v8_str(scope, "ok");
|
|
351
479
|
let ok_val = v8::Boolean::new(scope, true);
|
|
352
480
|
obj.set(scope, ok_key.into(), ok_val.into());
|
|
353
|
-
}
|
|
481
|
+
}
|
|
354
482
|
Err(e) => {
|
|
355
483
|
let ok_key = v8_str(scope, "ok");
|
|
356
484
|
let ok_val = v8::Boolean::new(scope, false);
|
|
357
485
|
obj.set(scope, ok_key.into(), ok_val.into());
|
|
358
|
-
|
|
486
|
+
|
|
359
487
|
let err_key = v8_str(scope, "error");
|
|
360
488
|
let err_val = v8_str(scope, &e.to_string());
|
|
361
489
|
obj.set(scope, err_key.into(), err_val.into());
|
|
@@ -364,33 +492,46 @@ fn native_fetch(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments
|
|
|
364
492
|
retval.set(obj.into());
|
|
365
493
|
}
|
|
366
494
|
|
|
367
|
-
fn native_jwt_sign(
|
|
495
|
+
fn native_jwt_sign(
|
|
496
|
+
scope: &mut v8::HandleScope,
|
|
497
|
+
args: v8::FunctionCallbackArguments,
|
|
498
|
+
mut retval: v8::ReturnValue,
|
|
499
|
+
) {
|
|
368
500
|
// payload, secret, options
|
|
369
501
|
let payload_val = args.get(0);
|
|
370
502
|
// Parse payload to serde_json::Map
|
|
371
|
-
let json_str = v8::json::stringify(scope, payload_val)
|
|
372
|
-
|
|
503
|
+
let json_str = v8::json::stringify(scope, payload_val)
|
|
504
|
+
.unwrap()
|
|
505
|
+
.to_rust_string_lossy(scope);
|
|
506
|
+
let mut payload: serde_json::Map<String, Value> =
|
|
507
|
+
serde_json::from_str(&json_str).unwrap_or_default();
|
|
373
508
|
|
|
374
509
|
let secret = v8_to_string(scope, args.get(1));
|
|
375
|
-
|
|
510
|
+
|
|
376
511
|
let opts_val = args.get(2);
|
|
377
512
|
if opts_val.is_object() {
|
|
378
513
|
let opts_obj = opts_val.to_object(scope).unwrap();
|
|
379
514
|
let exp_key = v8_str(scope, "expiresIn");
|
|
380
|
-
|
|
515
|
+
|
|
381
516
|
if let Some(val) = opts_obj.get(scope, exp_key.into()) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
let now = SystemTime::now()
|
|
392
|
-
|
|
393
|
-
|
|
517
|
+
let seconds = if val.is_number() {
|
|
518
|
+
Some(val.to_number(scope).unwrap().value() as u64)
|
|
519
|
+
} else if val.is_string() {
|
|
520
|
+
parse_expires_in(&v8_to_string(scope, val))
|
|
521
|
+
} else {
|
|
522
|
+
None
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
if let Some(sec) = seconds {
|
|
526
|
+
let now = SystemTime::now()
|
|
527
|
+
.duration_since(UNIX_EPOCH)
|
|
528
|
+
.unwrap()
|
|
529
|
+
.as_secs();
|
|
530
|
+
payload.insert(
|
|
531
|
+
"exp".to_string(),
|
|
532
|
+
Value::Number(serde_json::Number::from(now + sec)),
|
|
533
|
+
);
|
|
534
|
+
}
|
|
394
535
|
}
|
|
395
536
|
}
|
|
396
537
|
|
|
@@ -406,33 +547,41 @@ fn native_jwt_sign(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArgume
|
|
|
406
547
|
}
|
|
407
548
|
}
|
|
408
549
|
|
|
409
|
-
fn native_jwt_verify(
|
|
550
|
+
fn native_jwt_verify(
|
|
551
|
+
scope: &mut v8::HandleScope,
|
|
552
|
+
args: v8::FunctionCallbackArguments,
|
|
553
|
+
mut retval: v8::ReturnValue,
|
|
554
|
+
) {
|
|
410
555
|
let token = v8_to_string(scope, args.get(0));
|
|
411
556
|
let secret = v8_to_string(scope, args.get(1));
|
|
412
|
-
|
|
557
|
+
|
|
413
558
|
let mut validation = Validation::default();
|
|
414
559
|
validation.validate_exp = true;
|
|
415
|
-
|
|
560
|
+
|
|
416
561
|
let data = decode::<Value>(
|
|
417
562
|
&token,
|
|
418
563
|
&DecodingKey::from_secret(secret.as_bytes()),
|
|
419
564
|
&validation,
|
|
420
565
|
);
|
|
421
|
-
|
|
566
|
+
|
|
422
567
|
match data {
|
|
423
568
|
Ok(d) => {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
569
|
+
// Convert claim back to V8 object via JSON
|
|
570
|
+
let json_str = serde_json::to_string(&d.claims).unwrap();
|
|
571
|
+
let v8_json_str = v8_str(scope, &json_str);
|
|
572
|
+
if let Some(val) = v8::json::parse(scope, v8_json_str) {
|
|
573
|
+
retval.set(val);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
431
576
|
Err(e) => throw(scope, &format!("Invalid or expired JWT: {}", e)),
|
|
432
577
|
}
|
|
433
578
|
}
|
|
434
579
|
|
|
435
|
-
fn native_password_hash(
|
|
580
|
+
fn native_password_hash(
|
|
581
|
+
scope: &mut v8::HandleScope,
|
|
582
|
+
args: v8::FunctionCallbackArguments,
|
|
583
|
+
mut retval: v8::ReturnValue,
|
|
584
|
+
) {
|
|
436
585
|
let pw = v8_to_string(scope, args.get(0));
|
|
437
586
|
match hash(pw, DEFAULT_COST) {
|
|
438
587
|
Ok(h) => retval.set(v8_str(scope, &h).into()),
|
|
@@ -440,15 +589,23 @@ fn native_password_hash(scope: &mut v8::HandleScope, args: v8::FunctionCallbackA
|
|
|
440
589
|
}
|
|
441
590
|
}
|
|
442
591
|
|
|
443
|
-
fn native_password_verify(
|
|
592
|
+
fn native_password_verify(
|
|
593
|
+
scope: &mut v8::HandleScope,
|
|
594
|
+
args: v8::FunctionCallbackArguments,
|
|
595
|
+
mut retval: v8::ReturnValue,
|
|
596
|
+
) {
|
|
444
597
|
let pw = v8_to_string(scope, args.get(0));
|
|
445
598
|
let hash_str = v8_to_string(scope, args.get(1));
|
|
446
|
-
|
|
599
|
+
|
|
447
600
|
let ok = verify(pw, &hash_str).unwrap_or(false);
|
|
448
601
|
retval.set(v8::Boolean::new(scope, ok).into());
|
|
449
602
|
}
|
|
450
603
|
|
|
451
|
-
fn native_define_action(
|
|
604
|
+
fn native_define_action(
|
|
605
|
+
_scope: &mut v8::HandleScope,
|
|
606
|
+
args: v8::FunctionCallbackArguments,
|
|
607
|
+
mut retval: v8::ReturnValue,
|
|
608
|
+
) {
|
|
452
609
|
retval.set(args.get(0));
|
|
453
610
|
}
|
|
454
611
|
|
|
@@ -458,13 +615,17 @@ fn native_define_action(_scope: &mut v8::HandleScope, args: v8::FunctionCallback
|
|
|
458
615
|
|
|
459
616
|
// generic wrappers could go here if needed
|
|
460
617
|
|
|
461
|
-
fn native_invoke_extension(
|
|
618
|
+
fn native_invoke_extension(
|
|
619
|
+
scope: &mut v8::HandleScope,
|
|
620
|
+
args: v8::FunctionCallbackArguments,
|
|
621
|
+
mut retval: v8::ReturnValue,
|
|
622
|
+
) {
|
|
462
623
|
let fn_idx = args.get(0).to_integer(scope).unwrap().value() as usize;
|
|
463
624
|
|
|
464
625
|
// Get pointer from registry
|
|
465
626
|
let mut ptr = 0;
|
|
466
627
|
let mut sig = Signature::Unknown;
|
|
467
|
-
|
|
628
|
+
|
|
468
629
|
if let Ok(guard) = REGISTRY.lock() {
|
|
469
630
|
if let Some(registry) = &*guard {
|
|
470
631
|
if let Some(entry) = registry.natives.get(fn_idx) {
|
|
@@ -473,33 +634,39 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
473
634
|
}
|
|
474
635
|
}
|
|
475
636
|
}
|
|
476
|
-
|
|
637
|
+
|
|
477
638
|
if ptr == 0 {
|
|
478
|
-
|
|
479
|
-
|
|
639
|
+
throw(scope, "Native function not found");
|
|
640
|
+
return;
|
|
480
641
|
}
|
|
481
642
|
|
|
482
643
|
match sig {
|
|
483
644
|
Signature::F64TwoArgsRetF64 => {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
645
|
+
let a = args
|
|
646
|
+
.get(1)
|
|
647
|
+
.to_number(scope)
|
|
648
|
+
.unwrap_or(v8::Number::new(scope, 0.0))
|
|
649
|
+
.value();
|
|
650
|
+
let b = args
|
|
651
|
+
.get(2)
|
|
652
|
+
.to_number(scope)
|
|
653
|
+
.unwrap_or(v8::Number::new(scope, 0.0))
|
|
654
|
+
.value();
|
|
655
|
+
|
|
656
|
+
unsafe {
|
|
657
|
+
let func: extern "C" fn(f64, f64) -> f64 = std::mem::transmute(ptr);
|
|
658
|
+
let res = func(a, b);
|
|
659
|
+
retval.set(v8::Number::new(scope, res).into());
|
|
660
|
+
}
|
|
661
|
+
}
|
|
493
662
|
_ => throw(scope, "Unsupported signature"),
|
|
494
663
|
}
|
|
495
664
|
}
|
|
496
665
|
|
|
497
|
-
|
|
498
666
|
// ----------------------------------------------------------------------------
|
|
499
667
|
// INJECTOR
|
|
500
668
|
// ----------------------------------------------------------------------------
|
|
501
669
|
|
|
502
|
-
|
|
503
670
|
pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Object>) {
|
|
504
671
|
// Ensure globalThis reference
|
|
505
672
|
let gt_key = v8_str(scope, "globalThis");
|
|
@@ -508,13 +675,15 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
508
675
|
let t_obj = v8::Object::new(scope);
|
|
509
676
|
let t_key = v8_str(scope, "t");
|
|
510
677
|
// Use create_data_property to guarantee definition
|
|
511
|
-
global
|
|
678
|
+
global
|
|
679
|
+
.create_data_property(scope, t_key.into(), t_obj.into())
|
|
680
|
+
.unwrap();
|
|
512
681
|
|
|
513
682
|
// defineAction (identity function for clean typing)
|
|
514
683
|
let def_fn = v8::Function::new(scope, native_define_action).unwrap();
|
|
515
684
|
let def_key = v8_str(scope, "defineAction");
|
|
516
685
|
global.set(scope, def_key.into(), def_fn.into());
|
|
517
|
-
|
|
686
|
+
|
|
518
687
|
// t.read
|
|
519
688
|
let read_fn = v8::Function::new(scope, native_read).unwrap();
|
|
520
689
|
let read_key = v8_str(scope, "read");
|
|
@@ -524,7 +693,7 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
524
693
|
let log_fn = v8::Function::new(scope, native_log).unwrap();
|
|
525
694
|
let log_key = v8_str(scope, "log");
|
|
526
695
|
t_obj.set(scope, log_key.into(), log_fn.into());
|
|
527
|
-
|
|
696
|
+
|
|
528
697
|
// t.fetch
|
|
529
698
|
let fetch_fn = v8::Function::new(scope, native_fetch).unwrap();
|
|
530
699
|
let fetch_key = v8_str(scope, "fetch");
|
|
@@ -534,12 +703,12 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
534
703
|
let jwt_obj = v8::Object::new(scope);
|
|
535
704
|
let sign_fn = v8::Function::new(scope, native_jwt_sign).unwrap();
|
|
536
705
|
let verify_fn = v8::Function::new(scope, native_jwt_verify).unwrap();
|
|
537
|
-
|
|
706
|
+
|
|
538
707
|
let sign_key = v8_str(scope, "sign");
|
|
539
708
|
jwt_obj.set(scope, sign_key.into(), sign_fn.into());
|
|
540
709
|
let verify_key = v8_str(scope, "verify");
|
|
541
710
|
jwt_obj.set(scope, verify_key.into(), verify_fn.into());
|
|
542
|
-
|
|
711
|
+
|
|
543
712
|
let jwt_key = v8_str(scope, "jwt");
|
|
544
713
|
t_obj.set(scope, jwt_key.into(), jwt_obj.into());
|
|
545
714
|
|
|
@@ -547,16 +716,15 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
547
716
|
let pw_obj = v8::Object::new(scope);
|
|
548
717
|
let hash_fn = v8::Function::new(scope, native_password_hash).unwrap();
|
|
549
718
|
let pw_verify_fn = v8::Function::new(scope, native_password_verify).unwrap();
|
|
550
|
-
|
|
719
|
+
|
|
551
720
|
let hash_key = v8_str(scope, "hash");
|
|
552
721
|
pw_obj.set(scope, hash_key.into(), hash_fn.into());
|
|
553
722
|
let pw_verify_key = v8_str(scope, "verify");
|
|
554
723
|
pw_obj.set(scope, pw_verify_key.into(), pw_verify_fn.into());
|
|
555
|
-
|
|
724
|
+
|
|
556
725
|
let pw_key = v8_str(scope, "password");
|
|
557
726
|
t_obj.set(scope, pw_key.into(), pw_obj.into());
|
|
558
727
|
|
|
559
|
-
|
|
560
728
|
// Inject __titan_invoke_native
|
|
561
729
|
let invoke_fn = v8::Function::new(scope, native_invoke_extension).unwrap();
|
|
562
730
|
let invoke_key = v8_str(scope, "__titan_invoke_native");
|
|
@@ -574,60 +742,97 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
574
742
|
};
|
|
575
743
|
|
|
576
744
|
for module in modules {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
745
|
+
// 1. Prepare Native Wrappers
|
|
746
|
+
let natives_obj = v8::Object::new(scope);
|
|
747
|
+
for (fn_name, &idx) in &module.native_indices {
|
|
748
|
+
let code = format!(
|
|
749
|
+
"(function(a, b) {{ return __titan_invoke_native({}, a, b); }})",
|
|
750
|
+
idx
|
|
751
|
+
);
|
|
752
|
+
let source = v8_str(scope, &code);
|
|
753
|
+
// Compile wrappers
|
|
754
|
+
if let Some(script) = v8::Script::compile(scope, source, None) {
|
|
755
|
+
if let Some(val) = script.run(scope) {
|
|
756
|
+
let key = v8_str(scope, fn_name);
|
|
757
|
+
natives_obj.set(scope, key.into(), val);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// 2. Prepare JS Wrapper (CommonJS shim)
|
|
763
|
+
// We pass 't' and 'native' (the object we just made) to the module.
|
|
764
|
+
let wrapper_src = format!(
|
|
765
|
+
r#"(function(t, native) {{
|
|
766
|
+
var module = {{ exports: {{}} }};
|
|
767
|
+
var exports = module.exports;
|
|
768
|
+
{}
|
|
769
|
+
return module.exports;
|
|
770
|
+
}})"#,
|
|
771
|
+
module.js
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
let source = v8_str(scope, &wrapper_src);
|
|
775
|
+
let tc = &mut v8::TryCatch::new(scope);
|
|
776
|
+
|
|
777
|
+
// 3. Compile and Run
|
|
778
|
+
if let Some(script) = v8::Script::compile(tc, source, None) {
|
|
779
|
+
if let Some(factory_val) = script.run(tc) {
|
|
780
|
+
if let Ok(factory) = v8::Local::<v8::Function>::try_from(factory_val) {
|
|
781
|
+
let recv = v8::undefined(&mut *tc).into();
|
|
782
|
+
// Pass t_obj and natives_obj
|
|
783
|
+
let args = [t_obj.into(), natives_obj.into()];
|
|
784
|
+
|
|
785
|
+
if let Some(exports_val) = factory.call(&mut *tc, recv, &args) {
|
|
786
|
+
// 4. Assign exports to t.<extName>
|
|
787
|
+
let mod_key = v8_str(&mut *tc, &module.name);
|
|
788
|
+
t_obj.set(&mut *tc, mod_key.into(), exports_val);
|
|
789
|
+
|
|
790
|
+
// println!(
|
|
791
|
+
// "{} {} {}",
|
|
792
|
+
// crate::utils::blue("[Titan]"),
|
|
793
|
+
// crate::utils::green("Injected extension:"),
|
|
794
|
+
// module.name
|
|
795
|
+
// );
|
|
796
|
+
} else {
|
|
797
|
+
// Execution error
|
|
798
|
+
if let Some(msg) = tc.message() {
|
|
799
|
+
let text = msg.get(&mut *tc).to_rust_string_lossy(&mut *tc);
|
|
800
|
+
println!(
|
|
801
|
+
"{} {} {} -> {}",
|
|
802
|
+
crate::utils::blue("[Titan]"),
|
|
803
|
+
crate::utils::red("Error running extension"),
|
|
804
|
+
module.name,
|
|
805
|
+
text
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
} else {
|
|
811
|
+
// Runtime error during script run
|
|
812
|
+
if let Some(msg) = tc.message() {
|
|
813
|
+
let text = msg.get(&mut *tc).to_rust_string_lossy(&mut *tc);
|
|
814
|
+
println!(
|
|
815
|
+
"{} {} {} -> {}",
|
|
816
|
+
crate::utils::blue("[Titan]"),
|
|
817
|
+
crate::utils::red("Error evaluating extension wrapper"),
|
|
818
|
+
module.name,
|
|
819
|
+
text
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
} else {
|
|
824
|
+
// Compile error
|
|
825
|
+
if let Some(msg) = tc.message() {
|
|
826
|
+
let text = msg.get(&mut *tc).to_rust_string_lossy(&mut *tc);
|
|
827
|
+
println!(
|
|
828
|
+
"{} {} {} -> {}",
|
|
829
|
+
crate::utils::blue("[Titan]"),
|
|
830
|
+
crate::utils::red("Syntax Error in extension"),
|
|
831
|
+
module.name,
|
|
832
|
+
text
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
631
836
|
}
|
|
632
837
|
|
|
633
838
|
// t.db (Stub for now)
|