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