feature/driver indexed db (#14)

* fix: update commitizen version

* feat: add WASM build tags to db actions

* feat: Update all actions to follow `AddAsset` for error handling

* feat: remove database dependency in dwn and motr commands

* feat: add basic info form to registration view

* feat: implement basic browser navigation component

* refactor: move database related files to middleware

* fix: remove unused test command

* fix: update source directory for buf-publish workflow

* feat: embed dwn config data

* feat: add Sync RPC to query service

* refactor: rename  package to  for better organization

* feat: add new javascript exception handling for server requests

* refactor: move dwn.wasm to embed directory

* refactor: move server files to a new directory

* refactor: move session related code to client package

* refactor: Update dwn.wasm build path

* refactor: move dwn wasm build to vfs

* feat: introduce config loading middleware

* feat: introduce DWN config and address JSON

* refactor: move dwn wasm build output to embed directory

* feat: introduce  config and IndexedDB model

* refactor: move DWN config file generation to vfs

* refactor: move config package to

* feat: add Sonr.ID IPFS gateway proxy

* feat: add SWT data structure

* feat: update index.html to use Sonr styles and scripts

* feat(dwn): remove index.html server endpoint

* feat: add Navigator API for web credential management
This commit is contained in:
Prad Nukala
2024-09-18 02:22:17 -04:00
committed by GitHub
parent 8022428e37
commit 2c1cf56e3c
150 changed files with 2819 additions and 6661 deletions
+13 -6
View File
@@ -1,11 +1,12 @@
//go:build js && wasm
// +build js,wasm
package mdw
package middleware
import (
"github.com/donseba/go-htmx"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/internal/dwn/middleware/jsexc"
)
type Browser struct {
@@ -20,9 +21,15 @@ type Browser struct {
htmx *htmx.HTMX
// WebAPIs
credentials CredentialsAPI
indexedDB IndexedDBAPI
localStorage LocalStorageAPI
push PushAPI
sessionStorage SessionStorageAPI
indexedDB jsexc.IndexedDBAPI
localStorage jsexc.LocalStorageAPI
push jsexc.PushAPI
sessionStorage jsexc.SessionStorageAPI
}
func UseNavigator(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cc := jsexc.NewNavigator(c)
return next(cc)
}
}
-3
View File
@@ -1,3 +0,0 @@
package mdw
type AuthClient struct{}
@@ -1,4 +1,4 @@
package mdw
package client
type RequestHeaders struct {
Authorization *string `header:"Authorization"`
+51
View File
@@ -0,0 +1,51 @@
package client
import (
"net/http"
"time"
"github.com/donseba/go-htmx"
"github.com/labstack/echo/v4"
"github.com/segmentio/ksuid"
)
type Session struct {
echo.Context
htmx *htmx.HTMX
}
func (c *Session) Htmx() *htmx.HTMX {
return c.htmx
}
func (c *Session) ID() string {
return ReadCookie(c, "session")
}
func NewSession(c echo.Context) *Session {
s := &Session{Context: c}
if val := ReadCookie(c, "session"); val == "" {
id := ksuid.New().String()
WriteCookie(c, "session", id)
}
return s
}
func ReadCookie(c echo.Context, key string) string {
cookie, err := c.Cookie(key)
if err != nil {
return ""
}
if cookie == nil {
return ""
}
return cookie.Value
}
func WriteCookie(c echo.Context, key string, value string) {
cookie := new(http.Cookie)
cookie.Name = key
cookie.Value = value
cookie.Expires = time.Now().Add(24 * time.Hour)
c.SetCookie(cookie)
}
+51
View File
@@ -0,0 +1,51 @@
package client
import (
"fmt"
"time"
)
const (
OriginMacroonCaveat MacroonCaveat = "origin"
ScopesMacroonCaveat MacroonCaveat = "scopes"
SubjectMacroonCaveat MacroonCaveat = "subject"
ExpMacroonCaveat MacroonCaveat = "exp"
TokenMacroonCaveat MacroonCaveat = "token"
)
type MacroonCaveat string
func (c MacroonCaveat) Equal(other string) bool {
return string(c) == other
}
func (c MacroonCaveat) String() string {
return string(c)
}
func (c MacroonCaveat) Verify(value string) error {
switch c {
case OriginMacroonCaveat:
return nil
case ScopesMacroonCaveat:
return nil
case SubjectMacroonCaveat:
return nil
case ExpMacroonCaveat:
// Check if the expiration time is still valid
exp, err := time.Parse(time.RFC3339, value)
if err != nil {
return err
}
if time.Now().After(exp) {
return fmt.Errorf("expired")
}
return nil
case TokenMacroonCaveat:
return nil
default:
return fmt.Errorf("unknown caveat: %s", c)
}
}
var MacroonCaveats = []MacroonCaveat{OriginMacroonCaveat, ScopesMacroonCaveat, SubjectMacroonCaveat, ExpMacroonCaveat, TokenMacroonCaveat}
-6
View File
@@ -1,6 +0,0 @@
//go:build js && wasm
// +build js,wasm
package mdw
type CredentialsAPI interface{}
@@ -0,0 +1,72 @@
//go:build js && wasm
// +build js,wasm
package jsexc
import (
"errors"
"syscall/js"
"github.com/labstack/echo/v4"
)
type Navigator struct {
echo.Context
navigator js.Value
hasCredentials bool
}
func NewNavigator(c echo.Context) *Navigator {
navigator := js.Global().Get("navigator")
credentials := navigator.Get("credentials")
hasCredentials := !credentials.IsUndefined()
return &Navigator{
Context: c,
navigator: navigator,
hasCredentials: hasCredentials,
}
}
func (c *Navigator) CreateCredential(options js.Value) (js.Value, error) {
if !c.hasCredentials {
return js.Null(), errors.New("navigator.credentials is undefined")
}
promise := c.navigator.Get("credentials").Call("create", map[string]interface{}{"publicKey": options})
result, err := awaitPromise(promise)
return result, err
}
func (c *Navigator) GetCredential(options js.Value) (js.Value, error) {
if !c.hasCredentials {
return js.Null(), errors.New("navigator.credentials is undefined")
}
promise := c.navigator.Get("credentials").Call("get", map[string]interface{}{"publicKey": options})
result, err := awaitPromise(promise)
return result, err
}
func awaitPromise(promise js.Value) (js.Value, error) {
done := make(chan struct{})
var result js.Value
var err error
thenFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
result = args[0]
close(done)
return nil
})
catchFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
err = errors.New(args[0].String())
close(done)
return nil
})
defer thenFunc.Release()
defer catchFunc.Release()
promise.Call("then", thenFunc).Call("catch", catchFunc)
<-done
return result, err
}
@@ -1,7 +1,7 @@
//go:build js && wasm
// +build js,wasm
package mdw
package jsexc
import (
"context"
@@ -1,6 +1,6 @@
//go:build js && wasm
// +build js,wasm
package mdw
package jsexc
type PushAPI interface{}
+37
View File
@@ -0,0 +1,37 @@
//go:build js && wasm
package jsexc
import (
"bytes"
"net/http"
"net/http/httptest"
"syscall/js"
promise "github.com/nlepage/go-js-promise"
)
// Request builds and returns the equivalent http.Request
func Request(r js.Value) *http.Request {
jsBody := js.Global().Get("Uint8Array").New(promise.Await(r.Call("arrayBuffer")))
body := make([]byte, jsBody.Get("length").Int())
js.CopyBytesToGo(body, jsBody)
req := httptest.NewRequest(
r.Get("method").String(),
r.Get("url").String(),
bytes.NewBuffer(body),
)
headersIt := r.Get("headers").Call("entries")
for {
e := headersIt.Call("next")
if e.Get("done").Bool() {
break
}
v := e.Get("value")
req.Header.Set(v.Index(0).String(), v.Index(1).String())
}
return req
}
+50
View File
@@ -0,0 +1,50 @@
//go:build js && wasm
package jsexc
import (
"io"
"net/http/httptest"
"syscall/js"
)
// ResponseRecorder uses httptest.ResponseRecorder to build a JS Response
type ResponseRecorder struct {
*httptest.ResponseRecorder
}
// NewResponseRecorder returns a new ResponseRecorder
func NewResponseRecorder() ResponseRecorder {
return ResponseRecorder{httptest.NewRecorder()}
}
// JSResponse builds and returns the equivalent JS Response
func (rr ResponseRecorder) JSResponse() js.Value {
res := rr.Result()
body := js.Undefined()
if res.ContentLength != 0 {
b, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
body = js.Global().Get("Uint8Array").New(len(b))
js.CopyBytesToJS(body, b)
}
init := make(map[string]interface{}, 2)
if res.StatusCode != 0 {
init["status"] = res.StatusCode
}
if len(res.Header) != 0 {
headers := make(map[string]interface{}, len(res.Header))
for k := range res.Header {
headers[k] = res.Header.Get(k)
}
init["headers"] = headers
}
return js.Global().Get("Response").New(body, init)
}
+59
View File
@@ -0,0 +1,59 @@
//go:build js && wasm
package jsexc
import (
"fmt"
"net/http"
"strings"
"syscall/js"
promise "github.com/nlepage/go-js-promise"
)
// Serve serves HTTP requests using handler or http.DefaultServeMux if handler is nil.
func Serve(handler http.Handler) func() {
h := handler
if h == nil {
h = http.DefaultServeMux
}
prefix := js.Global().Get("wasmhttp").Get("path").String()
for strings.HasSuffix(prefix, "/") {
prefix = strings.TrimSuffix(prefix, "/")
}
if prefix != "" {
mux := http.NewServeMux()
mux.Handle(prefix+"/", http.StripPrefix(prefix, h))
h = mux
}
cb := js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
resPromise, resolve, reject := promise.New()
go func() {
defer func() {
if r := recover(); r != nil {
if err, ok := r.(error); ok {
reject(fmt.Sprintf("wasmhttp: panic: %+v\n", err))
} else {
reject(fmt.Sprintf("wasmhttp: panic: %v\n", r))
}
}
}()
res := NewResponseRecorder()
h.ServeHTTP(res, Request(args[0]))
resolve(res.JSResponse())
}()
return resPromise
})
js.Global().Get("wasmhttp").Call("setHandler", cb)
return cb.Release
}
+12
View File
@@ -0,0 +1,12 @@
//go:build js && wasm
// +build js,wasm
package jsexc
type LocalStorageAPI interface {
Get(key string) string
Set(key string, value string)
Remove(key string)
}
type SessionStorageAPI interface{}
@@ -1,59 +1,28 @@
package mdw
package middleware
import (
"fmt"
"net/http"
"time"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/internal/dwn/middleware/client"
"gopkg.in/macaroon.v2"
)
const (
OriginMacroonCaveat MacroonCaveat = "origin"
ScopesMacroonCaveat MacroonCaveat = "scopes"
SubjectMacroonCaveat MacroonCaveat = "subject"
ExpMacroonCaveat MacroonCaveat = "exp"
TokenMacroonCaveat MacroonCaveat = "token"
)
type MacroonCaveat string
func (c MacroonCaveat) Equal(other string) bool {
return string(c) == other
// GetSession returns the current Session
func GetSession(c echo.Context) *client.Session {
return c.(*client.Session)
}
func (c MacroonCaveat) String() string {
return string(c)
}
func (c MacroonCaveat) Verify(value string) error {
switch c {
case OriginMacroonCaveat:
return nil
case ScopesMacroonCaveat:
return nil
case SubjectMacroonCaveat:
return nil
case ExpMacroonCaveat:
// Check if the expiration time is still valid
exp, err := time.Parse(time.RFC3339, value)
if err != nil {
return err
}
if time.Now().After(exp) {
return fmt.Errorf("expired")
}
return nil
case TokenMacroonCaveat:
return nil
default:
return fmt.Errorf("unknown caveat: %s", c)
// UseSession establishes a Session Cookie.
func UseSession(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
sc := client.NewSession(c)
headers := new(client.RequestHeaders)
sc.Bind(headers)
return next(sc)
}
}
var MacroonCaveats = []MacroonCaveat{OriginMacroonCaveat, ScopesMacroonCaveat, SubjectMacroonCaveat, ExpMacroonCaveat, TokenMacroonCaveat}
func MacaroonMiddleware(secretKeyStr string, location string) echo.MiddlewareFunc {
secretKey := []byte(secretKeyStr)
return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -77,7 +46,7 @@ func MacaroonMiddleware(secretKeyStr string, location string) echo.MiddlewareFun
// Verify the macaroon
err = token.Verify(secretKey, func(caveat string) error {
for _, c := range MacroonCaveats {
for _, c := range client.MacroonCaveats {
if c.String() == caveat {
return nil
}
-66
View File
@@ -1,66 +0,0 @@
package mdw
import (
"net/http"
"time"
"github.com/donseba/go-htmx"
"github.com/labstack/echo/v4"
"github.com/segmentio/ksuid"
)
type Session struct {
echo.Context
htmx *htmx.HTMX
}
// GetSession returns the current Session
func GetSession(c echo.Context) *Session {
return c.(*Session)
}
// UseSession establishes a Session Cookie.
func UseSession(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
sc := initSession(c)
headers := new(RequestHeaders)
sc.Bind(headers)
return next(sc)
}
}
func (c *Session) Htmx() *htmx.HTMX {
return c.htmx
}
func (c *Session) ID() string {
return readCookie(c, "session")
}
func initSession(c echo.Context) *Session {
s := &Session{Context: c}
if val := readCookie(c, "session"); val == "" {
id := ksuid.New().String()
writeCookie(c, "session", id)
}
return s
}
func readCookie(c echo.Context, key string) string {
cookie, err := c.Cookie(key)
if err != nil {
return ""
}
if cookie == nil {
return ""
}
return cookie.Value
}
func writeCookie(c echo.Context, key string, value string) {
cookie := new(http.Cookie)
cookie.Name = key
cookie.Value = value
cookie.Expires = time.Now().Add(24 * time.Hour)
c.SetCookie(cookie)
}
-8
View File
@@ -1,8 +0,0 @@
//go:build js && wasm
// +build js,wasm
package mdw
type LocalStorageAPI interface{}
type SessionStorageAPI interface{}