mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 09:51:39 +00:00
feature/1114 implement account interface (#1167)
- **refactor: move session-related code to middleware package** - **refactor: update PKL build process and adjust related configurations** - **feat: integrate base.cosmos.v1 Genesis module** - **refactor: pass session context to modal rendering functions** - **refactor: move nebula package to app directory and update templ version** - **refactor: Move home section video view to dedicated directory** - **refactor: remove unused views file** - **refactor: move styles and UI components to global scope** - **refactor: Rename images.go to cdn.go** - **feat: Add Empty State Illustrations** - **refactor: Consolidate Vault Index Logic** - **fix: References to App.wasm and remove Vault Directory embedded CDN files** - **refactor: Move CDN types to Models** - **fix: Correct line numbers in templ error messages for arch_templ.go** - **refactor: use common types for peer roles** - **refactor: move common types and ORM to a shared package** - **fix: Config import dwn** - **refactor: move nebula directory to app** - **feat: Rebuild nebula** - **fix: correct file paths in panels templates** - **feat: Remove duplicate types** - **refactor: Move dwn to pkg/core** - **refactor: Binary Structure** - **feat: Introduce Crypto Pkg** - **fix: Broken Process Start** - **feat: Update pkg/* structure** - **feat: Refactor PKL Structure** - **build: update pkl build process** - **chore: Remove Empty Files** - **refactor: remove unused macaroon package** - **feat: Add WebAwesome Components** - **refactor: consolidate build and generation tasks into a single taskfile, remove redundant makefile targets** - **refactor: refactor server and move components to pkg/core/dwn** - **build: update go modules** - **refactor: move gateway logic into dedicated hway command** - **feat: Add KSS (Krawczyk-Song-Song) MPC cryptography module** - **feat: Implement MPC-based JWT signing and UCAN token generation** - **feat: add support for MPC-based JWT signing** - **feat: Implement MPC-based UCAN capabilities for smart accounts** - **feat: add address field to keyshareSource** - **feat: Add comprehensive MPC test suite for keyshares, UCAN tokens, and token attenuations** - **refactor: improve MPC keyshare management and signing process** - **feat: enhance MPC capability hierarchy documentation** - **refactor: rename GenerateKeyshares function to NewKeyshareSource for clarity** - **refactor: remove unused Ethereum address computation** - **feat: Add HasHandle and IsAuthenticated methods to HTTPContext** - **refactor: Add context.Context support to session HTTPContext** - **refactor: Resolve context interface conflicts in HTTPContext** - **feat: Add session ID context key and helper functions** - **feat: Update WebApp Page Rendering** - **refactor: Simplify context management by using single HTTPContext key** - **refactor: Simplify HTTPContext creation and context management in session middleware** - **refactor: refactor session middleware to use a single data structure** - **refactor: Simplify HTTPContext implementation and session data handling** - **refactor: Improve session context handling and prevent nil pointer errors** - **refactor: Improve session context handling with nil safety and type support** - **refactor: improve session data injection** - **feat: add full-screen modal component and update registration flow** - **chore: add .air.toml to .gitignore** - **feat: add Air to devbox and update dependencies**
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
// Code generated from Pkl module `common.types.DWN`. DO NOT EDIT.
|
||||
package dwn
|
||||
|
||||
type Config struct {
|
||||
IpfsGatewayUrl string `pkl:"ipfsGatewayUrl" json:"ipfsGatewayUrl,omitempty"`
|
||||
|
||||
MotrKeyshare string `pkl:"motrKeyshare" json:"motrKeyshare,omitempty"`
|
||||
|
||||
MotrAddress string `pkl:"motrAddress" json:"motrAddress,omitempty"`
|
||||
|
||||
SonrApiUrl string `pkl:"sonrApiUrl" json:"sonrApiUrl,omitempty"`
|
||||
|
||||
SonrRpcUrl string `pkl:"sonrRpcUrl" json:"sonrRpcUrl,omitempty"`
|
||||
|
||||
SonrChainId string `pkl:"sonrChainId" json:"sonrChainId,omitempty"`
|
||||
|
||||
VaultSchema *Schema `pkl:"vaultSchema" json:"vaultSchema,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Code generated from Pkl module `common.types.DWN`. DO NOT EDIT.
|
||||
package dwn
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/apple/pkl-go/pkl"
|
||||
)
|
||||
|
||||
type DWN struct {
|
||||
}
|
||||
|
||||
// LoadFromPath loads the pkl module at the given path and evaluates it into a DWN
|
||||
func LoadFromPath(ctx context.Context, path string) (ret *DWN, err error) {
|
||||
evaluator, err := pkl.NewEvaluator(ctx, pkl.PreconfiguredOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
cerr := evaluator.Close()
|
||||
if err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
ret, err = Load(ctx, evaluator, pkl.FileSource(path))
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Load loads the pkl module at the given source and evaluates it with the given evaluator into a DWN
|
||||
func Load(ctx context.Context, evaluator pkl.Evaluator, source *pkl.ModuleSource) (*DWN, error) {
|
||||
var ret DWN
|
||||
if err := evaluator.EvaluateModule(ctx, source, &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Code generated from Pkl module `common.types.DWN`. DO NOT EDIT.
|
||||
package dwn
|
||||
|
||||
type Environment struct {
|
||||
IsDevelopment bool `pkl:"isDevelopment" json:"isDevelopment,omitempty"`
|
||||
|
||||
CacheVersion string `pkl:"cacheVersion" json:"cacheVersion,omitempty"`
|
||||
|
||||
HttpserverPath string `pkl:"httpserverPath" json:"httpserverPath,omitempty"`
|
||||
|
||||
WasmExecPath string `pkl:"wasmExecPath" json:"wasmExecPath,omitempty"`
|
||||
|
||||
WasmPath string `pkl:"wasmPath" json:"wasmPath,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Code generated from Pkl module `common.types.DWN`. DO NOT EDIT.
|
||||
package dwn
|
||||
|
||||
type Schema struct {
|
||||
Version int `pkl:"version"`
|
||||
|
||||
Account string `pkl:"account" json:"account,omitempty"`
|
||||
|
||||
Asset string `pkl:"asset" json:"asset,omitempty"`
|
||||
|
||||
Chain string `pkl:"chain" json:"chain,omitempty"`
|
||||
|
||||
Credential string `pkl:"credential" json:"credential,omitempty"`
|
||||
|
||||
Jwk string `pkl:"jwk" json:"jwk,omitempty"`
|
||||
|
||||
Grant string `pkl:"grant" json:"grant,omitempty"`
|
||||
|
||||
Keyshare string `pkl:"keyshare" json:"keyshare,omitempty"`
|
||||
|
||||
Profile string `pkl:"profile" json:"profile,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
var (
|
||||
// Global buffer pool to reduce allocations
|
||||
bufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
// Cached JS globals
|
||||
jsGlobal = js.Global()
|
||||
jsUint8Array = jsGlobal.Get("Uint8Array")
|
||||
jsResponse = jsGlobal.Get("Response")
|
||||
jsPromise = jsGlobal.Get("Promise")
|
||||
jsWasmHTTP = jsGlobal.Get("wasmhttp")
|
||||
)
|
||||
|
||||
// serveFetch serves HTTP requests with optimized handler management
|
||||
func ServeFetch(handler http.Handler) func() {
|
||||
h := handler
|
||||
if h == nil {
|
||||
h = http.DefaultServeMux
|
||||
}
|
||||
|
||||
// Optimize prefix handling
|
||||
prefix := strings.TrimRight(jsWasmHTTP.Get("path").String(), "/")
|
||||
if prefix != "" {
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle(prefix+"/", http.StripPrefix(prefix, h))
|
||||
h = mux
|
||||
}
|
||||
|
||||
// Create request handler function
|
||||
cb := js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
|
||||
promise, resolve, reject := newPromiseOptimized()
|
||||
|
||||
go handleRequest(h, args[1], resolve, reject)
|
||||
|
||||
return promise
|
||||
})
|
||||
|
||||
jsWasmHTTP.Call("setHandler", cb)
|
||||
return cb.Release
|
||||
}
|
||||
|
||||
// handleRequest processes the request with panic recovery
|
||||
func handleRequest(h http.Handler, jsReq js.Value, resolve, reject func(interface{})) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var errMsg string
|
||||
if err, ok := r.(error); ok {
|
||||
errMsg = fmt.Sprintf("wasmhttp: panic: %+v", err)
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("wasmhttp: panic: %v", r)
|
||||
}
|
||||
reject(errMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
recorder := newResponseRecorder()
|
||||
h.ServeHTTP(recorder, buildRequest(jsReq))
|
||||
resolve(recorder.jsResponse())
|
||||
}
|
||||
|
||||
// buildRequest creates an http.Request from JS Request
|
||||
func buildRequest(jsReq js.Value) *http.Request {
|
||||
// Get request body
|
||||
arrayBuffer, err := awaitPromiseOptimized(jsReq.Call("arrayBuffer"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create body buffer
|
||||
jsBody := jsUint8Array.New(arrayBuffer)
|
||||
bodyLen := jsBody.Get("length").Int()
|
||||
body := make([]byte, bodyLen)
|
||||
js.CopyBytesToGo(body, jsBody)
|
||||
|
||||
// Create request
|
||||
req := httptest.NewRequest(
|
||||
jsReq.Get("method").String(),
|
||||
jsReq.Get("url").String(),
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
|
||||
// Set headers efficiently
|
||||
headers := jsReq.Get("headers")
|
||||
headersIt := headers.Call("entries")
|
||||
for {
|
||||
entry := headersIt.Call("next")
|
||||
if entry.Get("done").Bool() {
|
||||
break
|
||||
}
|
||||
pair := entry.Get("value")
|
||||
req.Header.Set(pair.Index(0).String(), pair.Index(1).String())
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
// ResponseRecorder with optimized buffer handling
|
||||
type ResponseRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
func newResponseRecorder() *ResponseRecorder {
|
||||
return &ResponseRecorder{
|
||||
ResponseRecorder: httptest.NewRecorder(),
|
||||
buffer: bufferPool.Get().(*bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
// jsResponse creates a JS Response with optimized memory usage
|
||||
func (rr *ResponseRecorder) jsResponse() js.Value {
|
||||
defer func() {
|
||||
rr.buffer.Reset()
|
||||
bufferPool.Put(rr.buffer)
|
||||
}()
|
||||
|
||||
res := rr.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
// Prepare response body
|
||||
body := js.Undefined()
|
||||
if res.ContentLength != 0 {
|
||||
if _, err := io.Copy(rr.buffer, res.Body); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bodyBytes := rr.buffer.Bytes()
|
||||
body = jsUint8Array.New(len(bodyBytes))
|
||||
js.CopyBytesToJS(body, bodyBytes)
|
||||
}
|
||||
|
||||
// Prepare response init object
|
||||
init := make(map[string]interface{}, 3)
|
||||
if res.StatusCode != 0 {
|
||||
init["status"] = res.StatusCode
|
||||
}
|
||||
|
||||
if len(res.Header) > 0 {
|
||||
headers := make(map[string]interface{}, len(res.Header))
|
||||
for k, v := range res.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
}
|
||||
}
|
||||
init["headers"] = headers
|
||||
}
|
||||
|
||||
return jsResponse.New(body, init)
|
||||
}
|
||||
|
||||
// newPromiseOptimized creates a new JavaScript Promise with optimized callback handling
|
||||
func newPromiseOptimized() (js.Value, func(interface{}), func(interface{})) {
|
||||
var (
|
||||
resolve func(interface{})
|
||||
reject func(interface{})
|
||||
promiseFunc = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
|
||||
resolve = func(v interface{}) { args[0].Invoke(v) }
|
||||
reject = func(v interface{}) { args[1].Invoke(v) }
|
||||
return js.Undefined()
|
||||
})
|
||||
)
|
||||
defer promiseFunc.Release()
|
||||
|
||||
return jsPromise.New(promiseFunc), resolve, reject
|
||||
}
|
||||
|
||||
// awaitPromiseOptimized waits for Promise resolution with optimized channel handling
|
||||
func awaitPromiseOptimized(promise js.Value) (js.Value, error) {
|
||||
done := make(chan struct{})
|
||||
var (
|
||||
result js.Value
|
||||
err error
|
||||
)
|
||||
|
||||
thenFunc := js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
|
||||
result = args[0]
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
defer thenFunc.Release()
|
||||
|
||||
catchFunc := js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
|
||||
err = js.Error{Value: args[0]}
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
defer catchFunc.Release()
|
||||
|
||||
promise.Call("then", thenFunc).Call("catch", catchFunc)
|
||||
<-done
|
||||
|
||||
return result, err
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func WasmContextMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
// Extract WASM context from headers
|
||||
if wasmCtx := c.Request().Header.Get("X-Wasm-Context"); wasmCtx != "" {
|
||||
if ctx, err := DecodeWasmContext(wasmCtx); err == nil {
|
||||
c.Set("wasm_context", ctx)
|
||||
}
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
// decodeWasmContext decodes the WASM context from a base64 encoded string
|
||||
func DecodeWasmContext(ctx string) (map[string]any, error) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ctxData map[string]any
|
||||
err = json.Unmarshal(decoded, &ctxData)
|
||||
return ctxData, err
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package dwn
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
)
|
||||
|
||||
const dwnJSONFileName = "dwn.json"
|
||||
|
||||
func LoadJSONConfig() (*Config, error) {
|
||||
// Read dwn.json config
|
||||
dwnBz, err := os.ReadFile(dwnJSONFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dwnConfig := new(Config)
|
||||
err = json.Unmarshal(dwnBz, dwnConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dwnConfig, nil
|
||||
}
|
||||
|
||||
func (c *Config) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
func (c *Config) UnmarshalJSON(data []byte) error {
|
||||
return json.Unmarshal(data, c)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Login Handlers │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
|
||||
// LoginSubjectCheck handles the login subject check.
|
||||
func LoginSubjectCheck(e echo.Context) error {
|
||||
return e.JSON(200, "HandleCredentialAssertion")
|
||||
}
|
||||
|
||||
// LoginSubjectStart handles the login subject start.
|
||||
func LoginSubjectStart(e echo.Context) error {
|
||||
opts := &protocol.PublicKeyCredentialRequestOptions{
|
||||
UserVerification: "preferred",
|
||||
Challenge: []byte("challenge"),
|
||||
}
|
||||
return e.JSON(200, opts)
|
||||
}
|
||||
|
||||
// LoginSubjectFinish handles the login subject finish.
|
||||
func LoginSubjectFinish(e echo.Context) error {
|
||||
var crr protocol.CredentialAssertionResponse
|
||||
if err := e.Bind(&crr); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.JSON(200, crr)
|
||||
}
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Register Handlers │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
|
||||
// RegisterSubjectCheck handles the register subject check.
|
||||
func RegisterSubjectCheck(e echo.Context) error {
|
||||
subject := e.FormValue("subject")
|
||||
return e.JSON(200, subject)
|
||||
}
|
||||
|
||||
// RegisterSubjectStart handles the register subject start.
|
||||
func RegisterSubjectStart(e echo.Context) error {
|
||||
// Get subject and address
|
||||
// subject := e.FormValue("subject")
|
||||
|
||||
// Get challenge
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterSubjectFinish handles the register subject finish.
|
||||
func RegisterSubjectFinish(e echo.Context) error {
|
||||
// Deserialize the JSON into a temporary struct
|
||||
var ccr protocol.CredentialCreationResponse
|
||||
if err := e.Bind(&ccr); err != nil {
|
||||
return err
|
||||
}
|
||||
//
|
||||
// // Parse the CredentialCreationResponse
|
||||
// parsedData, err := ccr.Parse()
|
||||
// if err != nil {
|
||||
// return e.JSON(500, err.Error())
|
||||
// }
|
||||
//
|
||||
// // Create the Credential
|
||||
// // credential := orm.NewCredential(parsedData, e.Request().Host, "")
|
||||
return e.JSON(201, ccr)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func GrantAuthorization(e echo.Context) error {
|
||||
// Implement authorization endpoint using passkey authentication
|
||||
// Store session data in cache
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetJWKS(e echo.Context) error {
|
||||
// Implement token endpoint
|
||||
// Use cached session data for validation
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetToken(e echo.Context) error {
|
||||
// Implement token endpoint
|
||||
// Use cached session data for validation
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/pkg/core/dwn"
|
||||
)
|
||||
|
||||
// generateRawServiceWorkerJS returns the service worker JavaScript as a string
|
||||
func generateRawServiceWorkerJS(cfg *dwn.Environment) string {
|
||||
return fmt.Sprintf(`const CACHE_NAMES = {
|
||||
wasm: "wasm-cache-%s",
|
||||
static: "static-cache-%s",
|
||||
dynamic: "dynamic-cache-%s"
|
||||
};
|
||||
|
||||
importScripts(
|
||||
%q,
|
||||
%q
|
||||
);
|
||||
|
||||
// Initialize WASM HTTP listener with configured path
|
||||
const wasmInstance = registerWasmHTTPListener(%q);
|
||||
|
||||
// MessageChannel port for WASM communication
|
||||
let wasmPort;
|
||||
|
||||
// Request queue for offline operations
|
||||
let requestQueue = new Map();
|
||||
|
||||
// Setup message channel handler
|
||||
self.addEventListener('message', async (event) => {
|
||||
if (event.data.type === 'PORT_INITIALIZATION') {
|
||||
wasmPort = event.data.port;
|
||||
setupWasmCommunication();
|
||||
}
|
||||
});
|
||||
|
||||
function setupWasmCommunication() {
|
||||
wasmPort.onmessage = async (event) => {
|
||||
const { type, data } = event.data;
|
||||
|
||||
switch (type) {
|
||||
case 'WASM_REQUEST':
|
||||
handleWasmRequest(data);
|
||||
break;
|
||||
case 'SYNC_REQUEST':
|
||||
processSyncQueue();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Notify that WASM is ready
|
||||
wasmPort.postMessage({ type: 'WASM_READY' });
|
||||
}
|
||||
|
||||
// Enhanced install event
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
Promise.all([
|
||||
skipWaiting(),
|
||||
// Cache WASM binary and essential resources
|
||||
caches.open(CACHE_NAMES.wasm).then(cache =>
|
||||
cache.addAll([
|
||||
%q,
|
||||
%q
|
||||
])
|
||||
)
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
// Enhanced activate event
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
Promise.all([
|
||||
clients.claim(),
|
||||
// Clean up old caches
|
||||
caches.keys().then(keys =>
|
||||
Promise.all(
|
||||
keys.map(key => {
|
||||
if (!Object.values(CACHE_NAMES).includes(key)) {
|
||||
return caches.delete(key);
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
// Intercept fetch events
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const request = event.request;
|
||||
|
||||
// Handle API requests differently from static resources
|
||||
if (request.url.includes('/api/')) {
|
||||
event.respondWith(handleApiRequest(request));
|
||||
} else {
|
||||
event.respondWith(handleStaticRequest(request));
|
||||
}
|
||||
});
|
||||
|
||||
async function handleApiRequest(request) {
|
||||
try {
|
||||
// Try to make the request
|
||||
const response = await fetch(request.clone());
|
||||
|
||||
// If successful, pass through WASM handler
|
||||
if (response.ok) {
|
||||
return await processWasmResponse(request, response);
|
||||
}
|
||||
|
||||
// If offline or failed, queue the request
|
||||
await queueRequest(request);
|
||||
|
||||
// Return cached response if available
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
// Return offline response
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Currently offline' }),
|
||||
{
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
await queueRequest(request);
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Request failed' }),
|
||||
{
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStaticRequest(request) {
|
||||
// Check cache first
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
|
||||
// Cache successful responses
|
||||
if (response.ok) {
|
||||
const cache = await caches.open(CACHE_NAMES.static);
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
// Return offline page for navigation requests
|
||||
if (request.mode === 'navigate') {
|
||||
return caches.match('/offline.html');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function processWasmResponse(request, response) {
|
||||
const responseClone = response.clone();
|
||||
|
||||
try {
|
||||
const processedResponse = await wasmInstance.processResponse(responseClone);
|
||||
|
||||
if (wasmPort) {
|
||||
wasmPort.postMessage({
|
||||
type: 'RESPONSE',
|
||||
requestId: request.headers.get('X-Wasm-Request-ID'),
|
||||
response: processedResponse
|
||||
});
|
||||
}
|
||||
|
||||
return processedResponse;
|
||||
} catch (error) {
|
||||
console.error('WASM processing error:', error);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
async function queueRequest(request) {
|
||||
const serializedRequest = await serializeRequest(request);
|
||||
requestQueue.set(request.url, serializedRequest);
|
||||
|
||||
try {
|
||||
await self.registration.sync.register('wasm-sync');
|
||||
} catch (error) {
|
||||
console.error('Sync registration failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function serializeRequest(request) {
|
||||
const headers = {};
|
||||
for (const [key, value] of request.headers.entries()) {
|
||||
headers[key] = value;
|
||||
}
|
||||
|
||||
return {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
headers,
|
||||
body: await request.text(),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
// Handle background sync
|
||||
self.addEventListener('sync', (event) => {
|
||||
if (event.tag === 'wasm-sync') {
|
||||
event.waitUntil(processSyncQueue());
|
||||
}
|
||||
});
|
||||
|
||||
async function processSyncQueue() {
|
||||
const requests = Array.from(requestQueue.values());
|
||||
|
||||
for (const serializedRequest of requests) {
|
||||
try {
|
||||
const response = await fetch(new Request(serializedRequest.url, {
|
||||
method: serializedRequest.method,
|
||||
headers: serializedRequest.headers,
|
||||
body: serializedRequest.body
|
||||
}));
|
||||
|
||||
if (response.ok) {
|
||||
requestQueue.delete(serializedRequest.url);
|
||||
|
||||
if (wasmPort) {
|
||||
wasmPort.postMessage({
|
||||
type: 'SYNC_COMPLETE',
|
||||
url: serializedRequest.url
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Sync failed for request:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle payment requests
|
||||
self.addEventListener("canmakepayment", function (e) {
|
||||
e.respondWith(Promise.resolve(true));
|
||||
});
|
||||
|
||||
// Handle periodic sync if available
|
||||
self.addEventListener('periodicsync', (event) => {
|
||||
if (event.tag === 'wasm-sync') {
|
||||
event.waitUntil(processSyncQueue());
|
||||
}
|
||||
});`,
|
||||
cfg.CacheVersion,
|
||||
cfg.CacheVersion,
|
||||
cfg.CacheVersion,
|
||||
cfg.WasmExecPath,
|
||||
cfg.HttpserverPath,
|
||||
cfg.WasmPath,
|
||||
cfg.WasmPath,
|
||||
cfg.WasmExecPath,
|
||||
)
|
||||
}
|
||||
|
||||
// ServiceWorkerHandler is an Echo handler that serves the service worker
|
||||
func ServiceWorkerHandler(cfg *dwn.Environment) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
// Set appropriate headers for service worker
|
||||
c.Response().Header().Set("Content-Type", "application/javascript")
|
||||
c.Response().Header().Set("Service-Worker-Allowed", "/")
|
||||
|
||||
// Generate and write the service worker JavaScript
|
||||
return c.String(http.StatusOK, generateRawServiceWorkerJS(cfg))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Code generated from Pkl module `common.types.DWN`. DO NOT EDIT.
|
||||
package dwn
|
||||
|
||||
import "github.com/apple/pkl-go/pkl"
|
||||
|
||||
func init() {
|
||||
pkl.RegisterMapping("common.types.DWN", DWN{})
|
||||
pkl.RegisterMapping("common.types.DWN#Config", Config{})
|
||||
pkl.RegisterMapping("common.types.DWN#Schema", Schema{})
|
||||
pkl.RegisterMapping("common.types.DWN#Environment", Environment{})
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/onsonr/sonr/pkg/common/middleware/session"
|
||||
"github.com/onsonr/sonr/pkg/core/dwn"
|
||||
|
||||
"github.com/onsonr/sonr/pkg/core/dwn/bridge"
|
||||
"github.com/onsonr/sonr/pkg/core/dwn/handlers"
|
||||
)
|
||||
|
||||
// Server is the interface that wraps the Serve function.
|
||||
type Server interface {
|
||||
Serve() func()
|
||||
}
|
||||
|
||||
type MotrServer struct {
|
||||
e *echo.Echo
|
||||
|
||||
WasmPath string
|
||||
WasmExecPath string
|
||||
HTTPServerPath string
|
||||
CacheVersion string
|
||||
IsDev bool
|
||||
}
|
||||
|
||||
func New(env *dwn.Environment, config *dwn.Config) Server {
|
||||
s := &MotrServer{e: echo.New()}
|
||||
|
||||
s.e.Use(session.MotrMiddleware(config))
|
||||
s.e.Use(bridge.WasmContextMiddleware)
|
||||
|
||||
// Add WASM-specific routes
|
||||
registerAPI(s.e)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *MotrServer) Serve() func() {
|
||||
return bridge.ServeFetch(s.e)
|
||||
}
|
||||
|
||||
// registerAPI registers the Decentralized Web Node API routes.
|
||||
func registerAPI(e *echo.Echo) {
|
||||
g1 := e.Group("api")
|
||||
g1.GET("/register/:subject/start", handlers.RegisterSubjectStart)
|
||||
g1.POST("/register/:subject/check", handlers.RegisterSubjectCheck)
|
||||
g1.POST("/register/:subject/finish", handlers.RegisterSubjectFinish)
|
||||
|
||||
g1.GET("/login/:subject/start", handlers.LoginSubjectStart)
|
||||
g1.POST("/login/:subject/check", handlers.LoginSubjectCheck)
|
||||
g1.POST("/login/:subject/finish", handlers.LoginSubjectFinish)
|
||||
|
||||
g1.GET("/:origin/grant/jwks", handlers.GetJWKS)
|
||||
g1.GET("/:origin/grant/token", handlers.GetToken)
|
||||
g1.POST("/:origin/grant/:subject", handlers.GrantAuthorization)
|
||||
}
|
||||
Reference in New Issue
Block a user