Add SelfFreezoneClient wrapper for Self components

- Created SelfFreezoneClient in Self components
- Wraps SDK FreezoneScriptClient for Self-specific operations
- Implements send_verification_email method
- Uses Rhai script template for email verification
- Includes template variable substitution
- Added serde-wasm-bindgen dependency

Usage:
  let client = SelfFreezoneClient::builder()
      .supervisor_url("http://localhost:8080")
      .secret("my-secret")
      .build()?;

  client.send_verification_email(
      "user@example.com",
      "123456",
      "https://verify.com/abc"
  ).await?;
This commit is contained in:
Timur Gordon
2025-11-03 16:16:18 +01:00
parent be061409af
commit f970f3fb58
33 changed files with 8947 additions and 449 deletions

View File

@@ -9,7 +9,10 @@ crate-type = ["cdylib"]
[dependencies]
self-components = { path = "../components" }
yew = { workspace = true, features = ["csr"] }
yew-router = "0.18"
wasm-bindgen = { workspace = true }
wasm-bindgen-futures = { workspace = true }
web-sys = { workspace = true }
js-sys = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

6
app/dist/index.html vendored
View File

@@ -105,7 +105,7 @@
transition: width 0.6s ease;
}
</style>
<link rel="modulepreload" href="/self-app-ea91d85454088543.js" crossorigin="anonymous" integrity="sha384-LtoStOLfGudTKD5QLifQMTi8an1aLEqMXZ1KhnQoVNPjpl8QZyBepu9D2sElroww"><link rel="preload" href="/self-app-ea91d85454088543_bg.wasm" crossorigin="anonymous" integrity="sha384-Ms0zVmkquqd/C5lI7dHomP8BqQnZQkKfzd+xAPLra7T1Z8ZAH59VCtXnUxWzzriT" as="fetch" type="application/wasm"></head>
<link rel="modulepreload" href="/self-app-b25013f584ee50e5.js" crossorigin="anonymous" integrity="sha384-1u18adVyfNdbwYr68l3Y+7ogP5yHPkK1snT5bpYfKGB/90poijA0I51N9oeEWm2E"><link rel="preload" href="/self-app-b25013f584ee50e5_bg.wasm" crossorigin="anonymous" integrity="sha384-sJLOBlXwGx/LmAiZtmypjzSbazD/ES2YkNdRfDm+qHUmAk4OJ6S7jIdZ5q9iVApF" as="fetch" type="application/wasm"></head>
<body>
<div id="app"></div>
@@ -113,8 +113,8 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script type="module">
import init, * as bindings from '/self-app-ea91d85454088543.js';
const wasm = await init({ module_or_path: '/self-app-ea91d85454088543_bg.wasm' });
import init, * as bindings from '/self-app-b25013f584ee50e5.js';
const wasm = await init({ module_or_path: '/self-app-b25013f584ee50e5_bg.wasm' });
window.wasmBindings = bindings;

View File

@@ -125,6 +125,11 @@ function passStringToWasm0(arg, malloc, realloc) {
return ptr;
}
function getArrayU8FromWasm0(ptr, len) {
ptr = ptr >>> 0;
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
}
function debugString(val) {
// primitive types
const type = typeof val;
@@ -198,28 +203,6 @@ state => {
}
);
function makeClosure(arg0, arg1, dtor, f) {
const state = { a: arg0, b: arg1, cnt: 1, dtor };
const real = (...args) => {
// First up with a closure we increment the internal reference
// count. This ensures that the Rust closure environment won't
// be deallocated while we're invoking it.
state.cnt++;
try {
return f(state.a, state.b, ...args);
} finally {
if (--state.cnt === 0) {
wasm.__wbindgen_export_7.get(state.dtor)(state.a, state.b); state.a = 0;
CLOSURE_DTORS.unregister(state);
}
}
};
real.original = state;
CLOSURE_DTORS.register(real, state, state);
return real;
}
function makeMutClosure(arg0, arg1, dtor, f) {
const state = { a: arg0, b: arg1, cnt: 1, dtor };
const real = (...args) => {
@@ -246,28 +229,134 @@ function makeMutClosure(arg0, arg1, dtor, f) {
return real;
}
function makeClosure(arg0, arg1, dtor, f) {
const state = { a: arg0, b: arg1, cnt: 1, dtor };
const real = (...args) => {
// First up with a closure we increment the internal reference
// count. This ensures that the Rust closure environment won't
// be deallocated while we're invoking it.
state.cnt++;
try {
return f(state.a, state.b, ...args);
} finally {
if (--state.cnt === 0) {
wasm.__wbindgen_export_7.get(state.dtor)(state.a, state.b); state.a = 0;
CLOSURE_DTORS.unregister(state);
}
}
};
real.original = state;
CLOSURE_DTORS.register(real, state, state);
return real;
}
export function run_app() {
wasm.run_app();
}
function takeFromExternrefTable0(idx) {
const value = wasm.__wbindgen_export_2.get(idx);
wasm.__externref_table_dealloc(idx);
return value;
}
function __wbg_adapter_6(arg0, arg1, arg2) {
wasm.closure293_externref_shim(arg0, arg1, arg2);
wasm.closure538_externref_shim(arg0, arg1, arg2);
}
function __wbg_adapter_9(arg0, arg1, arg2) {
wasm.closure223_externref_shim(arg0, arg1, arg2);
wasm.closure608_externref_shim(arg0, arg1, arg2);
}
function __wbg_adapter_14(arg0, arg1, arg2) {
wasm.closure286_externref_shim(arg0, arg1, arg2);
}
function __wbg_adapter_17(arg0, arg1) {
wasm.wasm_bindgen__convert__closures_____invoke__h5eb5ae71b8ec87bb(arg0, arg1);
function __wbg_adapter_16(arg0, arg1, arg2) {
wasm.closure604_externref_shim(arg0, arg1, arg2);
}
const __wbindgen_enum_RequestMode = ["same-origin", "no-cors", "cors", "navigate"];
const VaultJsFinalization = (typeof FinalizationRegistry === 'undefined')
? { register: () => {}, unregister: () => {} }
: new FinalizationRegistry(ptr => wasm.__wbg_vaultjs_free(ptr >>> 0, 1));
export class VaultJs {
__destroy_into_raw() {
const ptr = this.__wbg_ptr;
this.__wbg_ptr = 0;
VaultJsFinalization.unregister(this);
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_vaultjs_free(ptr, 0);
}
/**
* @param {string} private_key
* @param {string} password
*/
static storePrivateKey(private_key, password) {
const ptr0 = passStringToWasm0(private_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.vaultjs_storePrivateKey(ptr0, len0, ptr1, len1);
if (ret[1]) {
throw takeFromExternrefTable0(ret[0]);
}
}
/**
* @param {string} password
* @returns {string}
*/
static retrievePrivateKey(password) {
let deferred3_0;
let deferred3_1;
try {
const ptr0 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.vaultjs_retrievePrivateKey(ptr0, len0);
var ptr2 = ret[0];
var len2 = ret[1];
if (ret[3]) {
ptr2 = 0; len2 = 0;
throw takeFromExternrefTable0(ret[2]);
}
deferred3_0 = ptr2;
deferred3_1 = len2;
return getStringFromWasm0(ptr2, len2);
} finally {
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
}
}
/**
* @returns {boolean}
*/
static hasStoredKey() {
const ret = wasm.vaultjs_hasStoredKey();
return ret !== 0;
}
static clearStoredKey() {
const ret = wasm.vaultjs_clearStoredKey();
if (ret[1]) {
throw takeFromExternrefTable0(ret[0]);
}
}
/**
* @param {string} password
* @returns {boolean}
*/
static verifyPassword(password) {
const ptr0 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.vaultjs_verifyPassword(ptr0, len0);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return ret[0] !== 0;
}
}
const EXPECTED_RESPONSE_TYPES = new Set(['basic', 'cors', 'default']);
async function __wbg_load(module, imports) {
@@ -325,6 +414,10 @@ function __wbg_get_imports() {
const ret = arg0.call(arg1);
return ret;
}, arguments) };
imports.wbg.__wbg_call_f53f0647ceb9c567 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = arg0.call(arg1, arg2);
return ret;
}, arguments) };
imports.wbg.__wbg_cancelBubble_ba3496c52eac50f9 = function(arg0) {
const ret = arg0.cancelBubble;
return ret;
@@ -333,10 +426,6 @@ function __wbg_get_imports() {
const ret = arg0.childNodes;
return ret;
};
imports.wbg.__wbg_clearTimeout_5a54f8841c30079a = function(arg0) {
const ret = clearTimeout(arg0);
return ret;
};
imports.wbg.__wbg_clipboard_cb646efa8bab83fa = function(arg0) {
const ret = arg0.clipboard;
return ret;
@@ -364,6 +453,10 @@ function __wbg_get_imports() {
const ret = arg0.createTextNode(getStringFromWasm0(arg1, arg2));
return ret;
};
imports.wbg.__wbg_crypto_574e78ad8b13b65f = function(arg0) {
const ret = arg0.crypto;
return ret;
};
imports.wbg.__wbg_data_d1e564c046e31ed9 = function(arg0) {
const ret = arg0.data;
return ret;
@@ -399,6 +492,16 @@ function __wbg_get_imports() {
const ret = Array.from(arg0);
return ret;
};
imports.wbg.__wbg_getItem_7083c7e8090b4e20 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
const ret = arg1.getItem(getStringFromWasm0(arg2, arg3));
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
}, arguments) };
imports.wbg.__wbg_getRandomValues_b8f5dbd5f3995a9e = function() { return handleError(function (arg0, arg1) {
arg0.getRandomValues(arg1);
}, arguments) };
imports.wbg.__wbg_get_59c6316d15f9f1d0 = function(arg0, arg1) {
const ret = arg0[arg1 >>> 0];
return ret;
@@ -421,6 +524,16 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_instanceof_Response_0ab386c6818f788a = function(arg0) {
let result;
try {
result = arg0 instanceof Response;
} catch (_) {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_instanceof_ShadowRoot_ae4bc62016938ece = function(arg0) {
let result;
try {
@@ -445,6 +558,10 @@ function __wbg_get_imports() {
const ret = Object.is(arg0, arg1);
return ret;
};
imports.wbg.__wbg_json_dbaa85a926a80ed5 = function() { return handleError(function (arg0) {
const ret = arg0.json();
return ret;
}, arguments) };
imports.wbg.__wbg_lastChild_5847fcd93bd5162a = function(arg0) {
const ret = arg0.lastChild;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
@@ -453,13 +570,25 @@ function __wbg_get_imports() {
const ret = arg0.length;
return ret;
};
imports.wbg.__wbg_length_904c0910ed998bf3 = function(arg0) {
const ret = arg0.length;
return ret;
};
imports.wbg.__wbg_listenerid_ed1678830a5b97ec = function(arg0) {
const ret = arg0.__yew_listener_id;
return isLikeNone(ret) ? 0x100000001 : (ret) >>> 0;
};
imports.wbg.__wbg_localStorage_3e7df12e18c45ecc = function() { return handleError(function (arg0) {
const ret = arg0.localStorage;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
}, arguments) };
imports.wbg.__wbg_log_f3c04200b995730f = function(arg0) {
console.log(arg0);
};
imports.wbg.__wbg_msCrypto_a61aeb35a24c1329 = function(arg0) {
const ret = arg0.msCrypto;
return ret;
};
imports.wbg.__wbg_namespaceURI_16a9ca763a61b64a = function(arg0, arg1) {
const ret = arg1.namespaceURI;
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -471,6 +600,10 @@ function __wbg_get_imports() {
const ret = arg0.navigator;
return ret;
};
imports.wbg.__wbg_new0_85cc856927102294 = function() {
const ret = new Date();
return ret;
};
imports.wbg.__wbg_new_12588505388d0897 = function() { return handleError(function () {
const ret = new Headers();
return ret;
@@ -491,6 +624,10 @@ function __wbg_get_imports() {
const ret = new Function(getStringFromWasm0(arg0, arg1));
return ret;
};
imports.wbg.__wbg_newwithlength_ed0ee6c1edca86fc = function(arg0) {
const ret = new Uint8Array(arg0 >>> 0);
return ret;
};
imports.wbg.__wbg_newwithstrandinit_e8e22e9851f3c2fe = function() { return handleError(function (arg0, arg1, arg2) {
const ret = new Request(getStringFromWasm0(arg0, arg1), arg2);
return ret;
@@ -499,6 +636,18 @@ function __wbg_get_imports() {
const ret = arg0.nextSibling;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_node_905d3e251edff8a2 = function(arg0) {
const ret = arg0.node;
return ret;
};
imports.wbg.__wbg_now_e3057dd824ca0191 = function() {
const ret = Date.now();
return ret;
};
imports.wbg.__wbg_ok_3f777ee8a1c6baeb = function(arg0) {
const ret = arg0.ok;
return ret;
};
imports.wbg.__wbg_outerHTML_7306ec658b1ed630 = function(arg0, arg1) {
const ret = arg1.outerHTML;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -514,6 +663,16 @@ function __wbg_get_imports() {
const ret = arg0.parentNode;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_preventDefault_9f90a0a7802591fd = function(arg0) {
arg0.preventDefault();
};
imports.wbg.__wbg_process_dc0fbacc7c1c06f7 = function(arg0) {
const ret = arg0.process;
return ret;
};
imports.wbg.__wbg_prototypesetcall_c5f74efd31aea86b = function(arg0, arg1, arg2) {
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
};
imports.wbg.__wbg_queueMicrotask_bcc6e26d899696db = function(arg0) {
const ret = arg0.queueMicrotask;
return ret;
@@ -521,6 +680,9 @@ function __wbg_get_imports() {
imports.wbg.__wbg_queueMicrotask_f24a794d09c42640 = function(arg0) {
queueMicrotask(arg0);
};
imports.wbg.__wbg_randomFillSync_ac0988aba3254290 = function() { return handleError(function (arg0, arg1) {
arg0.randomFillSync(arg1);
}, arguments) };
imports.wbg.__wbg_removeAttribute_6930c6c8a4db23d2 = function() { return handleError(function (arg0, arg1, arg2) {
arg0.removeAttribute(getStringFromWasm0(arg1, arg2));
}, arguments) };
@@ -531,6 +693,13 @@ function __wbg_get_imports() {
imports.wbg.__wbg_removeEventListener_b25c194da9564efa = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
arg0.removeEventListener(getStringFromWasm0(arg1, arg2), arg3, arg4 !== 0);
}, arguments) };
imports.wbg.__wbg_removeItem_3accb1e04073ade4 = function() { return handleError(function (arg0, arg1, arg2) {
arg0.removeItem(getStringFromWasm0(arg1, arg2));
}, arguments) };
imports.wbg.__wbg_require_60cc747a6bc5215a = function() { return handleError(function () {
const ret = module.require;
return ret;
}, arguments) };
imports.wbg.__wbg_resolve_5775c0ef9222f556 = function(arg0) {
const ret = Promise.resolve(arg0);
return ret;
@@ -538,9 +707,8 @@ function __wbg_get_imports() {
imports.wbg.__wbg_setAttribute_6a3ee9b5deb88ed3 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
arg0.setAttribute(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) };
imports.wbg.__wbg_setTimeout_db2dbaeefb6f39c7 = function() { return handleError(function (arg0, arg1) {
const ret = setTimeout(arg0, arg1);
return ret;
imports.wbg.__wbg_setItem_328cbcd44fb5d487 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
arg0.setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) };
imports.wbg.__wbg_set_2df374478acad331 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
arg0.set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
@@ -617,6 +785,18 @@ function __wbg_get_imports() {
const ret = typeof window === 'undefined' ? null : window;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_status_31874648c8651949 = function(arg0) {
const ret = arg0.status;
return ret;
};
imports.wbg.__wbg_stringify_1f41b6198e0932e0 = function() { return handleError(function (arg0) {
const ret = JSON.stringify(arg0);
return ret;
}, arguments) };
imports.wbg.__wbg_subarray_a219824899e59712 = function(arg0, arg1, arg2) {
const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0);
return ret;
};
imports.wbg.__wbg_subtreeid_e65dfcc52d403fd9 = function(arg0) {
const ret = arg0.__yew_subtree_id;
return isLikeNone(ret) ? 0x100000001 : (ret) >>> 0;
@@ -632,6 +812,10 @@ function __wbg_get_imports() {
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
};
imports.wbg.__wbg_text_42c080764c927da6 = function() { return handleError(function (arg0) {
const ret = arg0.text();
return ret;
}, arguments) };
imports.wbg.__wbg_then_8d2fcccde5380a03 = function(arg0, arg1, arg2) {
const ret = arg0.then(arg1, arg2);
return ret;
@@ -640,6 +824,10 @@ function __wbg_get_imports() {
const ret = arg0.then(arg1);
return ret;
};
imports.wbg.__wbg_toISOString_61f131e920833685 = function(arg0) {
const ret = arg0.toISOString();
return ret;
};
imports.wbg.__wbg_value_1ae15635193fdbb5 = function(arg0, arg1) {
const ret = arg1.value;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -654,6 +842,10 @@ function __wbg_get_imports() {
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
};
imports.wbg.__wbg_versions_c01dfd4722a88165 = function(arg0) {
const ret = arg0.versions;
return ret;
};
imports.wbg.__wbg_wbindgencbdrop_a85ed476c6a370b9 = function(arg0) {
const obj = arg0.original;
if (obj.cnt-- == 1) {
@@ -674,6 +866,15 @@ function __wbg_get_imports() {
const ret = typeof(arg0) === 'function';
return ret;
};
imports.wbg.__wbg_wbindgenisobject_dfe064a121d87553 = function(arg0) {
const val = arg0;
const ret = typeof(val) === 'object' && val !== null;
return ret;
};
imports.wbg.__wbg_wbindgenisstring_4b74e4111ba029e6 = function(arg0) {
const ret = typeof(arg0) === 'string';
return ret;
};
imports.wbg.__wbg_wbindgenisundefined_71f08a6ade4354e7 = function(arg0) {
const ret = arg0 === undefined;
return ret;
@@ -698,24 +899,24 @@ function __wbg_get_imports() {
const ret = getStringFromWasm0(arg0, arg1);
return ret;
};
imports.wbg.__wbindgen_cast_33ed6ce34b165f3b = function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { dtor_idx: 222, function: Function { arguments: [Ref(NamedExternref("Event"))], shim_idx: 223, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
const ret = makeClosure(arg0, arg1, 222, __wbg_adapter_9);
imports.wbg.__wbindgen_cast_4d7be94769e4ba26 = function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { dtor_idx: 603, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 604, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, 603, __wbg_adapter_16);
return ret;
};
imports.wbg.__wbindgen_cast_52db8efac9e5598a = function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { dtor_idx: 292, function: Function { arguments: [Externref], shim_idx: 293, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, 292, __wbg_adapter_6);
imports.wbg.__wbindgen_cast_96deb231e670e363 = function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { dtor_idx: 537, function: Function { arguments: [Ref(NamedExternref("Event"))], shim_idx: 538, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
const ret = makeClosure(arg0, arg1, 537, __wbg_adapter_6);
return ret;
};
imports.wbg.__wbindgen_cast_8783041643bd3db7 = function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { dtor_idx: 285, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 286, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, 285, __wbg_adapter_14);
imports.wbg.__wbindgen_cast_cb9088102bce6b30 = function(arg0, arg1) {
// Cast intrinsic for `Ref(Slice(U8)) -> NamedExternref("Uint8Array")`.
const ret = getArrayU8FromWasm0(arg0, arg1);
return ret;
};
imports.wbg.__wbindgen_cast_8b1432b6c48212a6 = function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { dtor_idx: 287, function: Function { arguments: [], shim_idx: 288, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, 287, __wbg_adapter_17);
imports.wbg.__wbindgen_cast_edabf52bd3bd4133 = function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { dtor_idx: 607, function: Function { arguments: [Externref], shim_idx: 608, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, 607, __wbg_adapter_9);
return ret;
};
imports.wbg.__wbindgen_init_externref_table = function() {

Binary file not shown.

Binary file not shown.

View File

@@ -1,44 +1,398 @@
use yew::prelude::*;
use yew_router::prelude::*;
use wasm_bindgen::prelude::*;
use self_components::{Registration, RegistrationConfig};
use self_components::{Registration, RegistrationConfig, Login, LoginConfig, Identity, IdentityConfig, IdentityData, Sign, SignConfig, VaultManager, VaultConfig};
#[function_component(App)]
fn app() -> Html {
let registration_complete = Callback::from(|data: (String, String)| {
let (email, public_key) = data;
web_sys::console::log_1(&format!("Registration completed for {} with public key: {}", email, public_key).into());
});
mod pages;
use pages::{Landing, Documentation, AppPage};
let server_url = option_env!("SERVER_URL")
.unwrap_or("http://localhost:8080")
.to_string();
let config = RegistrationConfig {
server_url,
app_name: "Self-Sovereign Identity".to_string(),
};
#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/docs")]
Docs,
#[at("/docs/:section")]
DocsSection { section: String },
#[at("/app")]
App,
#[at("/app/:view")]
AppView { view: String },
#[not_found]
#[at("/404")]
NotFound,
}
html! {
<div class="app-container" style="min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 2rem 0;">
<div class="container">
<div class="row justify-content-center">
<div class="col-12 col-lg-8">
<div class="text-center mb-4">
<h1 class="display-4 text-white mb-3">{"Self"}</h1>
<p class="lead text-white-50">{"Sovereign Entity Local Framework"}</p>
#[derive(Clone, PartialEq)]
enum AppView {
Login,
Register,
Identity,
Sign,
Vault,
}
pub struct App {
current_view: AppView,
user_identity: Option<IdentityData>,
jwt_token: Option<String>,
}
pub enum AppMsg {
ShowLogin,
ShowRegister,
ShowIdentity,
ShowSign,
ShowVault,
LoginSuccess(String), // JWT token
RegistrationSuccess(String, String), // (email, public_key) - registration still uses this
IdentityFetched(IdentityData),
IdentityFetchFailed(String),
Logout,
}
impl Component for App {
type Message = AppMsg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self {
current_view: AppView::Login,
user_identity: None,
jwt_token: None,
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
AppMsg::ShowLogin => {
self.current_view = AppView::Login;
true
}
AppMsg::ShowRegister => {
self.current_view = AppView::Register;
true
}
AppMsg::ShowIdentity => {
self.current_view = AppView::Identity;
true
}
AppMsg::ShowSign => {
self.current_view = AppView::Sign;
true
}
AppMsg::ShowVault => {
self.current_view = AppView::Vault;
true
}
AppMsg::LoginSuccess(jwt_token) => {
self.jwt_token = Some(jwt_token.clone());
// Fetch user identity from server using JWT token
self.fetch_user_identity(_ctx, jwt_token);
true
}
AppMsg::IdentityFetched(identity_data) => {
self.user_identity = Some(identity_data);
true
}
AppMsg::IdentityFetchFailed(error) => {
web_sys::console::log_1(&format!("Failed to fetch identity: {}", error).into());
// Clear JWT token on failure
self.jwt_token = None;
true
}
AppMsg::RegistrationSuccess(email, public_key) => {
self.user_identity = Some(IdentityData {
email,
public_key: public_key.clone(),
name: "User".to_string(),
created_at: None,
});
// Stay on register view instead of auto-redirecting
true
}
AppMsg::Logout => {
self.user_identity = None;
self.jwt_token = None;
self.current_view = AppView::Login;
// Clear JWT token from localStorage
if let Some(window) = web_sys::window() {
if let Ok(Some(storage)) = window.local_storage() {
let _ = storage.remove_item("jwt_token");
web_sys::console::log_1(&"JWT token cleared from localStorage".into());
}
}
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let _link = ctx.link();
html! {
<div class="app-container" style="min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
{self.render_navbar(ctx)}
<div style="padding: 2rem 0;">
<div class="container">
<div class="row justify-content-center">
<div class="col-12 col-lg-8">
<div class="text-center mb-4">
<h1 class="display-4 text-white mb-3">{"Self"}</h1>
<p class="lead text-white-50">{"Sovereign Entity Local Framework"}</p>
</div>
{self.render_current_view(ctx)}
</div>
</div>
<Registration
config={config}
on_complete={registration_complete}
/>
</div>
</div>
</div>
</div>
}
}
}
impl App {
fn render_navbar(&self, ctx: &Context<Self>) -> Html {
let link = ctx.link();
html! {
<nav class="navbar navbar-expand-lg" style="background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px);">
<div class="container">
<a class="navbar-brand text-white fw-bold" href="#">
<i class="bi bi-shield-check me-2"></i>
{"Self"}
</a>
<div class="navbar-nav ms-auto">
{if self.jwt_token.is_some() {
// Show authenticated user navigation
html! {
<>
<button type="button"
class={format!("btn me-2 {}", if self.current_view == AppView::Identity { "btn-light" } else { "btn-outline-light" })}
onclick={link.callback(|_| AppMsg::ShowIdentity)}>
<i class="bi bi-person-badge me-1"></i>
{"Identity"}
</button>
<button type="button"
class={format!("btn me-2 {}", if self.current_view == AppView::Vault { "btn-light" } else { "btn-outline-light" })}
onclick={link.callback(|_| AppMsg::ShowVault)}>
<i class="bi bi-shield-lock me-1"></i>
{"Vault"}
</button>
<button type="button"
class={format!("btn me-2 {}", if self.current_view == AppView::Sign { "btn-light" } else { "btn-outline-light" })}
onclick={link.callback(|_| AppMsg::ShowSign)}>
<i class="bi bi-pen me-1"></i>
{"Sign"}
</button>
<button type="button"
class="btn btn-outline-danger"
onclick={link.callback(|_| AppMsg::Logout)}>
<i class="bi bi-box-arrow-right me-1"></i>
{"Logout"}
</button>
</>
}
} else {
// Show unauthenticated user navigation
html! {
<>
<button type="button"
class={format!("btn me-2 {}", if self.current_view == AppView::Login { "btn-light" } else { "btn-outline-light" })}
onclick={link.callback(|_| AppMsg::ShowLogin)}>
<i class="bi bi-box-arrow-in-right me-1"></i>
{"Login"}
</button>
<button type="button"
class={format!("btn {}", if self.current_view == AppView::Register { "btn-light" } else { "btn-outline-light" })}
onclick={link.callback(|_| AppMsg::ShowRegister)}>
<i class="bi bi-person-plus me-1"></i>
{"Register"}
</button>
</>
}
}}
</div>
</div>
</nav>
}
}
fn render_current_view(&self, ctx: &Context<Self>) -> Html {
let link = ctx.link();
let server_url = option_env!("SERVER_URL")
.unwrap_or("http://localhost:8080")
.to_string();
match self.current_view {
AppView::Login => {
let login_config = LoginConfig {
server_url,
};
html! {
<Login
config={login_config}
on_login_success={link.callback(|jwt_token| AppMsg::LoginSuccess(jwt_token))}
/>
}
}
AppView::Register => {
let registration_config = RegistrationConfig {
server_url,
app_name: "Self-Sovereign Identity".to_string(),
};
html! {
<Registration
config={registration_config}
on_complete={link.callback(|(email, public_key)| AppMsg::RegistrationSuccess(email, public_key))}
/>
}
}
AppView::Identity => {
if let Some(identity_data) = &self.user_identity {
let identity_config = IdentityConfig {
server_url,
app_name: "Self-Sovereign Identity".to_string(),
};
html! {
<Identity
config={identity_config}
on_logout={link.callback(|_| AppMsg::Logout)}
/>
}
} else {
// Fallback to login if no identity data
html! { <div>{"Error: No identity data available"}</div> }
}
}
AppView::Sign => {
let sign_config = SignConfig {
server_url,
app_name: "Self-Sovereign Identity".to_string(),
};
html! {
<Sign
config={sign_config}
on_signature_complete={link.callback(|(plaintext, signature)| {
web_sys::console::log_1(&format!("Signature created for text: {} -> {}", plaintext, signature).into());
AppMsg::ShowSign // Stay on sign view
})}
/>
}
}
AppView::Vault => {
let vault_config = VaultConfig {
server_url,
app_name: "Self-Sovereign Identity".to_string(),
};
html! {
<VaultManager
config={vault_config}
/>
}
}
}
}
fn fetch_user_identity(&self, ctx: &Context<Self>, jwt_token: String) {
let server_url = option_env!("SERVER_URL")
.unwrap_or("http://localhost:8080")
.to_string();
let link = ctx.link().clone();
wasm_bindgen_futures::spawn_local(async move {
match Self::fetch_identity_from_server(&server_url, &jwt_token).await {
Ok(identity_data) => {
link.send_message(AppMsg::IdentityFetched(identity_data));
}
Err(e) => {
link.send_message(AppMsg::IdentityFetchFailed(e));
}
}
});
}
async fn fetch_identity_from_server(server_url: &str, jwt_token: &str) -> Result<IdentityData, String> {
use web_sys::{Request, RequestInit, RequestMode, Response};
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture;
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::Cors);
let headers = js_sys::Object::new();
js_sys::Reflect::set(&headers, &"Authorization".into(), &format!("Bearer {}", jwt_token).into()).unwrap();
js_sys::Reflect::set(&headers, &"Content-Type".into(), &"application/json".into()).unwrap();
opts.headers(&headers);
let url = format!("{}/oauth/userinfo", server_url);
let request = Request::new_with_str_and_init(&url, &opts)
.map_err(|_| "Failed to create request")?;
let window = web_sys::window().ok_or("No window object")?;
let resp_value = JsFuture::from(window.fetch_with_request(&request))
.await
.map_err(|_| "Network request failed")?;
let resp: Response = resp_value.dyn_into()
.map_err(|_| "Failed to cast response")?;
if !resp.ok() {
return Err(format!("Failed to fetch user info with status: {}", resp.status()));
}
let json = JsFuture::from(resp.json().map_err(|_| "Failed to get response JSON")?)
.await
.map_err(|_| "Failed to parse JSON response")?;
let response_text = js_sys::JSON::stringify(&json)
.map_err(|_| "Failed to stringify response")?
.as_string()
.ok_or("Failed to convert response to string")?;
let user_info: serde_json::Value = serde_json::from_str(&response_text)
.map_err(|_| "Failed to parse response JSON")?;
// Extract user information from OpenID Connect userinfo response
let email = user_info["email"]
.as_str()
.unwrap_or("unknown@example.com")
.to_string();
let name = user_info["name"]
.as_str()
.unwrap_or("Unknown User")
.to_string();
let public_key = user_info["sub"] // Subject is typically the public key in our case
.as_str()
.unwrap_or("")
.to_string();
let created_at = user_info["created_at"]
.as_str()
.map(|s| s.to_string());
Ok(IdentityData {
email,
name,
public_key,
created_at,
})
}
}
#[wasm_bindgen::prelude::wasm_bindgen(start)]
pub fn run_app() {
yew::Renderer::<App>::new().render();

279
app/src/pages/landing.rs Normal file
View File

@@ -0,0 +1,279 @@
use yew::prelude::*;
use yew_router::prelude::*;
use crate::Route;
#[function_component(Landing)]
pub fn landing() -> Html {
let navigator = use_navigator().unwrap();
let on_get_started = {
let navigator = navigator.clone();
Callback::from(move |_| {
navigator.push(&Route::App);
})
};
let on_view_docs = {
let navigator = navigator.clone();
Callback::from(move |_| {
navigator.push(&Route::Docs);
})
};
html! {
<div class="landing-page">
// Hero Section
<section class="hero-section" style="min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; display: flex; align-items: center;">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6 mb-5 mb-lg-0">
<div class="hero-content" style="animation: fadeInUp 0.8s ease-out;">
<h1 class="display-2 fw-bold mb-4" style="font-size: 4rem;">
{"Self"}
</h1>
<p class="lead mb-4" style="font-size: 1.5rem; opacity: 0.95;">
{"Sovereign Entity Local Framework"}
</p>
<p class="fs-5 mb-5" style="opacity: 0.9; line-height: 1.8;">
{"Take control of your digital identity. No passwords, no central authority, just you and your cryptographic keys."}
</p>
<div class="d-flex gap-3 flex-wrap">
<button onclick={on_get_started} class="btn btn-light btn-lg px-5 py-3" style="border-radius: 50px; font-weight: 600; box-shadow: 0 8px 20px rgba(0,0,0,0.2);">
<i class="bi bi-rocket-takeoff me-2"></i>
{"Get Started"}
</button>
<button onclick={on_view_docs} class="btn btn-outline-light btn-lg px-5 py-3" style="border-radius: 50px; font-weight: 600; border-width: 2px;">
<i class="bi bi-book me-2"></i>
{"Documentation"}
</button>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="hero-visual" style="animation: fadeInUp 0.8s ease-out 0.2s both;">
<div class="p-5" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 24px; border: 2px solid rgba(255,255,255,0.2);">
<div class="text-center mb-4">
<i class="bi bi-shield-check" style="font-size: 6rem; opacity: 0.9;"></i>
</div>
<div class="d-flex justify-content-around text-center">
<div>
<i class="bi bi-key-fill fs-2 mb-2 d-block"></i>
<small>{"Your Keys"}</small>
</div>
<div>
<i class="bi bi-lock-fill fs-2 mb-2 d-block"></i>
<small>{"Your Data"}</small>
</div>
<div>
<i class="bi bi-person-check-fill fs-2 mb-2 d-block"></i>
<small>{"Your Identity"}</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
// Features Section
<section class="features-section py-5" style="background: #f8f9fa;">
<div class="container py-5">
<div class="text-center mb-5">
<h2 class="display-4 fw-bold mb-3">{"Why Self?"}</h2>
<p class="lead text-muted">{"True digital sovereignty through cryptographic identity"}</p>
</div>
<div class="row g-4">
<div class="col-md-6 col-lg-3">
<div class="feature-card card h-100 border-0 shadow-sm" style="transition: transform 0.3s;">
<div class="card-body text-center p-4">
<div class="feature-icon mb-3" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-shield-lock-fill text-white" style="font-size: 2.5rem;"></i>
</div>
<h4 class="fw-bold mb-3">{"Self-Sovereign"}</h4>
<p class="text-muted">{"You generate and control your own cryptographic keys. No central authority."}</p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="feature-card card h-100 border-0 shadow-sm">
<div class="card-body text-center p-4">
<div class="feature-icon mb-3" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-fingerprint text-white" style="font-size: 2.5rem;"></i>
</div>
<h4 class="fw-bold mb-3">{"Passwordless"}</h4>
<p class="text-muted">{"Authenticate using digital signatures, not passwords. More secure and convenient."}</p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="feature-card card h-100 border-0 shadow-sm">
<div class="card-body text-center p-4">
<div class="feature-icon mb-3" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-cpu-fill text-white" style="font-size: 2.5rem;"></i>
</div>
<h4 class="fw-bold mb-3">{"Client-Side Crypto"}</h4>
<p class="text-muted">{"All cryptographic operations happen in your browser. Your keys never leave your device."}</p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="feature-card card h-100 border-0 shadow-sm">
<div class="card-body text-center p-4">
<div class="feature-icon mb-3" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-diagram-3-fill text-white" style="font-size: 2.5rem;"></i>
</div>
<h4 class="fw-bold mb-3">{"OAuth Compatible"}</h4>
<p class="text-muted">{"Works with existing OAuth 2.0 and OpenID Connect infrastructure."}</p>
</div>
</div>
</div>
</div>
</div>
</section>
// How It Works Section
<section class="how-it-works-section py-5" style="background: white;">
<div class="container py-5">
<div class="text-center mb-5">
<h2 class="display-4 fw-bold mb-3">{"How It Works"}</h2>
<p class="lead text-muted">{"Simple, secure, and sovereign"}</p>
</div>
<div class="row g-5">
<div class="col-lg-4">
<div class="text-center">
<div class="step-number mb-4" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem; font-weight: bold;">
{"1"}
</div>
<h4 class="fw-bold mb-3">{"Generate Keys"}</h4>
<p class="text-muted">{"Create your cryptographic key pair locally in your browser. Your private key is encrypted with a password you choose."}</p>
</div>
</div>
<div class="col-lg-4">
<div class="text-center">
<div class="step-number mb-4" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem; font-weight: bold;">
{"2"}
</div>
<h4 class="fw-bold mb-3">{"Register Identity"}</h4>
<p class="text-muted">{"Verify your email and register your public key. The server never sees your private key or password."}</p>
</div>
</div>
<div class="col-lg-4">
<div class="text-center">
<div class="step-number mb-4" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem; font-weight: bold;">
{"3"}
</div>
<h4 class="fw-bold mb-3">{"Authenticate"}</h4>
<p class="text-muted">{"Sign challenges with your private key to prove your identity. No passwords needed."}</p>
</div>
</div>
</div>
</div>
</section>
// Technology Section
<section class="tech-section py-5" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
<div class="container py-5">
<div class="text-center mb-5">
<h2 class="display-4 fw-bold mb-3">{"Built with Modern Technology"}</h2>
<p class="lead" style="opacity: 0.9;">{"Rust, WebAssembly, and cutting-edge cryptography"}</p>
</div>
<div class="row g-4 text-center">
<div class="col-md-3 col-6">
<div class="tech-item p-4" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 16px; border: 1px solid rgba(255,255,255,0.2);">
<i class="bi bi-code-square fs-1 mb-3 d-block"></i>
<h5 class="fw-bold">{"Rust + WASM"}</h5>
</div>
</div>
<div class="col-md-3 col-6">
<div class="tech-item p-4" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 16px; border: 1px solid rgba(255,255,255,0.2);">
<i class="bi bi-shield-fill-check fs-1 mb-3 d-block"></i>
<h5 class="fw-bold">{"AES-256-GCM"}</h5>
</div>
</div>
<div class="col-md-3 col-6">
<div class="tech-item p-4" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 16px; border: 1px solid rgba(255,255,255,0.2);">
<i class="bi bi-key-fill fs-1 mb-3 d-block"></i>
<h5 class="fw-bold">{"Secp256k1"}</h5>
</div>
</div>
<div class="col-md-3 col-6">
<div class="tech-item p-4" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 16px; border: 1px solid rgba(255,255,255,0.2);">
<i class="bi bi-diagram-3-fill fs-1 mb-3 d-block"></i>
<h5 class="fw-bold">{"OAuth 2.0"}</h5>
</div>
</div>
</div>
</div>
</section>
// CTA Section
<section class="cta-section py-5" style="background: #f8f9fa;">
<div class="container py-5">
<div class="text-center">
<h2 class="display-4 fw-bold mb-4">{"Ready to Take Control?"}</h2>
<p class="lead text-muted mb-5">{"Start using Self today and experience true digital sovereignty"}</p>
<div class="d-flex gap-3 justify-content-center flex-wrap">
<button onclick={on_get_started.clone()} class="btn btn-primary btn-lg px-5 py-3" style="border-radius: 50px; font-weight: 600; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none;">
<i class="bi bi-rocket-takeoff me-2"></i>
{"Launch App"}
</button>
<a href="https://github.com/herocode/self" target="_blank" class="btn btn-outline-dark btn-lg px-5 py-3" style="border-radius: 50px; font-weight: 600; border-width: 2px;">
<i class="bi bi-github me-2"></i>
{"View on GitHub"}
</a>
</div>
</div>
</div>
</section>
// Footer
<footer class="py-4" style="background: #343a40; color: white;">
<div class="container">
<div class="row">
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
<p class="mb-0">{"© 2025 Self - Sovereign Entity Local Framework"}</p>
</div>
<div class="col-md-6 text-center text-md-end">
<a href="#" class="text-white text-decoration-none me-3">{"Privacy"}</a>
<a href="#" class="text-white text-decoration-none me-3">{"Terms"}</a>
<a href="https://github.com/herocode/self" target="_blank" class="text-white text-decoration-none">{"GitHub"}</a>
</div>
</div>
</div>
</footer>
<style>
{r#"
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.feature-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0,0,0,0.15) !important;
}
.tech-item:hover {
background: rgba(255,255,255,0.2) !important;
}
"#}
</style>
</div>
}
}

7
app/src/pages/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
mod landing;
mod documentation;
mod app_page;
pub use landing::Landing;
pub use documentation::Documentation;
pub use app_page::AppPage;