init (working)
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
target/
|
||||
node_modules/
|
||||
bunlib
|
92
README.md
Normal file
@ -0,0 +1,92 @@
|
||||
# Login with hyperware
|
||||
|
||||
Hyperware provides a secure PKI hosted in the Base network. Every hyperware node has a keypair which can be used to identify itself or sign messages.
|
||||
|
||||
For apps distributed inside hyperware, users are logged in by default, as the node reads the user identity from the hyperware node itself.
|
||||
|
||||
This package provides a library and example clients for using a Hyperware identity to login to applications and servers running outside hyperware.
|
||||
|
||||
There are three ways of using your Hyperware ID to authenticate to external applications:
|
||||
|
||||
1. Node-initiated
|
||||
2. Server-initiated OTP
|
||||
3. TOTP
|
||||
|
||||
## Node-initiated authentication
|
||||
|
||||
This method is suited for hybrid apps which have a pubilc facing frontend but also want to exclusive features to Hyperware users.
|
||||
The setup requires two parts:
|
||||
1. A hyperware package with a login frontend loaded inside hyperware ("the Client")
|
||||
2. Handling logic on the server ("the Server").
|
||||
|
||||
The basic idea is that users will login to your server using the hyperware package. Your server will issue an authentication token (not a cookie, as browser cookies are associated with the request origin, so here the cookie is already used for the browser to login to the user's hyperware web UI). The hyperware package will store the authentication token in the main process state. When a user accesses the package frontend, it will first check for the existence of an authentication token. If none exists, it will serve a login page. If it does, it will request your public facing frontend adding the auth token as a header in every request.
|
||||
|
||||
### Server-side (Your public facing server)
|
||||
You can see an example server as a barebones NextJS application in the `nextclient` folder.
|
||||
1. Import the `hyperware-login` JavaScript library (the `lib` folder in this repo) to your app.
|
||||
2. Set an endpoint in your server expecting an HTTP `POST` request with the following body:
|
||||
```json
|
||||
{
|
||||
"node": <the user's node id name>,
|
||||
"message": <the message that was signed as an array of numbers>,
|
||||
"signature": <the signature as an array of numbers>
|
||||
}
|
||||
```
|
||||
3. Instantiate a Kimap client by running:
|
||||
```ts
|
||||
import "KimapClient" from "hyperware-login"
|
||||
const kimap = new KimapClient(process.env.BASESCAN_API_KEY!);
|
||||
```
|
||||
4. Verify the signature sent to the endpoint by running:
|
||||
```ts
|
||||
const verificationResponse = await kimap.verifySignature(
|
||||
node,
|
||||
message,
|
||||
signature,
|
||||
);
|
||||
```
|
||||
The `kimap.verifySignature()` method will return a `Promise<{ok: boolean} | {error: string}>`
|
||||
If the verification is successful, generate an authentication token of your choice as a string.
|
||||
5. Send a response to the request. In the example, if the verification is successful, the NextJS endpoint returns `{ok: true}`, and an HTTP header `x-hyperware-auth` with the authentication token if the verification was successful. If the verification was unsuccessful the server returns `{error: string}` with different error messages, depending if the signature verification failed or there was any other error in the process.
|
||||
6. Handle users accessing your public frontend from within hyperware. In the example, requests from hyperware have an HTTP header `hypr-auth` with the authentication token issued by your server.
|
||||
|
||||
|
||||
### Client-side (Hyperware package)
|
||||
You can see a barebones example in the `client` folder. You can load it by cloning the repo and running `kit bs` inside the `client` folder.
|
||||
|
||||
- Make a hyperware package with a UI.
|
||||
- The package backend should expect requests, to three kinds of paths: the login frontend, the login API endpoint, and anything else, which will be redirected to your server. In the example, these are respectively `GET /hypr-login`, `POST /hypr-login` and else.
|
||||
|
||||
- Make a simple login interface in HTML (as in `client/pkg/ui/index.html`) and have the package serve that UI from a non root path. In the provided example the path is `/hypr-login`.
|
||||
|
||||
- The frontend should have a button that triggers an ajax `POST` request to the package backend in a non root path. In the example (see `client/pkg/ui/script.js`) we send the request to `<package_path>/hypr-login`.
|
||||
- The request body should be a json object with the following shape:
|
||||
```json
|
||||
{
|
||||
"Sign": null
|
||||
}
|
||||
```
|
||||
- Handle that request in the package backend (`client/loginex/src/lib.rs` in the example):
|
||||
- You can see an example handling the login request in the `handle_login_request()` function. The login request will do the following:
|
||||
1. Generate a signature with the user's hyperware identity's keypair.
|
||||
2. Send an HTTP `POST` request to the public facing server with the following json body:
|
||||
```json
|
||||
{
|
||||
"node": <the user's node id name>,
|
||||
"message": <the message that was signed as an array of numbers>,
|
||||
"signature": <the signature as an array of numbers>
|
||||
}
|
||||
```
|
||||
3. Await the response from the server.
|
||||
4. If the response is positive, grab the authentication token from the response. In the provided example the token is in the `x-hyperware-auth` header of the response. Save the authentication token to the process state.
|
||||
5. Send the result back to the hyperware frontend as a json object of the following shape:
|
||||
```ts
|
||||
{ok: boolean} | {error: string}
|
||||
```
|
||||
You are encouraged to handle the errors in your frontend to inform the user of the problem or however you see fit.
|
||||
If the response is successful, redirect the frontend to the root path of the package. Now that an authentication token has been set, this will request and render your public frontend.
|
||||
|
||||
|
||||
|
||||
## OTP (One Time Password)
|
||||
## TOTP (Time-based One Time Password)
|
4379
client/Cargo.lock
generated
Normal file
10
client/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"loginex",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
59
client/api/contacts:sys-v0.wit
Normal file
@ -0,0 +1,59 @@
|
||||
interface contacts {
|
||||
enum capability {
|
||||
read-name-only,
|
||||
read,
|
||||
add,
|
||||
remove,
|
||||
}
|
||||
|
||||
variant request {
|
||||
/// requires ReadNameOnly capability
|
||||
/// lazy-load-blob: none.
|
||||
get-names,
|
||||
/// requires Read capability
|
||||
/// lazy-load-blob: none.
|
||||
get-all-contacts,
|
||||
/// requires Read capability
|
||||
/// lazy-load-blob: none.
|
||||
get-contact(string),
|
||||
/// requires Add capability
|
||||
/// lazy-load-blob: none.
|
||||
add-contact(string),
|
||||
/// requires Add capability
|
||||
/// lazy-load-blob: none.
|
||||
/// tuple<node, field, value>
|
||||
add-field(tuple<string, string, string>),
|
||||
/// requires Remove capability
|
||||
/// lazy-load-blob: none.
|
||||
remove-contact(string),
|
||||
/// requires Remove capability
|
||||
/// lazy-load-blob: none.
|
||||
/// tuple<node, field>
|
||||
remove-field(tuple<string, string>),
|
||||
}
|
||||
|
||||
variant response {
|
||||
/// lazy-load-blob: none.
|
||||
get-names(list<string>),
|
||||
/// lazy-load-blob: required; JSON all-contacts dict in blob.
|
||||
get-all-contacts,
|
||||
/// lazy-load-blob: required; JSON contact dict in blob.
|
||||
get-contact,
|
||||
/// lazy-load-blob: none.
|
||||
add-contact,
|
||||
/// lazy-load-blob: none.
|
||||
add-field,
|
||||
/// lazy-load-blob: none.
|
||||
remove-contact,
|
||||
/// lazy-load-blob: none.
|
||||
remove-field,
|
||||
/// any failed request will receive this response
|
||||
/// lazy-load-blob: none.
|
||||
err(string),
|
||||
}
|
||||
}
|
||||
|
||||
world contacts-sys-v0 {
|
||||
import contacts;
|
||||
include process-v1;
|
||||
}
|
25
client/loginex/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "loginex"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
simulation-mode = []
|
||||
|
||||
[dependencies]
|
||||
hex = "0.4.3"
|
||||
lol_html = "2.2.0"
|
||||
anyhow = "1.0"
|
||||
hyperware_process_lib = { version = "1.0.3", features = ["logging"] }
|
||||
process_macros = "0.1.0"
|
||||
regex = "1.11"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
url = "2.5.4"
|
||||
wit-bindgen = "0.36.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "hyperware:process"
|
1
client/loginex/src/icon
Normal file
@ -0,0 +1 @@
|
||||
data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwMCIgaGVpZ2h0PSIxMDAwIiB2aWV3Qm94PSIwIDAgMTAwMCAxMDAwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cmVjdCB4PSIyMCIgeT0iMjAiIHdpZHRoPSI5NjAiIGhlaWdodD0iOTYwIiByeD0iNDgwIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfMjA5MV8xMDI4MikiIGZpbGwtb3BhY2l0eT0iMC40Ii8+CjxyZWN0IHg9IjIwIiB5PSIyMCIgd2lkdGg9Ijk2MCIgaGVpZ2h0PSI5NjAiIHJ4PSI0ODAiIHN0cm9rZT0idXJsKCNwYWludDFfbGluZWFyXzIwOTFfMTAyODIpIiBzdHJva2Utd2lkdGg9IjQwIi8+CjxwYXRoIGQ9Ik01ODEuMTE2IDU4NS43ODNMNjY0LjUzOCA2NjkuMTJDNjE3LjY5OSA3MTUuNzU4IDU1Ni42MjUgNzM5IDQ5NS4yNDUgNzM5QzQzMy44NjUgNzM5IDM3Mi42MzggNzE1LjYwNSAzMjYuMTA1IDY2OS4xMkMyODAuNzk3IDYyMy44NTggMjU2IDU2My45MTcgMjU2IDUwMEMyNTYgNDM2LjA4MyAyODAuOTUgMzc2LjE0MiAzMjYuMTA1IDMzMC44OEMzNzEuMjYgMjg1LjYxOSA0MzEuMjYyIDI2MSA0OTUuMjQ1IDI2MUM1NTkuMjI3IDI2MSA2MTkuMjMgMjg1LjkyNCA2NjQuNTM4IDMzMC44OEw1ODAuOTYzIDQxNC41MjNDNTQ1LjYwNCAzODMuOTQgNTA1LjE5NCAzNzQuNzY2IDQ2MS4yNjQgMzkxLjEyN0MzOTQuOTg1IDQxNS44OTkgMzY2LjgyMSA0ODkuMjk2IDM5Ny40MzUgNTUyLjc1NEMzOTguNjU5IDU1NS4yMDEgMzk4Ljk2NSA1NTguNzE4IDM5OC4zNTMgNTYxLjMxN0MzOTUuNzUxIDU3Mi40OCAzOTIuNjg5IDU4My40OSAzODkuNzgxIDU5NC4zNDZDMzg2LjEwNyA2MDcuOTU1IDM5My45MTQgNjE1Ljc1NCA0MDcuNTM3IDYxMi4yMzdDNDE4LjU1OCA2MDkuMzMxIDQyOS41NzkgNjA2LjEyIDQ0MC42IDYwMy42NzRDNDQzLjM1NSA2MDIuOTA5IDQ0Ny4wMjkgNjAzLjIxNSA0NDkuNjMxIDYwNC40MzhDNDkwLjUgNjIzLjM5OSA1MjkuOTkxIDYyMC44IDU2Ny43OTkgNTk2LjE4MUM1NzIuNTQ0IDU5My4xMjMgNTc2LjY3NyA1ODkuNDUzIDU4MS4xMTYgNTg1Ljc4M1oiIGZpbGw9InVybCgjcGFpbnQyX2xpbmVhcl8yMDkxXzEwMjgyKSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzIwOTFfMTAyODIiIHgxPSI1MDAiIHkxPSIwIiB4Mj0iNTAwIiB5Mj0iMTAwMCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjM1NDIyIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl8yMDkxXzEwMjgyIiB4MT0iNzgyLjUiIHkxPSI3My41IiB4Mj0iMTg1LjUiIHkyPSI4OTQuNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjM1NDIyIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0E1MzEwQyIvPgo8L2xpbmVhckdyYWRpZW50Pgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50Ml9saW5lYXJfMjA5MV8xMDI4MiIgeDE9Ijc1NCIgeTE9Ijg5LjUiIHgyPSIyNTYiIHkyPSIxMDE1LjUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0YzNTQyMiIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNBNzMyMEQiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K
|
354
client/loginex/src/lib.rs
Normal file
@ -0,0 +1,354 @@
|
||||
use crate::hyperware::process::contacts;
|
||||
use anyhow;
|
||||
use hyperware_process_lib::http::client::send_request_await_response;
|
||||
use hyperware_process_lib::http::server::{send_response, HttpBindingConfig, WsBindingConfig};
|
||||
use hyperware_process_lib::http::{Method, StatusCode};
|
||||
use hyperware_process_lib::logging::{info, init_logging, Level};
|
||||
use hyperware_process_lib::{
|
||||
await_message, call_init, eth, get_blob, get_typed_state, homepage, http, hypermap, kiprintln,
|
||||
set_state, Address, Capability, LazyLoadBlob, Message, Request,
|
||||
};
|
||||
use process_macros::SerdeJsonInto;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use url::Url;
|
||||
mod proxy;
|
||||
|
||||
// DEVS: replace with your Web2 site URL, login endpoint and login nonce
|
||||
// TODO if testing with memedeck etc. disable the auth check below, see the other TODO comment
|
||||
// const WEB2_URL: &str = "https://www.memedeck.xyz";
|
||||
const WEB2_URL: &str = "http://localhost:3000";
|
||||
const WEB2_LOGIN_ENDPOINT: &str = "http://localhost:3000/api/hypr-login";
|
||||
const WEB2_LOGIN_NONCE: &str = "lorem ipsum";
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "target/wit",
|
||||
world: "contacts-sys-v0",
|
||||
generate_unused_types: true,
|
||||
additional_derives: [PartialEq, serde::Deserialize, serde::Serialize, process_macros::SerdeJsonInto],
|
||||
});
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum FrontendRequest {
|
||||
Sign,
|
||||
CheckAuth,
|
||||
Logout,
|
||||
Debug(String),
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum LoginRequest {
|
||||
Sign(SignRequest),
|
||||
Verify { from: Address, data: SignResponse },
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct SignRequest {
|
||||
pub site: String,
|
||||
pub time: u64,
|
||||
pub nonce: Option<String>,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, SerdeJsonInto)]
|
||||
struct SignResponse {
|
||||
pub body: SignRequest,
|
||||
pub message: Vec<u8>,
|
||||
pub signature: Vec<u8>,
|
||||
}
|
||||
const ICON: &str = include_str!("icon");
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct ProxyStateV1 {
|
||||
// TODO this should probably be generic and go on login:sys:sys
|
||||
pub auth: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "version")]
|
||||
enum VersionedState {
|
||||
/// State fully stored in memory, persisted using serde_json.
|
||||
/// Future state version will use SQLite.
|
||||
V1(ProxyStateV1),
|
||||
}
|
||||
|
||||
impl VersionedState {
|
||||
fn load() -> Self {
|
||||
get_typed_state(|bytes| serde_json::from_slice(bytes))
|
||||
.unwrap_or(Self::V1(ProxyStateV1 { auth: None }))
|
||||
}
|
||||
fn _save(&self) {
|
||||
set_state(&serde_json::to_vec(&self).expect("Failed to serialize state!"));
|
||||
}
|
||||
fn save_auth(&self, auth: String) {
|
||||
let ns = Self::V1(ProxyStateV1 { auth: Some(auth) });
|
||||
set_state(&serde_json::to_vec(&ns).expect("Failed to serialize state!"));
|
||||
}
|
||||
fn wipe_auth(&self) {
|
||||
let ns = Self::V1(ProxyStateV1 { auth: None });
|
||||
set_state(&serde_json::to_vec(&ns).expect("Failed to serialize state!"));
|
||||
}
|
||||
fn get_auth(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::V1(ps) => ps.auth.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
call_init!(initialize);
|
||||
fn initialize(our: Address) {
|
||||
init_logging(Level::DEBUG, Level::INFO, None, None, None).unwrap();
|
||||
info!("begin");
|
||||
|
||||
homepage::add_to_homepage("Login", Some(ICON), Some("/"), None);
|
||||
|
||||
let mut state = VersionedState::load();
|
||||
let auth = state.get_auth();
|
||||
kiprintln!("auth {:#?}", auth);
|
||||
|
||||
let mut http_server = http::server::HttpServer::new(5);
|
||||
let http_config = HttpBindingConfig::default().secure_subdomain(false);
|
||||
|
||||
http_server
|
||||
.serve_ui("ui", vec!["/hypr-login"], http_config.clone())
|
||||
.expect("Failed to serve UI");
|
||||
http_server.bind_http_path("/", http_config).unwrap();
|
||||
http_server
|
||||
.bind_ws_path("/", WsBindingConfig::default())
|
||||
.unwrap();
|
||||
// let http_config = HttpBindingConfig::default().secure_subdomain(true);
|
||||
|
||||
// http_server
|
||||
// .serve_ui("ui", vec!["/hypr-login"], http_config.clone())
|
||||
// .expect("Failed to serve UI");
|
||||
// http_server.secure_bind_http_path("/").unwrap();
|
||||
|
||||
main_loop(&our, &mut state, &mut http_server);
|
||||
}
|
||||
|
||||
fn main_loop(
|
||||
our: &Address,
|
||||
state: &mut VersionedState,
|
||||
http_server: &mut http::server::HttpServer,
|
||||
) {
|
||||
loop {
|
||||
match await_message() {
|
||||
Err(_send_error) => {
|
||||
// ignore send errors, local-only process
|
||||
continue;
|
||||
}
|
||||
Ok(Message::Request {
|
||||
source,
|
||||
body,
|
||||
capabilities,
|
||||
..
|
||||
}) => {
|
||||
// ignore messages from other nodes -- technically superfluous check
|
||||
// since manifest does not acquire networking capability
|
||||
if source.node() != our.node {
|
||||
continue;
|
||||
}
|
||||
let _ = handle_request(our, &source, &body, capabilities, state, http_server);
|
||||
}
|
||||
_ => continue, // ignore responses
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
our: &Address,
|
||||
source: &Address,
|
||||
body: &[u8],
|
||||
_capabilities: Vec<Capability>,
|
||||
state: &mut VersionedState,
|
||||
http_server: &mut http::server::HttpServer,
|
||||
) -> anyhow::Result<()> {
|
||||
// source node is ALWAYS ourselves since networking is disabled
|
||||
if source.process == "http-server:distro:sys" {
|
||||
// receive HTTP requests and websocket connection messages from our server
|
||||
let server_request = http_server.parse_request(body).unwrap();
|
||||
match server_request {
|
||||
http::server::HttpServerRequest::Http(request) => {
|
||||
handle_http_request(our, state, &request)?;
|
||||
}
|
||||
// TODO handle websockets
|
||||
_ => (),
|
||||
};
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle HTTP requests from our own frontend.
|
||||
fn handle_http_request(
|
||||
our: &Address,
|
||||
state: &mut VersionedState,
|
||||
http_request: &http::server::IncomingHttpRequest,
|
||||
) -> anyhow::Result<()> {
|
||||
let method = http_request.method().unwrap();
|
||||
let url = http_request.url().unwrap();
|
||||
let paths = url.path_segments().unwrap();
|
||||
let last = paths.last().unwrap();
|
||||
if last == "hypr-login" {
|
||||
handle_login_request(our, state, method)?;
|
||||
} else {
|
||||
handle_page_request(our, state, http_request)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_login_request(
|
||||
our: &Address,
|
||||
state: &mut VersionedState,
|
||||
method: Method,
|
||||
) -> anyhow::Result<()> {
|
||||
match method {
|
||||
Method::POST => {
|
||||
let blob = get_blob().unwrap();
|
||||
let request_bytes = blob.bytes();
|
||||
let request = serde_json::from_slice::<FrontendRequest>(request_bytes)?;
|
||||
match request {
|
||||
FrontendRequest::Sign => {
|
||||
let target = Address::new(our.node(), ("login", "login", "sys"));
|
||||
let lr = LoginRequest::Sign(SignRequest {
|
||||
site: WEB2_URL.to_string(),
|
||||
nonce: Some(WEB2_LOGIN_NONCE.to_string()),
|
||||
time: get_now(),
|
||||
});
|
||||
// Get the signature from login:sys:sys
|
||||
let res: SignResponse = Request::to(target)
|
||||
.body(serde_json::to_vec(&lr)?)
|
||||
.send_and_await_response(10)??
|
||||
.body()
|
||||
.try_into()?;
|
||||
// Send signature to designated endpoint on Web2 app
|
||||
attempt_login(our, state, res)?;
|
||||
}
|
||||
FrontendRequest::CheckAuth => {
|
||||
let auth = state.get_auth();
|
||||
let json = match auth {
|
||||
None => serde_json::Value::Null,
|
||||
Some(s) => json!(s),
|
||||
};
|
||||
send_response(StatusCode::OK, None, serde_json::to_vec(&json)?)
|
||||
}
|
||||
FrontendRequest::Logout => {
|
||||
state.wipe_auth();
|
||||
let json = serde_json::to_vec(&json!(true))?;
|
||||
send_response(StatusCode::OK, None, json);
|
||||
}
|
||||
FrontendRequest::Debug(s) => {
|
||||
let value = serde_json::from_str::<serde_json::Value>(&s)?;
|
||||
kiprintln!("frontend log\n{:#?}\n", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
send_response(StatusCode::METHOD_NOT_ALLOWED, None, vec![]);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
fn attempt_login(
|
||||
our: &Address,
|
||||
state: &mut VersionedState,
|
||||
signature_response: SignResponse,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut json_headers = HashMap::new();
|
||||
json_headers.insert("Content-type".to_string(), "application/json".to_string());
|
||||
let node = our.node();
|
||||
let message = signature_response.message;
|
||||
let signature = signature_response.signature;
|
||||
let json =
|
||||
serde_json::to_vec(&json!({"node":node, "message": message, "signature": signature}))?;
|
||||
let url = Url::parse(WEB2_LOGIN_ENDPOINT)?;
|
||||
let res = send_request_await_response(Method::POST, url, Some(json_headers), 5000, json)?;
|
||||
let resbody = res.body();
|
||||
let resjson = serde_json::from_slice::<serde_json::Value>(resbody)?;
|
||||
let okres = resjson.get("ok");
|
||||
match okres {
|
||||
None => {
|
||||
send_json_response(
|
||||
StatusCode::OK,
|
||||
&json!({"error": "Signature verification failed"}),
|
||||
)?;
|
||||
}
|
||||
Some(_) => {
|
||||
let auth_header = res.headers().get("x-hyperware-auth");
|
||||
match auth_header {
|
||||
None => {
|
||||
send_json_response(
|
||||
StatusCode::OK,
|
||||
&json!({"error": "No auth string found in response"}),
|
||||
)?;
|
||||
}
|
||||
Some(av) => {
|
||||
let auth_string = av.to_str()?;
|
||||
state.save_auth(auth_string.to_string());
|
||||
send_json_response(StatusCode::OK, &json!({"ok": "go ahead"}))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
fn handle_page_request(
|
||||
our: &Address,
|
||||
state: &mut VersionedState,
|
||||
http_request: &http::server::IncomingHttpRequest,
|
||||
) -> anyhow::Result<()> {
|
||||
// TODO remove before release
|
||||
let auth = state.get_auth();
|
||||
// let auth = Some(String::new());
|
||||
match auth {
|
||||
None => {
|
||||
let package = our.package_id();
|
||||
let process = our.process();
|
||||
let redirect_to = format!("/{}:{}/hypr-login", process, package);
|
||||
let mut redirect_headers = HashMap::new();
|
||||
redirect_headers.insert("Location".to_string(), redirect_to);
|
||||
send_response(
|
||||
StatusCode::TEMPORARY_REDIRECT,
|
||||
Some(redirect_headers),
|
||||
vec![],
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Some(auth_token) => proxy::run_proxy(http_request, WEB2_URL, &auth_token),
|
||||
}
|
||||
}
|
||||
|
||||
fn _invalid_node(
|
||||
hypermap: &hypermap::Hypermap,
|
||||
node: &str,
|
||||
) -> Option<(contacts::Response, Option<LazyLoadBlob>)> {
|
||||
if hypermap
|
||||
.get(&node)
|
||||
.map(|(tba, _, _)| tba != eth::Address::ZERO)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
contacts::Response::Err("Node name invalid or does not exist".to_string()),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn send_json_response<T: serde::Serialize>(status: StatusCode, data: &T) -> anyhow::Result<()> {
|
||||
let json_data = serde_json::to_vec(data)?;
|
||||
send_response(
|
||||
status,
|
||||
Some(HashMap::from([(
|
||||
String::from("Content-Type"),
|
||||
String::from("application/json"),
|
||||
)])),
|
||||
json_data,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_now() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
599
client/loginex/src/proxy.rs
Normal file
@ -0,0 +1,599 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use hyperware_process_lib::http::server::IncomingHttpRequest;
|
||||
use hyperware_process_lib::kiprintln;
|
||||
use lol_html::html_content::ContentType;
|
||||
use lol_html::{element, text, HtmlRewriter, Settings};
|
||||
use regex::Regex;
|
||||
use url::Url;
|
||||
|
||||
use hyperware_process_lib::{
|
||||
get_blob, http::client::send_request_await_response, http::server::send_response,
|
||||
};
|
||||
|
||||
fn replace_domain(original_url: &Url, new_domain: &str) -> anyhow::Result<Url> {
|
||||
let mut new_url = Url::parse(new_domain)?;
|
||||
new_url.set_path(original_url.path());
|
||||
Ok(new_url)
|
||||
}
|
||||
|
||||
fn split_first_path_segment(url: &Url) -> Result<(String, Url), url::ParseError> {
|
||||
let mut new_url = url.clone();
|
||||
|
||||
// Get the first segment
|
||||
let first_segment = url
|
||||
.path_segments()
|
||||
.and_then(|mut segments| segments.next().map(|s| s.to_string()))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Collect remaining segments
|
||||
let segments: Vec<_> = url
|
||||
.path_segments()
|
||||
.map(|segments| segments.skip(1).collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
|
||||
// Create new path from remaining segments
|
||||
let new_path = if segments.is_empty() {
|
||||
"/"
|
||||
} else {
|
||||
&format!("/{}", segments.join("/"))
|
||||
};
|
||||
|
||||
new_url.set_path(new_path);
|
||||
Ok((first_segment, new_url))
|
||||
}
|
||||
|
||||
fn replace_files(input: &str, output: &str) -> anyhow::Result<String> {
|
||||
// TODO single quotes?
|
||||
let file_ext_regex =
|
||||
Regex::new(r#"\\\"\/[^"]*\.(css|js|ttf|woff2|ico|png|svg|jpg|jpeg|webp|html)[^"]*\\\""#)?;
|
||||
|
||||
let replaced = file_ext_regex
|
||||
.replace_all(input, |caps: ®ex::Captures| {
|
||||
let capture = caps[0].to_string();
|
||||
let quoteless = capture.replace(r#"\""#, "");
|
||||
let news = format!(r#"\"/{}{}\""#, output, quoteless);
|
||||
news
|
||||
})
|
||||
.to_string();
|
||||
Ok(replaced)
|
||||
}
|
||||
fn replace_urls_css(input: &str, output: &str) -> anyhow::Result<String> {
|
||||
// TODO single quotes?
|
||||
let file_ext_regex = Regex::new(r#"url\((\/[^)]+)\)"#)?;
|
||||
|
||||
let replaced = file_ext_regex
|
||||
.replace_all(input, |caps: ®ex::Captures| {
|
||||
let capture = caps[1].to_string();
|
||||
let news = format!(r#"url(/{}{})"#, output, capture);
|
||||
news
|
||||
})
|
||||
.to_string();
|
||||
Ok(replaced)
|
||||
}
|
||||
fn window_shenanigans(s: &str) -> anyhow::Result<()> {
|
||||
let rx = Regex::new(r#"window.location.(href|replace)[^)]+\)"#)?;
|
||||
let mut count = 0;
|
||||
for _mtch in rx.find_iter(s) {
|
||||
count = count + 1;
|
||||
}
|
||||
if count > 1 {
|
||||
kiprintln!("{} matches", count);
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
fn replace_urls_js(input: &str, output: &str) -> anyhow::Result<String> {
|
||||
window_shenanigans(input);
|
||||
// let file_ext_regex = Regex::new(r#"http"#)?;
|
||||
|
||||
// let replaced = file_ext_regex
|
||||
// .replace_all(input, |caps: ®ex::Captures| {
|
||||
// let capture = caps[0].to_string();
|
||||
// let news = format!(r#"/{}{})"#, output, capture);
|
||||
// kiprintln!("js\n{}\n{}\njs", capture, news);
|
||||
// news
|
||||
// })
|
||||
// .to_string();
|
||||
// Ok(replaced)
|
||||
Ok(input.to_owned())
|
||||
}
|
||||
|
||||
fn modify_html(html_bytes: &[u8], prefix: &str) -> anyhow::Result<Vec<u8>> {
|
||||
// Ensure prefix is clean (no leading/trailing slashes for consistency)
|
||||
let prefix = prefix.trim_matches('/');
|
||||
|
||||
// List of attributes that can contain URLs
|
||||
let url_attributes = vec![
|
||||
"href",
|
||||
"src",
|
||||
"action",
|
||||
"background",
|
||||
"cite",
|
||||
"data",
|
||||
"icon",
|
||||
"longdesc",
|
||||
"manifest",
|
||||
"poster",
|
||||
"profile",
|
||||
"usemap",
|
||||
"classid",
|
||||
"codebase",
|
||||
"archive",
|
||||
"code",
|
||||
];
|
||||
//
|
||||
|
||||
// Build a selector for elements with any of these attributes
|
||||
let selector: String = url_attributes
|
||||
.iter()
|
||||
.map(|attr| format!("[{}]", attr))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
// Output buffer for the rewritten HTML
|
||||
let mut output = Vec::new();
|
||||
|
||||
// Create an HTML rewriter with element content handlers
|
||||
let mut rewriter = HtmlRewriter::new(
|
||||
Settings {
|
||||
element_content_handlers: vec![
|
||||
// Handler for elements with URL attributes
|
||||
element!("head", move |el| {
|
||||
el.prepend(&mother_script(prefix), ContentType::Html);
|
||||
Ok(())
|
||||
}),
|
||||
element!(selector, move |el| {
|
||||
for attr in &url_attributes {
|
||||
if let Some(value) = el.get_attribute(attr) {
|
||||
if value.starts_with('/') {
|
||||
let new_value =
|
||||
format!(r#"/{}/{}"#, prefix, value.trim_start_matches('/'));
|
||||
el.set_attribute(attr, &new_value)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
text!("script", |el| {
|
||||
let text_content = el.as_str();
|
||||
// window_shenanigans(text_content);
|
||||
let replaced = replace_files(text_content, prefix)?;
|
||||
el.replace(&replaced, ContentType::Text);
|
||||
|
||||
Ok(())
|
||||
}),
|
||||
],
|
||||
..Settings::default()
|
||||
},
|
||||
|c: &[u8]| output.extend_from_slice(c),
|
||||
);
|
||||
|
||||
// Write the input HTML to the rewriter and finalize
|
||||
rewriter.write(html_bytes)?;
|
||||
rewriter.end()?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub fn run_proxy(
|
||||
request: &IncomingHttpRequest,
|
||||
web2_url: &str,
|
||||
auth_token: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let blob = get_blob().unwrap();
|
||||
let body = blob.bytes().to_vec();
|
||||
let url = replace_domain(&request.url()?, web2_url)?;
|
||||
let (first_path_segment, url) = split_first_path_segment(&url)?;
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("hypr-auth".to_string(), auth_token.to_string());
|
||||
kiprintln!("fetching\n{}", url.to_string());
|
||||
let response = send_request_await_response(request.method()?, url, Some(headers), 6000, body)?;
|
||||
let resheaders = response.headers();
|
||||
// DEVS: choose which headers are necessary for the hyperware client
|
||||
// don't put them all, that doesn't work
|
||||
|
||||
let content_type = resheaders.get("content-type").unwrap().to_str()?;
|
||||
let mime_regex = Regex::new(";.*")?;
|
||||
let mime = mime_regex.replace_all(content_type, "").to_string();
|
||||
// kiprintln!("response headers from proxy {:#?}\n{}", resheaders, mime);
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Content-type".to_string(), content_type.to_owned());
|
||||
let body = match mime.as_str() {
|
||||
"text/html" => {
|
||||
let html = modify_html(response.body(), &first_path_segment)?;
|
||||
html
|
||||
}
|
||||
"text/css" => {
|
||||
let text = String::from_utf8_lossy(response.body()).to_string();
|
||||
let replaced = replace_urls_css(&text, &first_path_segment)?;
|
||||
replaced.as_bytes().to_vec()
|
||||
}
|
||||
"application/javascript" => {
|
||||
let text = String::from_utf8_lossy(response.body()).to_string();
|
||||
let replaced = replace_urls_js(&text, &first_path_segment)?;
|
||||
replaced.as_bytes().to_vec()
|
||||
}
|
||||
_ => response.body().to_vec(),
|
||||
};
|
||||
// let body = modify_html(response.body(), &first_path_segment)?;
|
||||
send_response(response.status(), Some(headers), body);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mother_script(prefix: &str) -> String {
|
||||
let script_text = format!(
|
||||
r#"
|
||||
<script>
|
||||
console.log("mother script running");
|
||||
|
||||
// Store original functions
|
||||
const originalFetch = window.fetch;
|
||||
const originalXHROpen = XMLHttpRequest.prototype.open;
|
||||
const originalCreateElement = document.createElement;
|
||||
|
||||
function getURL(resource){{
|
||||
if (typeof resource === "string") {{
|
||||
const isURL = URL.canParse(resource);
|
||||
if (isURL) return URL.parse(resource)
|
||||
else {{
|
||||
const urlTry = (window.location.origin + resource);
|
||||
if (URL.canParse(urlTry)) return URL.parse(urlTry)
|
||||
else return null
|
||||
}}
|
||||
}}
|
||||
else if ("pathname" in resource) return resource
|
||||
else if ("method" in resource) return URL.parse(resource.url)
|
||||
else {{
|
||||
console.warn("weird fetch", resource);
|
||||
return null
|
||||
}}
|
||||
}}
|
||||
function reRouteToHyperware(url){{
|
||||
console.log("Rewriting URL:", url);
|
||||
if (url.origin === window.location.origin)
|
||||
url.pathname = '{0}' + url.pathname;
|
||||
else console.warn("external fetch?", url.href);
|
||||
return url
|
||||
}}
|
||||
// Log all network activity for debugging
|
||||
window.fetch = async function(resource, options) {{
|
||||
const url1 = getURL(resource);
|
||||
if (!url) return
|
||||
const url = reRouteToHyperware(url1);
|
||||
const res = await originalFetch(url, options);
|
||||
const resc = res.clone();
|
||||
const headers = {{}};
|
||||
for (let [k, v] of resc.headers.entries()){{
|
||||
headers[k] = v;
|
||||
}}
|
||||
console.warn({{url, status: resc.status, headers}});
|
||||
return res
|
||||
}};
|
||||
|
||||
XMLHttpRequest.prototype.open = function(method, url, ...rest) {{
|
||||
console.log("XHR intercepted:", url);
|
||||
let newUrl = url;
|
||||
if (url.includes('/_next/') || url.includes('/static/chunks/')) {{
|
||||
console.log("Rewriting XHR URL:", url);
|
||||
newUrl = '{0}' + url;
|
||||
}}
|
||||
// return originalXHROpen.call(this, method, newUrl, ...rest);
|
||||
}};
|
||||
|
||||
// Most important: intercept script tag creation since webpack uses this
|
||||
document.createElement = function(tagName) {{
|
||||
const element = originalCreateElement.call(document, tagName);
|
||||
console.log("dynamic element created", tagName);
|
||||
const ltn = tagName.toLowerCase();
|
||||
|
||||
if (ltn === 'link' || ltn === "a") {{
|
||||
let originalHref = '';
|
||||
Object.defineProperty(element, 'href', {{
|
||||
get: function() {{
|
||||
return originalHref;
|
||||
}},
|
||||
set: function(value) {{
|
||||
console.log("setting href", {{new: value, old: element.getAttribute("href")}});
|
||||
const uri = typeof value === "string" ? value : value.toString();
|
||||
const url = getURL(uri);
|
||||
originalHref = url;
|
||||
return url;
|
||||
}}
|
||||
}});
|
||||
}}
|
||||
if (['script', 'img', 'video', 'audio', 'track', 'iframe'].includes(ltn)) {{
|
||||
// Add a property setter interceptor for the src attribute
|
||||
let originalSrc = '';
|
||||
Object.defineProperty(element, 'src', {{
|
||||
get: function() {{
|
||||
return originalSrc;
|
||||
}},
|
||||
set: function(value) {{
|
||||
const uri = typeof value === "string" ? value : value.toString();
|
||||
console.log("setting src", {{new: value, old: element.src}});
|
||||
// if (uri.includes("vercel")){{
|
||||
// console.log({{element}});
|
||||
// return ""
|
||||
// }}
|
||||
const url = getURL(uri);
|
||||
if (!url) return
|
||||
originalSrc = url
|
||||
return url;
|
||||
}}
|
||||
}});
|
||||
}}
|
||||
if (ltn === 'img') {{
|
||||
const originalSetAttribute = element.setAttribute;
|
||||
element.setAttribute = function(name, value) {{
|
||||
let newValue = value;
|
||||
console.log("setting img", {{name, value}});
|
||||
if (name === "src" || name === "href"){{
|
||||
const uri = typeof value === "string" ? value : value.toString();
|
||||
const url1 = getURL(uri);
|
||||
if (url1) newValue = reRouteToHyperware(url1);
|
||||
}}
|
||||
return originalSetAttribute.call(this, name, newValue);
|
||||
}};
|
||||
}}
|
||||
if (ltn === 'meta') {{
|
||||
const originalSetAttribute = element.setAttribute;
|
||||
element.setAttribute = function(name, value) {{
|
||||
if (name === 'http-equiv' && value.toLowerCase() === 'refresh') {{
|
||||
console.warn("META REFRESH DETECTED");
|
||||
console.trace("Meta refresh stack trace");
|
||||
debugger;
|
||||
}}
|
||||
return originalSetAttribute.call(this, name, value);
|
||||
}};
|
||||
}}
|
||||
else console.log("element created", {{tagName, element}});
|
||||
|
||||
return element;
|
||||
}};
|
||||
|
||||
// Add monitoring of script tags to see what's happening
|
||||
function observeMutations(){{
|
||||
let oldHref = document.location.href;
|
||||
// let oldHref2 = window.location.href;
|
||||
const body = document.querySelector("body");
|
||||
const observer = new MutationObserver(mutations => {{
|
||||
if (oldHref !== document.location.href){{
|
||||
console.warn("mutation", {{new: document.location.href, old: oldHref}});
|
||||
// console.warn("mutationw", {{neww: window.location.href, oldw: oldHref2}});
|
||||
|
||||
}};
|
||||
|
||||
for (let mutation of mutations){{
|
||||
console.log(mutation.type, mutation)
|
||||
for (let node of mutation.addedNodes){{
|
||||
const attrs = node.getAttributeNames();
|
||||
const data = Array.from(attrs).reduce((acc, item) => ({{...acc, [item]: node.getAttribute(item)}}), {{}});
|
||||
data.outerHTML = node.outerHTML;
|
||||
data.tagName = node.tagName;
|
||||
fuckNext();
|
||||
}}
|
||||
}}
|
||||
}});
|
||||
observer.observe(body, {{childList: true, subtree: true, attributes: true, attributeOldValue: true}})
|
||||
|
||||
}}
|
||||
|
||||
// next router crap
|
||||
|
||||
let fuckingNext = false;
|
||||
// Wait for Next.js to initialize
|
||||
const fuckNext = () => {{
|
||||
if (fuckingNext) return
|
||||
if (window.next && window.next.router) {{
|
||||
console.log("next router", window.next.router);
|
||||
// Store original router methods
|
||||
const originalPush = window.next.router.push;
|
||||
const originalReplace = window.next.router.replace;
|
||||
const originalPrefetch = window.next.router.prefetch;
|
||||
|
||||
// Override router methods
|
||||
window.next.router.push = function() {{
|
||||
console.log("Router push intercepted:", arguments);
|
||||
// Return without calling the original to prevent navigation
|
||||
return new Promise(() => {{}}); // Return a never-resolving promise
|
||||
}};
|
||||
|
||||
window.next.router.replace = function() {{
|
||||
console.log("Router replace intercepted:", arguments);
|
||||
// Return without calling the original to prevent navigation
|
||||
return new Promise(() => {{}});
|
||||
}};
|
||||
window.next.router.refresh= function() {{
|
||||
console.log("Router refresh intercepted:", arguments);
|
||||
// Return without calling the original to prevent navigation
|
||||
return new Promise(() => {{}});
|
||||
}};
|
||||
window.next.router.prefetch = function() {{
|
||||
console.log("Router prefetch intercepted:", arguments);
|
||||
return originalPrefetch.call(arguments)
|
||||
}};
|
||||
window.next.router.hmrRefresh = function() {{
|
||||
console.log("Router hmrRefresh intercepted:", arguments);
|
||||
// Return without calling the original to prevent navigation
|
||||
return new Promise(() => {{}});
|
||||
}};
|
||||
window.next.router.forward = function() {{
|
||||
console.log("Router forward intercepted:", arguments);
|
||||
// Return without calling the original to prevent navigation
|
||||
return new Promise(() => {{}});
|
||||
}};
|
||||
window.next.router.back= function() {{
|
||||
console.log("Router back intercepted:", arguments);
|
||||
// Return without calling the original to prevent navigation
|
||||
return new Promise(() => {{}});
|
||||
}};
|
||||
|
||||
console.log("Next.js router methods intercepted");
|
||||
fuckingNext = true;
|
||||
}} else {{
|
||||
console.log("Next.js router not found");
|
||||
}}
|
||||
}};
|
||||
|
||||
// Listeners
|
||||
// Start observing when body is available
|
||||
document.addEventListener("DOMContentLoaded",observeMutations);
|
||||
navigation.addEventListener("navigate", (e) => {{
|
||||
console.log("navigation", e.destination)
|
||||
console.log("navigation", e.info)
|
||||
console.log("navigation", e.navigationType)
|
||||
console.log("navigation", e.userInitiated)
|
||||
console.log("navigation", e.canIntercept)
|
||||
e.intercept({{
|
||||
async handler(){{
|
||||
console.log("intercepting navigation")
|
||||
}}
|
||||
}})
|
||||
}})
|
||||
window.addEventListener("hashchange", (e) => {{
|
||||
console.warn("navigation", window.location)
|
||||
}})
|
||||
window.addEventListener('beforeunload', function(e) {{
|
||||
remoteLog("unloading!!", e);
|
||||
}});
|
||||
window.addEventListener('popstate', function(e) {{
|
||||
remoteLog("popstate", e);
|
||||
}});
|
||||
window.addEventListener('pageswap', function(e) {{
|
||||
remoteLog("pageswap", e);
|
||||
}});
|
||||
|
||||
const originalListener = window.addEventListener;
|
||||
|
||||
window.addEventListener = function(type, listener, options) {{
|
||||
if (type === 'popstate') {{
|
||||
console.log("Popstate event listener added");
|
||||
// Replace with no-op function
|
||||
return originalListener.call(this, type, function(e) {{
|
||||
console.log("Popstate event prevented", e);
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}}, options);
|
||||
}}
|
||||
return originalListener.call(this, type, listener, options);
|
||||
}};
|
||||
|
||||
// // prevent redirects
|
||||
// const originalLocationDescriptor = Object.getOwnPropertyDescriptor(window, 'location');
|
||||
// // Override location to prevent redirects
|
||||
// let interceptedLocation = {{ ...location }};
|
||||
// Object.defineProperty(window, 'location', {{
|
||||
// get: function() {{
|
||||
// return interceptedLocation;
|
||||
// }},
|
||||
// set: function(value) {{
|
||||
// console.log("PREVENTED REDIRECT to:", value);
|
||||
// console.trace("Redirect stack trace");
|
||||
// // Don't actually set it, just log it
|
||||
// return interceptedLocation;
|
||||
// }}
|
||||
// }});
|
||||
|
||||
// // Also overwrite properties on our fake location
|
||||
// for (let prop in interceptedLocation) {{
|
||||
// if (typeof interceptedLocation[prop] !== 'function') {{
|
||||
// Object.defineProperty(interceptedLocation, prop, {{
|
||||
// get: function() {{
|
||||
// return location[prop];
|
||||
// }},
|
||||
// set: function(value) {{
|
||||
// console.log(`PREVENTED location.${{prop}} change to:`, value);
|
||||
// console.trace(`location.${{prop}} change stack trace`);
|
||||
// // Don't actually apply the change
|
||||
// }}
|
||||
// }});
|
||||
// }}
|
||||
// }}
|
||||
|
||||
// Store logs in sessionStorage
|
||||
const originalConsoleLog = console.warn;
|
||||
console.warn= function(...args) {{{{
|
||||
remoteLog(...args);
|
||||
// originalConsoleLog.apply(this, args);
|
||||
}}}};
|
||||
// console.log = function() {{{{
|
||||
// const jon = JSON.stringify(arguments);
|
||||
// remoteLog(jon);
|
||||
// console.log(arguments)
|
||||
// originalConsoleLog.apply(this, arguments);
|
||||
// }}}};
|
||||
|
||||
|
||||
console.log("mother script finished loading");
|
||||
|
||||
async function remoteLog(...args){{
|
||||
const logObj = Array.from(args).reduce((acc, item, i) => ({{...acc, [i.toString()]: safeStringify(item)}}), {{}});
|
||||
|
||||
await originalFetch("http://localhost:8283", {{
|
||||
method: 'POST',
|
||||
keepalive: true, // This allows the request to outlive the page
|
||||
headers: {{'Content-Type': 'application/json'}},
|
||||
body: JSON.stringify({{
|
||||
Debug: logObj
|
||||
}})
|
||||
}})}}
|
||||
|
||||
|
||||
function safeStringify(obj, depth = 0) {{
|
||||
if (depth > 3) return "[Object depth limit exceeded]";
|
||||
|
||||
try {{
|
||||
if (obj === null) return "null";
|
||||
if (obj === undefined) return "undefined";
|
||||
|
||||
const type = typeof obj;
|
||||
|
||||
// Handle primitive types
|
||||
if (type !== 'object' && type !== 'function') {{
|
||||
return String(obj);
|
||||
}}
|
||||
|
||||
// Handle special objects
|
||||
if (obj instanceof Error) {{
|
||||
return `Error: ${{obj.message}}\n${{obj.stack || ''}}`;
|
||||
}}
|
||||
|
||||
if (obj instanceof HTMLElement) {{
|
||||
return `[${{obj.tagName.toLowerCase()}}${{obj.id ? '#'+obj.id : ''}}]`;
|
||||
}}
|
||||
|
||||
if (Array.isArray(obj)) {{
|
||||
const items = obj.map(item => safeStringify(item, depth + 1));
|
||||
return `[${{items.join(', ')}}]`;
|
||||
}}
|
||||
|
||||
// Handle regular objects
|
||||
const pairs = [];
|
||||
for (const key in obj) {{
|
||||
try {{
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {{
|
||||
const value = safeStringify(obj[key], depth + 1);
|
||||
pairs.push(`${{key}}: ${{value}}`);
|
||||
// Limit number of properties to avoid huge objects
|
||||
if (pairs.length >= 10) {{
|
||||
pairs.push("...");
|
||||
break;
|
||||
}}
|
||||
}}
|
||||
}} catch (e) {{
|
||||
pairs.push(`${{key}}: [Error during serialization]`);
|
||||
}}
|
||||
}}
|
||||
return `{{${{pairs.join(', ')}}}}`;
|
||||
}} catch (e) {{
|
||||
return `[Non-serializable: ${{e.message}}]`;
|
||||
}}
|
||||
}}
|
||||
|
||||
</script>
|
||||
"#,
|
||||
prefix
|
||||
);
|
||||
script_text
|
||||
}
|
18
client/metadata.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Login example",
|
||||
"description": "Trying that login thingy",
|
||||
"image": "",
|
||||
"properties": {
|
||||
"package_name": "login-example",
|
||||
"current_version": "1.0.0",
|
||||
"publisher": "sortugdev.os",
|
||||
"mirrors": [],
|
||||
"code_hashes": {
|
||||
"1.0.0": ""
|
||||
},
|
||||
"wit_version": 1,
|
||||
"dependencies": []
|
||||
},
|
||||
"external_url": "https://hyperware.ai",
|
||||
"animation_url": ""
|
||||
}
|
BIN
client/pkg/api.zip
Normal file
BIN
client/pkg/loginex.wasm
Normal file
23
client/pkg/manifest.json
Normal file
@ -0,0 +1,23 @@
|
||||
[
|
||||
{
|
||||
"process_name": "loginex",
|
||||
"process_wasm_path": "/loginex.wasm",
|
||||
"on_exit": "None",
|
||||
"request_networking": false,
|
||||
"request_capabilities": [
|
||||
"homepage:homepage:sys",
|
||||
"http-server:distro:sys",
|
||||
"http-client:distro:sys",
|
||||
"login:login:sys",
|
||||
"vfs:distro:sys"
|
||||
],
|
||||
"grant_capabilities": [
|
||||
"homepage:homepage:sys",
|
||||
"http-server:distro:sys",
|
||||
"http-client:distro:sys",
|
||||
"login:login:sys",
|
||||
"vfs:distro:sys"
|
||||
],
|
||||
"public": false
|
||||
}
|
||||
]
|
18
client/pkg/scripts.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"get_names.wasm": {
|
||||
"root": false,
|
||||
"public": false,
|
||||
"request_networking": false,
|
||||
"request_capabilities": [
|
||||
"contacts:contacts:sys",
|
||||
{
|
||||
"process": "contacts:contacts:sys",
|
||||
"params": "ReadNameOnly"
|
||||
}
|
||||
],
|
||||
"grant_capabilities": [
|
||||
"contacts:contacts:sys"
|
||||
],
|
||||
"wit_version": 1
|
||||
}
|
||||
}
|
91
client/pkg/ui/index.html
Normal file
@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<link rel="stylesheet" href="/hyperware.css">
|
||||
<link href="https://api.fontshare.com/v2/css?f[]=clash-display@400,700,500,600,300&display=swap" rel="stylesheet">
|
||||
<script src="/our.js"></script>
|
||||
<script>
|
||||
document.title = "Hyperware Login Example - " + window.our.node;
|
||||
</script>
|
||||
<style>
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
a,
|
||||
li {
|
||||
font-family: 'Kode Mono', monospace;
|
||||
}
|
||||
|
||||
#title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
max-width: 720px;
|
||||
min-width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#title h1 {
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#title button {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#form-wrapper {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
main {
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
max-width: 960px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
#login-button {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#error-div {
|
||||
color: darkred;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<span id="title">
|
||||
<a id="back-button" href="/"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 576 512"
|
||||
height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z">
|
||||
</path>
|
||||
</svg></a>
|
||||
<h1>Hyperware Login</h1>
|
||||
</span>
|
||||
<main>
|
||||
<article id="form-wrapper">
|
||||
<form id="form">
|
||||
<button type="submit" id="login-button">Login to </button>
|
||||
</form>
|
||||
</article>
|
||||
<div id="error-div"></div>
|
||||
<script src="/loginex:login-example:sortugdev.os/script.js"></script>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
71
client/pkg/ui/script.js
Normal file
@ -0,0 +1,71 @@
|
||||
// Devs: Replace this with the name of your hyperware package
|
||||
const APP_PATH = '/loginex:login-example:sortugdev.os';
|
||||
// Devs: Replace this with the name of your app
|
||||
const APP_NAME = "Moomdeck";
|
||||
document.addEventListener("DOMContentLoaded", checkAuth);
|
||||
|
||||
|
||||
async function checkAuth() {
|
||||
const res = await call_app({ CheckAuth: null });
|
||||
const j = await res.json();
|
||||
console.log({j})
|
||||
if (j) {
|
||||
const button = document.getElementById('login-button');
|
||||
button.innerText = "Logout"
|
||||
document.getElementById('form').addEventListener('submit', logout);
|
||||
} else {
|
||||
document.getElementById('form').addEventListener('submit', login);
|
||||
}
|
||||
}
|
||||
async function call_app(body) {
|
||||
return await fetch(APP_PATH + "/hypr-login", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
const logout = async (e) => {
|
||||
e.preventDefault();
|
||||
await call_app({Logout: null});
|
||||
window.location.reload();
|
||||
}
|
||||
const login = (e) => {
|
||||
e.preventDefault();
|
||||
const body = {
|
||||
Sign: null
|
||||
};
|
||||
send(body)
|
||||
};
|
||||
document.getElementById('login-button').innerText += ` ${APP_NAME}`;
|
||||
const errorDiv = document.getElementById("error-div");
|
||||
async function send(body) {
|
||||
try {
|
||||
const res = await call_app(body);
|
||||
let j = await res.json();
|
||||
console.log("signature...?", j);
|
||||
if ("error" in j) errorDiv += j.error;
|
||||
else redirect();
|
||||
} catch (e) {
|
||||
console.log(`${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function redirect() {
|
||||
window.location.replace(APP_PATH);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register(APP_PATH + '/sw.js')
|
||||
.then(registration => {
|
||||
console.log('Service Worker registered with scope:', registration.scope);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Service Worker registration failed:', error);
|
||||
});
|
||||
}
|
19
client/pkg/ui/sw.js
Normal file
@ -0,0 +1,19 @@
|
||||
const WEB2_URL = "https://www.memedeck.xyz";
|
||||
const APP_PATH = "/loginex:login-example:sortugdev.os";
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
event.request.url = "/lmao";
|
||||
console.log(event.request.url, "modified?")
|
||||
// Create a new Headers object based on the original request's headers
|
||||
const modifiedHeaders = new Headers(event.request.headers);
|
||||
// Add a custom header as an example modification
|
||||
modifiedHeaders.append('X-Custom-Header', 'my-value');
|
||||
|
||||
// Create a modified request with the new headers
|
||||
const modifiedRequest = new Request(event.request, {
|
||||
headers: modifiedHeaders
|
||||
});
|
||||
|
||||
// Respond with the result of fetching the modified request
|
||||
event.respondWith(fetch(modifiedRequest));
|
||||
});
|
41
hyperware-hybrid-app/.gitignore
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
36
hyperware-hybrid-app/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
7
hyperware-hybrid-app/next.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
957
hyperware-hybrid-app/package-lock.json
generated
Normal file
@ -0,0 +1,957 @@
|
||||
{
|
||||
"name": "hyperware-hybrid-app",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hyperware-hybrid-app",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"hyperware-login": "file:../lib",
|
||||
"next": "15.2.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"typescript": "^5"
|
||||
}
|
||||
},
|
||||
"../lib": {
|
||||
"name": "hyperware-signature-js-lib",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ethersproject/bytes": "^5.8.0",
|
||||
"@noble/ed25519": "^2.2.3",
|
||||
"ethers": "^6.13.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
|
||||
"integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
|
||||
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
|
||||
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
|
||||
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
|
||||
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
|
||||
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
|
||||
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.2.tgz",
|
||||
"integrity": "sha512-yWgopCfA9XDR8ZH3taB5nRKtKJ1Q5fYsTOuYkzIIoS8TJ0UAUKAGF73JnGszbjk2ufAQDj6mDdgsJAFx5CLtYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.2.tgz",
|
||||
"integrity": "sha512-HNBRnz+bkZ+KfyOExpUxTMR0Ow8nkkcE6IlsdEa9W/rI7gefud19+Sn1xYKwB9pdCdxIP1lPru/ZfjfA+iT8pw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.2.tgz",
|
||||
"integrity": "sha512-mJOUwp7al63tDpLpEFpKwwg5jwvtL1lhRW2fI1Aog0nYCPAhxbJsaZKdoVyPZCy8MYf/iQVNDuk/+i29iLCzIA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.2.tgz",
|
||||
"integrity": "sha512-5ZZ0Zwy3SgMr7MfWtRE7cQWVssfOvxYfD9O7XHM7KM4nrf5EOeqwq67ZXDgo86LVmffgsu5tPO57EeFKRnrfSQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.2.tgz",
|
||||
"integrity": "sha512-cgKWBuFMLlJ4TWcFHl1KOaVVUAF8vy4qEvX5KsNd0Yj5mhu989QFCq1WjuaEbv/tO1ZpsQI6h/0YR8bLwEi+nA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.2.tgz",
|
||||
"integrity": "sha512-c3kWSOSsVL8rcNBBfOq1+/j2PKs2nsMwJUV4icUxRgGBwUOfppeh7YhN5s79enBQFU+8xRgVatFkhHU1QW7yUA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.2.tgz",
|
||||
"integrity": "sha512-PXTW9PLTxdNlVYgPJ0equojcq1kNu5NtwcNjRjHAB+/sdoKZ+X8FBu70fdJFadkxFIGekQTyRvPMFF+SOJaQjw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.2.tgz",
|
||||
"integrity": "sha512-nG644Es5llSGEcTaXhnGWR/aThM/hIaz0jx4MDg4gWC8GfTCp8eDBWZ77CVuv2ha/uL9Ce+nPTfYkSLG67/sHg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.2.tgz",
|
||||
"integrity": "sha512-52nWy65S/R6/kejz3jpvHAjZDPKIbEQu4x9jDBzmB9jJfuOy5rspjKu4u77+fI4M/WzLXrrQd57hlFGzz1ubcQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.17.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz",
|
||||
"integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
|
||||
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz",
|
||||
"integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||
"dependencies": {
|
||||
"streamsearch": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001704",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001704.tgz",
|
||||
"integrity": "sha512-+L2IgBbV6gXB4ETf0keSvLr7JUrRVbIaB/lrQ1+z8mRcQiisG5k+lG6O4n6Y5q6f5EuNfaYXKgymucphlEXQew==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/hyperware-login": {
|
||||
"resolved": "../lib",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.9",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz",
|
||||
"integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.2.2.tgz",
|
||||
"integrity": "sha512-dgp8Kcx5XZRjMw2KNwBtUzhngRaURPioxoNIVl5BOyJbhi9CUgEtKDO7fx5wh8Z8vOVX1nYZ9meawJoRrlASYA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "15.2.2",
|
||||
"@swc/counter": "0.1.3",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"busboy": "1.6.0",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
"postcss": "8.4.31",
|
||||
"styled-jsx": "5.1.6"
|
||||
},
|
||||
"bin": {
|
||||
"next": "dist/bin/next"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "15.2.2",
|
||||
"@next/swc-darwin-x64": "15.2.2",
|
||||
"@next/swc-linux-arm64-gnu": "15.2.2",
|
||||
"@next/swc-linux-arm64-musl": "15.2.2",
|
||||
"@next/swc-linux-x64-gnu": "15.2.2",
|
||||
"@next/swc-linux-x64-musl": "15.2.2",
|
||||
"@next/swc-win32-arm64-msvc": "15.2.2",
|
||||
"@next/swc-win32-x64-msvc": "15.2.2",
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": "^1.1.0",
|
||||
"@playwright/test": "^1.41.2",
|
||||
"babel-plugin-react-compiler": "*",
|
||||
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
||||
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
||||
"sass": "^1.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@opentelemetry/api": {
|
||||
"optional": true
|
||||
},
|
||||
"@playwright/test": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-plugin-react-compiler": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
||||
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.25.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
|
||||
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
||||
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.33.5",
|
||||
"@img/sharp-darwin-x64": "0.33.5",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
|
||||
"@img/sharp-linux-arm": "0.33.5",
|
||||
"@img/sharp-linux-arm64": "0.33.5",
|
||||
"@img/sharp-linux-s390x": "0.33.5",
|
||||
"@img/sharp-linux-x64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.5",
|
||||
"@img/sharp-wasm32": "0.33.5",
|
||||
"@img/sharp-win32-ia32": "0.33.5",
|
||||
"@img/sharp-win32-x64": "0.33.5"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-jsx": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"client-only": "0.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@babel/core": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-plugin-macros": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
23
hyperware-hybrid-app/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "hyperware-hybrid-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.0.0",
|
||||
"hyperware-login": "file:../lib",
|
||||
"react-dom": "^19.0.0",
|
||||
"next": "15.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19"
|
||||
}
|
||||
}
|
BIN
hyperware-hybrid-app/public/favicon.png
Normal file
After Width: | Height: | Size: 28 KiB |
1
hyperware-hybrid-app/public/file.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 391 B |
1
hyperware-hybrid-app/public/globe.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
After Width: | Height: | Size: 1.0 KiB |
48
hyperware-hybrid-app/public/logo.svg
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="207.85339mm"
|
||||
height="212.0607mm"
|
||||
viewBox="0 0 207.85339 212.0607"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.3.1 (9b9bdc1480, 2023-11-25, custom)"
|
||||
sodipodi:docname="logo.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.0868961"
|
||||
inkscape:cx="382.28126"
|
||||
inkscape:cy="400.68228"
|
||||
inkscape:window-width="2510"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="50"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" /><defs
|
||||
id="defs1" /><g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-0.27483167,-2.8325584)"><rect
|
||||
style="vector-effect:non-scaling-stroke;fill:#e5ff44;fill-opacity:1;stroke:none;stroke-width:0.264583;-inkscape-stroke:hairline"
|
||||
id="rect2"
|
||||
width="183.83772"
|
||||
height="184.75897"
|
||||
x="9.7677469"
|
||||
y="14.452851" /><path
|
||||
style="fill:#000000"
|
||||
d="M 23.429211,213.83557 C 12.319378,210.43918 2.438871,199.11529 0.817349,187.9205 0.32877,184.54741 0.133717,146.55637 0.383898,103.49598 0.882546,17.670433 0.481078,21.494955 9.989743,11.986296 19.562608,2.4134317 15.206152,2.8353267 104.48198,2.8353267 c 89.27583,0 84.91937,-0.421895 94.49224,9.1509693 9.58696,9.586966 9.15097,4.978313 9.15097,96.729124 0,91.75082 0.43599,87.14217 -9.15097,96.72913 -9.53208,9.53207 -5.54334,9.12475 -92.25534,9.421 -43.470493,0.14852 -80.950845,-0.31497 -83.289669,-1.02998 z m 39.687202,-31.67559 c 8.736853,-6.66392 10.048914,-11.38133 10.048914,-36.1301 0,-21.88556 0,-21.88556 4.359089,-26.24465 4.3591,-4.3591 4.3591,-4.3591 23.975024,-4.3591 19.61593,0 19.61593,0 23.97502,4.3591 4.3591,4.35909 4.3591,4.35909 4.38363,26.47002 0.0223,20.11479 0.32523,22.63935 3.35535,27.96413 6.05242,10.63581 22.05389,15.90641 32.26326,10.62694 2.29811,-1.1884 6.29121,-4.56711 8.87356,-7.50824 4.69518,-5.34751 4.69518,-5.34751 4.6896,-72.0299 -0.005,-63.564319 -0.14204,-66.906213 -2.92403,-71.468868 -9.36209,-15.354431 -34.65898,-13.881841 -42.92692,2.498873 -3.04561,6.034058 -3.35535,8.762975 -3.35535,29.561137 0,22.913398 0,22.913398 -4.3591,27.272488 -4.35909,4.3591 -4.35909,4.3591 -23.97502,4.3591 -19.615924,0 -19.615924,0 -23.975024,-4.3591 -4.359089,-4.35909 -4.359089,-4.35909 -4.359089,-27.735916 0,-26.438146 -1.193321,-30.866888 -10.159845,-37.705988 -4.499226,-3.431727 -6.712043,-4.016813 -15.19173,-4.016813 -8.514964,0 -10.684691,0.579126 -15.268455,4.075333 -10.073594,7.683504 -10.242943,9.063193 -9.763915,79.546984 0.426429,62.74455 0.426429,62.74455 4.111836,68.12106 5.740374,8.37442 12.601707,11.75236 22.638818,11.14542 6.288634,-0.38028 9.738092,-1.5082 13.584377,-4.44191 z M 39.8429,167.87594 c -2.211047,-2.44318 -2.468032,-9.01848 -2.468032,-63.1478 0,-56.640653 0.170614,-60.575063 2.727144,-62.888691 3.106905,-2.81171 8.060453,-3.184446 12.283585,-0.924294 2.780501,1.488078 2.8845,3.769328 2.8845,63.272405 0,54.12862 -0.288523,62.0172 -2.343423,64.0721 -3.164164,3.16417 -10.055899,2.96204 -13.083774,-0.38372 z m 110.22931,0.38372 c -2.05391,-2.05391 -2.34342,-9.87078 -2.34342,-63.27241 0,-66.938987 -0.18088,-65.615833 8.96957,-65.615833 8.85291,0 8.92566,0.534803 8.92566,65.615833 0,50.97438 -0.31535,59.43734 -2.32147,62.30148 -2.75682,3.93592 -9.752,4.44927 -13.23034,0.97093 z"
|
||||
id="path2" /></g></svg>
|
After Width: | Height: | Size: 3.9 KiB |
1
hyperware-hybrid-app/public/next.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
hyperware-hybrid-app/public/vercel.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
After Width: | Height: | Size: 128 B |
1
hyperware-hybrid-app/public/window.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
After Width: | Height: | Size: 385 B |
43
hyperware-hybrid-app/src/app/api/hypr-login/route.ts
Normal file
@ -0,0 +1,43 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import KimapClient from "hyperware-login";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
type Data =
|
||||
| {
|
||||
ok: boolean;
|
||||
}
|
||||
| { error: string };
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { node, message, signature } = await req.json();
|
||||
console.log({ node, message, signature });
|
||||
const kimap = new KimapClient(process.env.BASESCAN_API_KEY!);
|
||||
const verificationResponse = await kimap.verifySignature(
|
||||
node,
|
||||
message,
|
||||
signature,
|
||||
);
|
||||
console.log({ verificationResponse });
|
||||
if ("error" in verificationResponse)
|
||||
return Response.json(verificationResponse, { status: 500 });
|
||||
else {
|
||||
// const isGud = true;
|
||||
const isGud = verificationResponse.ok;
|
||||
if (isGud) {
|
||||
// generate some cookie-ish string here
|
||||
const auth = "henlo";
|
||||
const authToken = `${node}:${auth}`;
|
||||
|
||||
return Response.json(
|
||||
{ ok: isGud },
|
||||
{ headers: { "x-hyperware-auth": authToken } },
|
||||
);
|
||||
}
|
||||
return Response.json({ error: "invalid signature" });
|
||||
}
|
||||
} catch (e) {
|
||||
return Response.json({ error: "bad request" }, { status: 500 });
|
||||
}
|
||||
}
|
42
hyperware-hybrid-app/src/app/globals.css
Normal file
@ -0,0 +1,42 @@
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
39
hyperware-hybrid-app/src/app/layout.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Hyperware Hybrid App Example",
|
||||
description: "visit https://github.com/hyperware-ai/hyperware-login",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<link rel="icon" href="/favicon.png" type="image/png" sizes="16x16" />
|
||||
|
||||
<link
|
||||
precedence="default"
|
||||
href="https://db.onlinewebfonts.com/c/cecb4be234bd82f3bc858201426b2b59?family=CHANEY+Ultra+Extended"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<body className={`${geistSans.variable} ${geistMono.variable}`}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
94
hyperware-hybrid-app/src/app/lol
Normal file
@ -0,0 +1,94 @@
|
||||
import Image from "next/image";
|
||||
import styles from "./page.module.css";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<main className={styles.main}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol>
|
||||
<li>
|
||||
<h1>FUCK THIS</h1>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div className={styles.ctas}>
|
||||
<a
|
||||
className={styles.primary}
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.secondary}
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className={styles.footer}>
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
197
hyperware-hybrid-app/src/app/page.module.css
Normal file
@ -0,0 +1,197 @@
|
||||
.page {
|
||||
--gray-rgb: 0, 0, 0;
|
||||
--gray-alpha-200: rgba(var(--gray-rgb), 0.08);
|
||||
--gray-alpha-100: rgba(var(--gray-rgb), 0.05);
|
||||
|
||||
--button-primary-hover: #383838;
|
||||
--button-secondary-hover: #f2f2f2;
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: 20px 1fr 20px;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
min-height: 100svh;
|
||||
padding: 80px;
|
||||
gap: 64px;
|
||||
font-family: var(--font-geist-sans);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.page {
|
||||
--gray-rgb: 255, 255, 255;
|
||||
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
|
||||
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
|
||||
|
||||
--button-primary-hover: #ccc;
|
||||
--button-secondary-hover: #1a1a1a;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
grid-row-start: 2;
|
||||
}
|
||||
|
||||
.main ol {
|
||||
font-family: var(--font-geist-mono);
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.01em;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.main li:not(:last-of-type) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.main code {
|
||||
font-family: inherit;
|
||||
background: var(--gray-alpha-100);
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ctas {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ctas a {
|
||||
appearance: none;
|
||||
border-radius: 128px;
|
||||
height: 48px;
|
||||
padding: 0 20px;
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
transition:
|
||||
background 0.2s,
|
||||
color 0.2s,
|
||||
border-color 0.2s;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a.primary {
|
||||
background: var(--foreground);
|
||||
color: var(--background);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
a.secondary {
|
||||
border-color: var(--gray-alpha-200);
|
||||
min-width: 158px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
grid-row-start: 3;
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.footer img {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Enable hover only on non-touch devices */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
a.primary:hover {
|
||||
background: var(--button-primary-hover);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
a.secondary:hover {
|
||||
background: var(--button-secondary-hover);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.page {
|
||||
padding: 32px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.main {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main ol {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ctas {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ctas a {
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
a.secondary {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.logo {
|
||||
filter: invert();
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.hyw {
|
||||
font-family: CHANEY Ultra Extended, sans-serif;
|
||||
color: rgb(229 255 68 / var(--un-text-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.hyw {
|
||||
font-family: CHANEY Ultra Extended, sans-serif;
|
||||
color: darkgreen;
|
||||
}
|
||||
}
|
||||
|
||||
.hywlogo {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.appheader {
|
||||
margin: auto;
|
||||
width: 70%;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
98
hyperware-hybrid-app/src/app/page.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import { headers, cookies } from "next/headers";
|
||||
import Image from "next/image";
|
||||
import styles from "./page.module.css";
|
||||
|
||||
export default async function Home() {
|
||||
const hdrs = await headers();
|
||||
const hypr_auth = hdrs.get("hypr-auth");
|
||||
console.log(hdrs.get("hypr-auth"), "lol?");
|
||||
const cokis = await cookies();
|
||||
console.log({ cokis });
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<main className={styles.main}>
|
||||
<div className={styles.appheader}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/logo.svg"
|
||||
alt="Hyperware logo"
|
||||
width={180}
|
||||
height={80}
|
||||
priority
|
||||
/>
|
||||
<h3 className={`${styles.hyw} ${styles.hywlogo}`}>Hyperware Login</h3>
|
||||
</div>
|
||||
{hypr_auth ? <HyperLogged token={hypr_auth} /> : <LoginPrompt />}
|
||||
</main>
|
||||
<footer className={styles.footer}>
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
href="https://hyperware.ai"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to hyperware.ai →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HyperLogged({ token }: { token: string }) {
|
||||
const [node, tok] = token.split(":");
|
||||
return <div className={styles.ctas}>Hi {node}!</div>;
|
||||
}
|
||||
function LoginPrompt() {
|
||||
return (
|
||||
<div>
|
||||
<p style={{ textAlign: "center", marginBottom: "2rem" }}>
|
||||
You are not logged in
|
||||
</p>
|
||||
<div className={styles.ctas}>
|
||||
<a
|
||||
className={styles.primary}
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Get on Hyperware
|
||||
</a>
|
||||
<a
|
||||
href="https://hyperware.ai"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.secondary}
|
||||
>
|
||||
Read More
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
27
hyperware-hybrid-app/tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
12
lib/dist/index.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import { ethers } from "ethers";
|
||||
import type { AsyncRes } from "./types.js";
|
||||
declare class KimapClient {
|
||||
apiKey: string;
|
||||
contract: ethers.Contract;
|
||||
constructor(apiKey: string);
|
||||
fetchAbi(): Promise<any>;
|
||||
getPublicKey(name: string): AsyncRes<string>;
|
||||
namehash(name: string): Promise<string>;
|
||||
verifySignature(node: string, messageArray: number[], signatureArray: number[]): AsyncRes<boolean>;
|
||||
}
|
||||
export default KimapClient;
|
147
lib/dist/index.js
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
import { ethers } from "ethers";
|
||||
import * as ed from "@noble/ed25519";
|
||||
const KIMAP_ABI = [
|
||||
"function get(bytes32 namehash) external view returns (address tba, address owner, bytes memory data)",
|
||||
"function leaf(bytes32 parenthash, bytes calldata label) external pure returns (bytes32 namehash)",
|
||||
"function tbaOf(bytes32 entry) external view returns (address tba)",
|
||||
];
|
||||
const KIMAP_ADDRESS = "0x000000000044C6B8Cb4d8f0F889a3E47664EAeda";
|
||||
// const KIMAP_ADDRESS = "0x969cAbCE3625224BA3d340ea4dC2f929301188Ad";
|
||||
const RPC_URL = "https://base-mainnet.g.alchemy.com/v2/jWsKWKv217Jz-9Nnnu7uJpW6gqmBcNog";
|
||||
class KimapClient {
|
||||
apiKey;
|
||||
contract;
|
||||
constructor(apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
const provider = ethers.getDefaultProvider(RPC_URL);
|
||||
const contract = new ethers.Contract(KIMAP_ADDRESS, KIMAP_ABI, provider);
|
||||
this.contract = contract;
|
||||
}
|
||||
async fetchAbi() {
|
||||
const url = `https://api.basescan.org/api?module=contract&action=getabi&address=${KIMAP_ADDRESS}&apikey=${this.apiKey}`;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
if (data.status === "1") {
|
||||
return data.result; // ABI as a JSON string
|
||||
}
|
||||
else {
|
||||
throw new Error("Failed to fetch ABI: " + data.message);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error fetching ABI:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async getPublicKey(name) {
|
||||
const hash = await this.namehash(name);
|
||||
console.log({ name, hash, contract: KIMAP_ADDRESS });
|
||||
// const res = await this.contract!.get(hash);
|
||||
// console.log({ res });
|
||||
const netKeyNoteNamehash = await this.contract.leaf(hash, ethers.toUtf8Bytes("~net-key"));
|
||||
const stoof = await this.contract.get(hash);
|
||||
console.log({ stoof });
|
||||
const [tba, owner, data] = await this.contract.get(netKeyNoteNamehash);
|
||||
if (!data || data === "0x") {
|
||||
return { error: "no data" };
|
||||
}
|
||||
return { ok: data };
|
||||
}
|
||||
async namehash(name) {
|
||||
let hash = ethers.ZeroHash;
|
||||
const labels = name.split(".").reverse();
|
||||
for (const part of labels) {
|
||||
const newhash = await this.contract.leaf(hash, ethers.toUtf8Bytes(part));
|
||||
hash = newhash;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
async verifySignature(node, messageArray, signatureArray) {
|
||||
// This is not a Browser transaction signature, this doesn't work
|
||||
try {
|
||||
const publicKey = await this.getPublicKey(node);
|
||||
console.log({ publicKey });
|
||||
if ("error" in publicKey)
|
||||
return { error: "no public key found for " + node };
|
||||
const publicKeyBytes = ethers.getBytes(publicKey.ok);
|
||||
let messageBytes = new Uint8Array(messageArray);
|
||||
let signatureBytes = new Uint8Array(signatureArray);
|
||||
const isValid = await ed.verifyAsync(signatureBytes, messageBytes, publicKeyBytes);
|
||||
return { ok: isValid };
|
||||
}
|
||||
catch (e) {
|
||||
return { error: `${e}` };
|
||||
}
|
||||
}
|
||||
}
|
||||
export default KimapClient;
|
||||
// export function getClient() {
|
||||
// const client = new KimapClient(KIMAP_ADDRESS, Bun.env.BASESCAN_API_KEY!);
|
||||
// return client;
|
||||
// }
|
||||
// async setContract() {
|
||||
// const abi = await this.fetchAbi();
|
||||
// // console.log(JSON.parse(abi));
|
||||
// const provider = ethers.getDefaultProvider(RPC_URL);
|
||||
// const contract = new ethers.Contract(
|
||||
// this.contractAddress,
|
||||
// abi,
|
||||
// provider,
|
||||
// );
|
||||
// this.contract = contract;
|
||||
// }
|
||||
// async verifySignature(
|
||||
// node: string,
|
||||
// messageBytes: BytesLike,
|
||||
// signatureBytes: BytesLike,
|
||||
// ): Promise<boolean> {
|
||||
// // This is not a Browser transaction signature, this doesn't work
|
||||
// const publicKey = await this.getPublicKey(node);
|
||||
// console.log({ publicKey });
|
||||
// if ("error" in publicKey) return false;
|
||||
// // const messageBytes = ethers.toUtf8Bytes(message);
|
||||
// const r = ethers.hexlify(signatureBytes.slice(0, 32));
|
||||
// const s = ethers.hexlify(signatureBytes.slice(32, 64));
|
||||
// const v = Number(ethers.hexlify(signatureBytes.slice(64, 65)));
|
||||
// const signature = { r, s, v };
|
||||
// const messageHash = ethers.hashMessage(messageBytes);
|
||||
// const recovered = ethers.recoverAddress(messageHash, signature);
|
||||
// // const lol: ethers.SignatureLike = "";
|
||||
// console.log({ messageHash, recovered, publicKey });
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// const toCheck = {
|
||||
// data: {
|
||||
// body: {
|
||||
// site: "f",
|
||||
// time: 1741297185312,
|
||||
// nonce: "ff",
|
||||
// },
|
||||
// message: [
|
||||
// 115, 111, 114, 116, 117, 103, 100, 101, 118, 111, 46, 111, 115, 64, 108,
|
||||
// 111, 103, 105, 110, 58, 108, 111, 103, 105, 110, 58, 115, 121, 115, 123,
|
||||
// 34, 83, 105, 103, 110, 34, 58, 123, 34, 115, 105, 116, 101, 34, 58, 34,
|
||||
// 102, 34, 44, 34, 116, 105, 109, 101, 34, 58, 49, 55, 52, 49, 50, 57, 55,
|
||||
// 49, 56, 53, 51, 49, 50, 44, 34, 110, 111, 110, 99, 101, 34, 58, 34, 102,
|
||||
// 102, 34, 125, 125,
|
||||
// ],
|
||||
// signature: [
|
||||
// 41, 45, 105, 167, 143, 76, 68, 129, 157, 100, 71, 117, 117, 151, 89, 80,
|
||||
// 40, 118, 14, 21, 235, 190, 98, 239, 156, 131, 225, 216, 167, 160, 96, 43,
|
||||
// 132, 148, 254, 29, 49, 164, 131, 181, 205, 197, 136, 63, 175, 223, 57,
|
||||
// 231, 253, 87, 130, 12, 118, 153, 72, 201, 246, 125, 200, 70, 4, 176, 98,
|
||||
// 9,
|
||||
// ],
|
||||
// },
|
||||
// };
|
||||
// async function run() {
|
||||
// const client = new KimapClient(KIMAP_ADDRESS, BASESCAN_API_KEY);
|
||||
// // await client.setContract();
|
||||
// // client.getPublicKey("sortugdevo.os");
|
||||
// const messageBytes = new Uint8Array(toCheck.data.message);
|
||||
// const signatureBytes = new Uint8Array(toCheck.data.signature);
|
||||
// client.verifySignature("sortugdevo.os", messageBytes, signatureBytes);
|
||||
// }
|
||||
// run();
|
6
lib/dist/types.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
export type Result<T> = {
|
||||
ok: T;
|
||||
} | {
|
||||
error: string;
|
||||
};
|
||||
export type AsyncRes<T> = Promise<Result<T>>;
|
1
lib/dist/types.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
168
lib/package-lock.json
generated
Normal file
@ -0,0 +1,168 @@
|
||||
{
|
||||
"name": "hyperware-signature-js-lib",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hyperware-signature-js-lib",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ethersproject/bytes": "^5.8.0",
|
||||
"@noble/ed25519": "^2.2.3",
|
||||
"ethers": "^6.13.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@adraffy/ens-normalize": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
|
||||
"integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ethersproject/bytes": {
|
||||
"version": "5.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz",
|
||||
"integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.buymeacoffee.com/ricmoo"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ethersproject/logger": "^5.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ethersproject/logger": {
|
||||
"version": "5.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz",
|
||||
"integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.buymeacoffee.com/ricmoo"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
|
||||
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/ed25519": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.2.3.tgz",
|
||||
"integrity": "sha512-iHV8eI2mRcUmOx159QNrU8vTpQ/Xm70yJ2cTk3Trc86++02usfqFoNl6x0p3JN81ZDS/1gx6xiK0OwrgqCT43g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
|
||||
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
|
||||
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/aes-js": {
|
||||
"version": "4.0.0-beta.5",
|
||||
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
|
||||
"integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ethers": {
|
||||
"version": "6.13.5",
|
||||
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz",
|
||||
"integrity": "sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/ethers-io/"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.buymeacoffee.com/ricmoo"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@adraffy/ens-normalize": "1.10.1",
|
||||
"@noble/curves": "1.2.0",
|
||||
"@noble/hashes": "1.3.2",
|
||||
"@types/node": "22.7.5",
|
||||
"aes-js": "4.0.0-beta.5",
|
||||
"tslib": "2.7.0",
|
||||
"ws": "8.17.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
lib/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "hyperware-signature-js-lib",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"description": "JavaScript library to validate hyperware signatures",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Hyperware",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ethersproject/bytes": "^5.8.0",
|
||||
"@noble/ed25519": "^2.2.3",
|
||||
"ethers": "^6.13.5"
|
||||
}
|
||||
}
|
163
lib/src/index.ts
Normal file
@ -0,0 +1,163 @@
|
||||
import { ethers, type BytesLike } from "ethers";
|
||||
import type { AsyncRes } from "./types.js";
|
||||
import * as ed from "@noble/ed25519";
|
||||
|
||||
const KIMAP_ABI = [
|
||||
"function get(bytes32 namehash) external view returns (address tba, address owner, bytes memory data)",
|
||||
"function leaf(bytes32 parenthash, bytes calldata label) external pure returns (bytes32 namehash)",
|
||||
"function tbaOf(bytes32 entry) external view returns (address tba)",
|
||||
];
|
||||
|
||||
const KIMAP_ADDRESS = "0x000000000044C6B8Cb4d8f0F889a3E47664EAeda";
|
||||
// const KIMAP_ADDRESS = "0x969cAbCE3625224BA3d340ea4dC2f929301188Ad";
|
||||
const RPC_URL =
|
||||
"https://base-mainnet.g.alchemy.com/v2/jWsKWKv217Jz-9Nnnu7uJpW6gqmBcNog";
|
||||
class KimapClient {
|
||||
apiKey;
|
||||
contract;
|
||||
constructor(apiKey: string) {
|
||||
this.apiKey = apiKey;
|
||||
const provider = ethers.getDefaultProvider(RPC_URL);
|
||||
const contract = new ethers.Contract(KIMAP_ADDRESS, KIMAP_ABI, provider);
|
||||
this.contract = contract;
|
||||
}
|
||||
async fetchAbi() {
|
||||
const url = `https://api.basescan.org/api?module=contract&action=getabi&address=${KIMAP_ADDRESS}&apikey=${this.apiKey}`;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
if (data.status === "1") {
|
||||
return data.result; // ABI as a JSON string
|
||||
} else {
|
||||
throw new Error("Failed to fetch ABI: " + data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching ABI:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async getPublicKey(name: string): AsyncRes<string> {
|
||||
const hash = await this.namehash(name);
|
||||
console.log({ name, hash, contract: KIMAP_ADDRESS });
|
||||
// const res = await this.contract!.get(hash);
|
||||
// console.log({ res });
|
||||
const netKeyNoteNamehash = await this.contract.leaf(
|
||||
hash,
|
||||
ethers.toUtf8Bytes("~net-key"),
|
||||
);
|
||||
const stoof = await this.contract.get(hash);
|
||||
console.log({ stoof });
|
||||
const [tba, owner, data] = await this.contract.get(netKeyNoteNamehash);
|
||||
if (!data || data === "0x") {
|
||||
return { error: "no data" };
|
||||
}
|
||||
return { ok: data };
|
||||
}
|
||||
async namehash(name: string) {
|
||||
let hash = ethers.ZeroHash;
|
||||
const labels = name.split(".").reverse();
|
||||
for (const part of labels) {
|
||||
const newhash = await this.contract!.leaf(hash, ethers.toUtf8Bytes(part));
|
||||
hash = newhash;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
async verifySignature(
|
||||
node: string,
|
||||
messageArray: number[],
|
||||
signatureArray: number[],
|
||||
): AsyncRes<boolean> {
|
||||
// This is not a Browser transaction signature, this doesn't work
|
||||
try {
|
||||
const publicKey = await this.getPublicKey(node);
|
||||
console.log({ publicKey });
|
||||
if ("error" in publicKey)
|
||||
return { error: "no public key found for " + node };
|
||||
const publicKeyBytes = ethers.getBytes(publicKey.ok);
|
||||
let messageBytes = new Uint8Array(messageArray);
|
||||
let signatureBytes = new Uint8Array(signatureArray);
|
||||
const isValid = await ed.verifyAsync(
|
||||
signatureBytes,
|
||||
messageBytes,
|
||||
publicKeyBytes,
|
||||
);
|
||||
return { ok: isValid };
|
||||
} catch (e) {
|
||||
return { error: `${e}` };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default KimapClient;
|
||||
|
||||
// export function getClient() {
|
||||
// const client = new KimapClient(KIMAP_ADDRESS, Bun.env.BASESCAN_API_KEY!);
|
||||
// return client;
|
||||
// }
|
||||
// async setContract() {
|
||||
// const abi = await this.fetchAbi();
|
||||
// // console.log(JSON.parse(abi));
|
||||
// const provider = ethers.getDefaultProvider(RPC_URL);
|
||||
// const contract = new ethers.Contract(
|
||||
// this.contractAddress,
|
||||
// abi,
|
||||
// provider,
|
||||
// );
|
||||
// this.contract = contract;
|
||||
// }
|
||||
// async verifySignature(
|
||||
// node: string,
|
||||
// messageBytes: BytesLike,
|
||||
// signatureBytes: BytesLike,
|
||||
// ): Promise<boolean> {
|
||||
// // This is not a Browser transaction signature, this doesn't work
|
||||
// const publicKey = await this.getPublicKey(node);
|
||||
// console.log({ publicKey });
|
||||
// if ("error" in publicKey) return false;
|
||||
// // const messageBytes = ethers.toUtf8Bytes(message);
|
||||
// const r = ethers.hexlify(signatureBytes.slice(0, 32));
|
||||
// const s = ethers.hexlify(signatureBytes.slice(32, 64));
|
||||
// const v = Number(ethers.hexlify(signatureBytes.slice(64, 65)));
|
||||
// const signature = { r, s, v };
|
||||
// const messageHash = ethers.hashMessage(messageBytes);
|
||||
// const recovered = ethers.recoverAddress(messageHash, signature);
|
||||
// // const lol: ethers.SignatureLike = "";
|
||||
// console.log({ messageHash, recovered, publicKey });
|
||||
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// const toCheck = {
|
||||
// data: {
|
||||
// body: {
|
||||
// site: "f",
|
||||
// time: 1741297185312,
|
||||
// nonce: "ff",
|
||||
// },
|
||||
// message: [
|
||||
// 115, 111, 114, 116, 117, 103, 100, 101, 118, 111, 46, 111, 115, 64, 108,
|
||||
// 111, 103, 105, 110, 58, 108, 111, 103, 105, 110, 58, 115, 121, 115, 123,
|
||||
// 34, 83, 105, 103, 110, 34, 58, 123, 34, 115, 105, 116, 101, 34, 58, 34,
|
||||
// 102, 34, 44, 34, 116, 105, 109, 101, 34, 58, 49, 55, 52, 49, 50, 57, 55,
|
||||
// 49, 56, 53, 51, 49, 50, 44, 34, 110, 111, 110, 99, 101, 34, 58, 34, 102,
|
||||
// 102, 34, 125, 125,
|
||||
// ],
|
||||
// signature: [
|
||||
// 41, 45, 105, 167, 143, 76, 68, 129, 157, 100, 71, 117, 117, 151, 89, 80,
|
||||
// 40, 118, 14, 21, 235, 190, 98, 239, 156, 131, 225, 216, 167, 160, 96, 43,
|
||||
// 132, 148, 254, 29, 49, 164, 131, 181, 205, 197, 136, 63, 175, 223, 57,
|
||||
// 231, 253, 87, 130, 12, 118, 153, 72, 201, 246, 125, 200, 70, 4, 176, 98,
|
||||
// 9,
|
||||
// ],
|
||||
// },
|
||||
// };
|
||||
// async function run() {
|
||||
// const client = new KimapClient(KIMAP_ADDRESS, BASESCAN_API_KEY);
|
||||
// // await client.setContract();
|
||||
// // client.getPublicKey("sortugdevo.os");
|
||||
// const messageBytes = new Uint8Array(toCheck.data.message);
|
||||
// const signatureBytes = new Uint8Array(toCheck.data.signature);
|
||||
// client.verifySignature("sortugdevo.os", messageBytes, signatureBytes);
|
||||
// }
|
||||
// run();
|
2
lib/src/types.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export type Result<T> = { ok: T } | { error: string };
|
||||
export type AsyncRes<T> = Promise<Result<T>>;
|
25
lib/tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"moduleDetection": "force",
|
||||
"declaration": true,
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
3706
login/Cargo.lock
generated
Normal file
10
login/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"login",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
6
login/api/login:sys-v0.wit
Normal file
@ -0,0 +1,6 @@
|
||||
interface all {}
|
||||
|
||||
world login-sys-v0 {
|
||||
import all;
|
||||
include process-v1;
|
||||
}
|
22
login/login/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "login"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
simulation-mode = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.97"
|
||||
hyperware_process_lib = "1.0.3"
|
||||
process_macros = "0.1"
|
||||
rmp-serde = "1.3.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = "0.36.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "hyperware:process"
|
1
login/login/src/icon
Normal file
@ -0,0 +1 @@
|
||||
data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwMCIgaGVpZ2h0PSIxMDAwIiB2aWV3Qm94PSIwIDAgMTAwMCAxMDAwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cmVjdCB4PSIyMCIgeT0iMjAiIHdpZHRoPSI5NjAiIGhlaWdodD0iOTYwIiByeD0iNDgwIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfMjA5MV8xMDI4MikiIGZpbGwtb3BhY2l0eT0iMC40Ii8+CjxyZWN0IHg9IjIwIiB5PSIyMCIgd2lkdGg9Ijk2MCIgaGVpZ2h0PSI5NjAiIHJ4PSI0ODAiIHN0cm9rZT0idXJsKCNwYWludDFfbGluZWFyXzIwOTFfMTAyODIpIiBzdHJva2Utd2lkdGg9IjQwIi8+CjxwYXRoIGQ9Ik01ODEuMTE2IDU4NS43ODNMNjY0LjUzOCA2NjkuMTJDNjE3LjY5OSA3MTUuNzU4IDU1Ni42MjUgNzM5IDQ5NS4yNDUgNzM5QzQzMy44NjUgNzM5IDM3Mi42MzggNzE1LjYwNSAzMjYuMTA1IDY2OS4xMkMyODAuNzk3IDYyMy44NTggMjU2IDU2My45MTcgMjU2IDUwMEMyNTYgNDM2LjA4MyAyODAuOTUgMzc2LjE0MiAzMjYuMTA1IDMzMC44OEMzNzEuMjYgMjg1LjYxOSA0MzEuMjYyIDI2MSA0OTUuMjQ1IDI2MUM1NTkuMjI3IDI2MSA2MTkuMjMgMjg1LjkyNCA2NjQuNTM4IDMzMC44OEw1ODAuOTYzIDQxNC41MjNDNTQ1LjYwNCAzODMuOTQgNTA1LjE5NCAzNzQuNzY2IDQ2MS4yNjQgMzkxLjEyN0MzOTQuOTg1IDQxNS44OTkgMzY2LjgyMSA0ODkuMjk2IDM5Ny40MzUgNTUyLjc1NEMzOTguNjU5IDU1NS4yMDEgMzk4Ljk2NSA1NTguNzE4IDM5OC4zNTMgNTYxLjMxN0MzOTUuNzUxIDU3Mi40OCAzOTIuNjg5IDU4My40OSAzODkuNzgxIDU5NC4zNDZDMzg2LjEwNyA2MDcuOTU1IDM5My45MTQgNjE1Ljc1NCA0MDcuNTM3IDYxMi4yMzdDNDE4LjU1OCA2MDkuMzMxIDQyOS41NzkgNjA2LjEyIDQ0MC42IDYwMy42NzRDNDQzLjM1NSA2MDIuOTA5IDQ0Ny4wMjkgNjAzLjIxNSA0NDkuNjMxIDYwNC40MzhDNDkwLjUgNjIzLjM5OSA1MjkuOTkxIDYyMC44IDU2Ny43OTkgNTk2LjE4MUM1NzIuNTQ0IDU5My4xMjMgNTc2LjY3NyA1ODkuNDUzIDU4MS4xMTYgNTg1Ljc4M1oiIGZpbGw9InVybCgjcGFpbnQyX2xpbmVhcl8yMDkxXzEwMjgyKSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzIwOTFfMTAyODIiIHgxPSI1MDAiIHkxPSIwIiB4Mj0iNTAwIiB5Mj0iMTAwMCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjM1NDIyIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl8yMDkxXzEwMjgyIiB4MT0iNzgyLjUiIHkxPSI3My41IiB4Mj0iMTg1LjUiIHkyPSI4OTQuNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjM1NDIyIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0E1MzEwQyIvPgo8L2xpbmVhckdyYWRpZW50Pgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50Ml9saW5lYXJfMjA5MV8xMDI4MiIgeDE9Ijc1NCIgeTE9Ijg5LjUiIHgyPSIyNTYiIHkyPSIxMDE1LjUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0YzNTQyMiIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNBNzMyMEQiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K
|
165
login/login/src/lib.rs
Normal file
@ -0,0 +1,165 @@
|
||||
use crate::hyperware::process;
|
||||
use anyhow::{anyhow, Result};
|
||||
use hyperware::process::standard::send_response;
|
||||
use hyperware_process_lib::net::{NetAction, NetResponse};
|
||||
use hyperware_process_lib::{
|
||||
await_message, call_init, eth, get_blob, get_typed_state, homepage, http, hypermap, kiprintln,
|
||||
set_state, Address, Capability, LazyLoadBlob, Message, NodeId, Request, Response,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "target/wit",
|
||||
world: "login-sys-v0",
|
||||
generate_unused_types: true,
|
||||
additional_derives: [PartialEq, serde::Deserialize, serde::Serialize, process_macros::SerdeJsonInto],
|
||||
});
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct LoginStateV1 {
|
||||
our: Address,
|
||||
apps: HashMap<NodeId, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum LoginRequest {
|
||||
Sign(SignRequest),
|
||||
Verify { from: Address, data: SignResponse },
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct SignRequest {
|
||||
pub site: String,
|
||||
pub time: u64,
|
||||
pub nonce: Option<String>,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct SignResponse {
|
||||
pub body: SignRequest,
|
||||
pub message: Vec<u8>,
|
||||
pub signature: Vec<u8>,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "version")]
|
||||
enum VersionedState {
|
||||
/// State fully stored in memory, persisted using serde_json.
|
||||
/// Future state version will use SQLite.
|
||||
V1(LoginStateV1),
|
||||
}
|
||||
|
||||
impl VersionedState {
|
||||
fn load(our: Address) -> Self {
|
||||
get_typed_state(|bytes| serde_json::from_slice(bytes)).unwrap_or(Self::V1(LoginStateV1 {
|
||||
our,
|
||||
apps: HashMap::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn save(&self) {
|
||||
set_state(&serde_json::to_vec(&self).expect("Failed to serialize contacts state!"));
|
||||
}
|
||||
}
|
||||
|
||||
call_init!(initialize);
|
||||
fn initialize(our: Address) {
|
||||
let mut state = VersionedState::load(our.clone());
|
||||
loop {
|
||||
let msg = await_message();
|
||||
match msg {
|
||||
Err(_send_error) => {
|
||||
// ignore send errors, local-only process
|
||||
continue;
|
||||
}
|
||||
Ok(Message::Request {
|
||||
source,
|
||||
body,
|
||||
capabilities,
|
||||
..
|
||||
}) => {
|
||||
handle_request(&our, &source, &mut state, &body, capabilities).unwrap_or_default()
|
||||
}
|
||||
_ => continue, // ignore responses
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
our: &Address,
|
||||
source: &Address,
|
||||
state: &mut VersionedState,
|
||||
request_bytes: &[u8],
|
||||
capabilities: Vec<Capability>,
|
||||
) -> Result<()> {
|
||||
let req = serde_json::from_slice::<LoginRequest>(request_bytes)?;
|
||||
match req {
|
||||
LoginRequest::Sign(r) => handle_sign(our, r, request_bytes),
|
||||
LoginRequest::Verify { from, data } => handle_verify(from, data),
|
||||
}
|
||||
}
|
||||
|
||||
// let message = [
|
||||
// from.to_string().as_bytes(),
|
||||
// &km.lazy_load_blob
|
||||
// .as_ref()
|
||||
// .unwrap_or(&lib::core::LazyLoadBlob {
|
||||
// mime: None,
|
||||
// bytes: vec![],
|
||||
// })
|
||||
// .bytes,
|
||||
// ]
|
||||
// .concat();
|
||||
// pub fn validate_signature(from: &str, signature: &[u8], message: &[u8], pki: &OnchainPKI) -> bool {
|
||||
// if let Some(peer_id) = pki.get(from) {
|
||||
// let their_networking_key = signature::UnparsedPublicKey::new(
|
||||
// &signature::ED25519,
|
||||
// net_key_string_to_hex(&peer_id.networking_key),
|
||||
// );
|
||||
// their_networking_key.verify(message, signature).is_ok()
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// }
|
||||
|
||||
fn handle_sign(our: &Address, req: SignRequest, request_bytes: &[u8]) -> Result<()> {
|
||||
let body = rmp_serde::to_vec(&NetAction::Sign)?;
|
||||
let res = Request::to(("our", "net", "distro", "sys"))
|
||||
.blob_bytes(request_bytes)
|
||||
.body(body)
|
||||
.send_and_await_response(10)??;
|
||||
let Ok(NetResponse::Signed) = rmp_serde::from_slice::<NetResponse>(res.body()) else {
|
||||
return Err(anyhow!("signature failed"));
|
||||
};
|
||||
let newblob = res.blob();
|
||||
let message = [our.to_string().as_bytes(), request_bytes].concat();
|
||||
match newblob {
|
||||
None => Err(anyhow!("no blob")),
|
||||
Some(b) => {
|
||||
let lr = SignResponse {
|
||||
body: req,
|
||||
message,
|
||||
signature: b.bytes().to_vec(),
|
||||
};
|
||||
let lrj = serde_json::to_vec(&lr)?;
|
||||
Response::new().body(lrj).send()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn handle_verify(from: Address, data: SignResponse) -> Result<()> {
|
||||
let signature = data.signature;
|
||||
let body = rmp_serde::to_vec(&NetAction::Verify { from, signature })?;
|
||||
let req_bytes = rmp_serde::to_vec(&data.body)?;
|
||||
let res = Request::to(("our", "net", "distro", "sys"))
|
||||
.blob_bytes(req_bytes)
|
||||
.body(body)
|
||||
.send_and_await_response(10)??;
|
||||
let resp = rmp_serde::from_slice::<NetResponse>(res.body())?;
|
||||
match resp {
|
||||
NetResponse::Verified(is_good) => {
|
||||
Response::new().body(serde_json::to_vec(&is_good)?).send()?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(anyhow!("weird response")),
|
||||
}
|
||||
}
|
18
login/metadata.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Login",
|
||||
"description": "Login with Hyperware",
|
||||
"image": "",
|
||||
"properties": {
|
||||
"package_name": "login",
|
||||
"current_version": "1.0.0",
|
||||
"publisher": "sys",
|
||||
"mirrors": [],
|
||||
"code_hashes": {
|
||||
"1.0.0": ""
|
||||
},
|
||||
"wit_version": 1,
|
||||
"dependencies": []
|
||||
},
|
||||
"external_url": "https://hyperware.ai",
|
||||
"animation_url": ""
|
||||
}
|
BIN
login/pkg/api.zip
Normal file
BIN
login/pkg/login.wasm
Normal file
11
login/pkg/manifest.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"process_name": "login",
|
||||
"process_wasm_path": "/login.wasm",
|
||||
"on_exit": "Restart",
|
||||
"request_networking": false,
|
||||
"request_capabilities": ["net:distro:sys"],
|
||||
"grant_capabilities": ["net:distro:sys"],
|
||||
"public": false
|
||||
}
|
||||
]
|