feat: implement HTMX for dynamic updates

This commit is contained in:
Prad Nukala 2025-01-06 19:38:17 -05:00
parent ddacc127c6
commit f8974b3d1f
9 changed files with 807 additions and 19 deletions

21
go.mod
View File

@ -1,6 +1,6 @@
module github.com/onsonr/sonr
go 1.23.3
go 1.23.4
// overrides
replace (
@ -67,14 +67,16 @@ require (
github.com/gorilla/mux v1.8.1
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/gtank/merlin v0.1.1
github.com/ipfs/boxo v0.26.0
github.com/ipfs/go-cid v0.4.1
github.com/joho/godotenv v1.5.1
github.com/libp2p/go-libp2p v0.37.2
github.com/libp2p/go-libp2p v0.38.1
github.com/mr-tron/base58 v1.2.0
github.com/multiformats/go-multibase v0.2.0
github.com/multiformats/go-multicodec v0.9.0
github.com/multiformats/go-multihash v0.2.3
github.com/multiformats/go-varint v0.0.7
github.com/onsonr/motr v0.0.0-20250106200953-0f9e40a14ad8
github.com/pkg/errors v0.9.1
github.com/spf13/cast v1.7.1
github.com/spf13/cobra v1.8.1
@ -100,6 +102,7 @@ require (
github.com/99designs/keyring v1.2.1 // indirect
github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/apple/pkl-go v0.9.0 // indirect
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
@ -124,6 +127,7 @@ require (
github.com/cosmos/iavl v1.1.2 // indirect
github.com/cosmos/ics23/go v0.10.0 // indirect
github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect
github.com/crackcomm/go-gitignore v0.0.0-20241020182519-7843d2ba8fdf // indirect
github.com/creachadair/atomicfile v0.3.1 // indirect
github.com/creachadair/tomledit v0.0.24 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
@ -204,7 +208,6 @@ require (
github.com/mtibben/percent v0.2.1 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr v0.14.0 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
@ -231,6 +234,8 @@ require (
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
go.etcd.io/bbolt v1.3.10 // indirect
@ -239,18 +244,16 @@ require (
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/sdk v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
golang.org/x/time v0.8.0 // indirect
google.golang.org/api v0.172.0 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect

32
go.sum
View File

@ -843,6 +843,8 @@ github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0I
github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=
github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg=
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
github.com/apple/pkl-go v0.9.0 h1:aA4Bh+WQ797p8nEnQhHzCahVuQP2HJ40ffSQWlAR5es=
github.com/apple/pkl-go v0.9.0/go.mod h1:5Hwil5tyZGrOekh7JXLZJvIAcGHb4gT19lnv4WEiKeI=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
@ -977,6 +979,8 @@ github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5n
github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/crackcomm/go-gitignore v0.0.0-20241020182519-7843d2ba8fdf h1:dwGgBWn84wUS1pVikGiruW+x5XM4amhjaZO20vCjay4=
github.com/crackcomm/go-gitignore v0.0.0-20241020182519-7843d2ba8fdf/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE=
github.com/creachadair/atomicfile v0.3.1 h1:yQORkHjSYySh/tv5th1dkKcn02NEW5JleB84sjt+W4Q=
github.com/creachadair/atomicfile v0.3.1/go.mod h1:mwfrkRxFKwpNAflYZzytbSwxvbK6fdGRRlp0KEQc0qU=
github.com/creachadair/tomledit v0.0.24 h1:5Xjr25R2esu1rKCbQEmjZYlrhFkDspoAbAKb6QKQDhQ=
@ -1351,6 +1355,8 @@ github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPt
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ipfs/boxo v0.26.0 h1:RRxEon7rJMy8ScVaTLncSZ5/nA6majYhRSbzc80snO8=
github.com/ipfs/boxo v0.26.0/go.mod h1:iHyc9cjoF7/zoiKVY65d2fBWRhoS2zx4cMk8hKgqrac=
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
@ -1406,8 +1412,8 @@ github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-libp2p v0.37.2 h1:Irh+n9aDPTLt9wJYwtlHu6AhMUipbC1cGoJtOiBqI9c=
github.com/libp2p/go-libp2p v0.37.2/go.mod h1:M8CRRywYkqC6xKHdZ45hmqVckBj5z4mRLIMLWReypz8=
github.com/libp2p/go-libp2p v0.38.1 h1:aT1K7IFWi+gZUsQGCzTHBTlKX5QVZQOahng8DnOr6tQ=
github.com/libp2p/go-libp2p v0.38.1/go.mod h1:QWV4zGL3O9nXKdHirIC59DoRcZ446dfkjbOJ55NEWFo=
github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5OfGQ=
github.com/linxGnu/grocksdb v1.8.14/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
@ -1515,6 +1521,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
github.com/onsonr/motr v0.0.0-20250106200953-0f9e40a14ad8 h1:mRyGBI8HbCtvBOy+glT47Tz2Hlz2BYUU/r2MOckgNgg=
github.com/onsonr/motr v0.0.0-20250106200953-0f9e40a14ad8/go.mod h1:lGU/f0rProgg/SzEko5NTD8n2uXjOodewcudSPf2GJQ=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
@ -1676,6 +1684,10 @@ github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -1782,8 +1794,8 @@ golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8H
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -1904,8 +1916,8 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1940,8 +1952,8 @@ golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -2127,8 +2139,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

1
x/dwn/types/attns.go Normal file
View File

@ -0,0 +1 @@
package types

View File

@ -0,0 +1,48 @@
package embed
import (
"encoding/json"
"github.com/ipfs/boxo/files"
motr "github.com/onsonr/motr/pkg/config"
)
const SchemaVersion = 1
const (
AppManifestFileName = "app.webmanifest"
DWNConfigFileName = "dwn.json"
IndexHTMLFileName = "index.html"
MainJSFileName = "main.js"
ServiceWorkerFileName = "sw.js"
)
// spawnVaultDirectory creates a new directory with the default files
func NewVaultFS(cfg *motr.Config) (files.Directory, error) {
manifestBz, err := NewWebManifest()
if err != nil {
return nil, err
}
cnfBz, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
return files.NewMapDirectory(map[string]files.Node{
AppManifestFileName: files.NewBytesFile(manifestBz),
DWNConfigFileName: files.NewBytesFile(cnfBz),
IndexHTMLFileName: files.NewBytesFile(IndexHTML),
MainJSFileName: files.NewBytesFile(MainJS),
ServiceWorkerFileName: files.NewBytesFile(WorkerJS),
}), nil
}
// NewVaultConfig returns the default vault config
func NewVaultConfig(addr string, ucanCID string) *motr.Config {
return &motr.Config{
MotrToken: ucanCID,
MotrAddress: addr,
IpfsGatewayUrl: "http://localhost:80",
SonrApiUrl: "http://localhost:1317",
SonrRpcUrl: "http://localhost:26657",
SonrChainId: "sonr-testnet-1",
}
}

View File

@ -0,0 +1,138 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sonr DWN</title>
<!-- HTMX -->
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<!-- WASM Support -->
<script src="https://cdn.jsdelivr.net/gh/golang/go@go1.22.5/misc/wasm/wasm_exec.js"></script>
<!-- Main JS -->
<script src="main.js"></script>
<!-- Tailwind (assuming you're using it based on your classes) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Add manifest for PWA support -->
<link
rel="manifest"
href="/app.webmanifest"
crossorigin="use-credentials"
/>
<!-- Offline detection styles -->
<style>
.offline-indicator {
display: none;
}
body.offline .offline-indicator {
display: block;
background: #f44336;
color: white;
text-align: center;
padding: 0.5rem;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
}
</style>
</head>
<body
class="flex items-center justify-center h-full bg-zinc-50 lg:p-24 md:16 p-4"
>
<!-- Offline indicator -->
<div class="offline-indicator">
You are currently offline. Some features may be limited.
</div>
<!-- Loading indicator -->
<div
id="loading-indicator"
class="fixed top-0 left-0 w-full h-1 bg-blue-200 transition-all duration-300"
style="display: none"
>
<div class="h-full bg-blue-600 w-0 transition-all duration-300"></div>
</div>
<main
class="flex-row items-center justify-center mx-auto w-fit max-w-screen-sm gap-y-3"
>
<div
id="content"
hx-get="/#"
hx-trigger="load"
hx-swap="outerHTML"
hx-indicator="#loading-indicator"
>
Loading...
</div>
</main>
<!-- WASM Ready Indicator (hidden) -->
<div
id="wasm-status"
class="hidden fixed bottom-4 right-4 p-2 rounded-md bg-green-500 text-white"
hx-swap-oob="true"
>
WASM Ready
</div>
<script>
// Initialize service worker
if ("serviceWorker" in navigator) {
window.addEventListener("load", async function () {
try {
const registration =
await navigator.serviceWorker.register("/sw.js");
console.log(
"Service Worker registered with scope:",
registration.scope,
);
} catch (error) {
console.error("Service Worker registration failed:", error);
}
});
}
// HTMX loading indicator
htmx.on("htmx:beforeRequest", function (evt) {
document.getElementById("loading-indicator").style.display = "block";
});
htmx.on("htmx:afterRequest", function (evt) {
document.getElementById("loading-indicator").style.display = "none";
});
// WASM ready event handler
document.addEventListener("wasm-ready", function () {
const status = document.getElementById("wasm-status");
status.classList.remove("hidden");
setTimeout(() => {
status.classList.add("hidden");
}, 3000);
});
// Offline status handler
window.addEventListener("offline", function () {
document.body.classList.add("offline");
});
window.addEventListener("online", function () {
document.body.classList.remove("offline");
});
// Initial offline check
if (!navigator.onLine) {
document.body.classList.add("offline");
}
</script>
</body>
</html>

158
x/dwn/types/embed/main.js Normal file
View File

@ -0,0 +1,158 @@
// MessageChannel for WASM communication
let wasmChannel;
let wasmPort;
async function initWasmChannel() {
wasmChannel = new MessageChannel();
wasmPort = wasmChannel.port1;
// Setup message handling from WASM
wasmPort.onmessage = (event) => {
const { type, data } = event.data;
switch (type) {
case "WASM_READY":
console.log("WASM is ready");
document.dispatchEvent(new CustomEvent("wasm-ready"));
break;
case "RESPONSE":
handleWasmResponse(data);
break;
case "SYNC_COMPLETE":
handleSyncComplete(data);
break;
}
};
}
// Initialize WebAssembly and Service Worker
async function init() {
try {
// Register service worker
if ("serviceWorker" in navigator) {
const registration = await navigator.serviceWorker.register("./sw.js");
console.log("ServiceWorker registered");
// Wait for the service worker to be ready
await navigator.serviceWorker.ready;
// Initialize MessageChannel
await initWasmChannel();
// Send the MessageChannel port to the service worker
navigator.serviceWorker.controller.postMessage(
{
type: "PORT_INITIALIZATION",
port: wasmChannel.port2,
},
[wasmChannel.port2],
);
// Register for periodic sync if available
if ("periodicSync" in registration) {
try {
await registration.periodicSync.register("wasm-sync", {
minInterval: 24 * 60 * 60 * 1000, // 24 hours
});
} catch (error) {
console.log("Periodic sync could not be registered:", error);
}
}
}
// Initialize HTMX with custom config
htmx.config.withCredentials = true;
htmx.config.wsReconnectDelay = "full-jitter";
// Override HTMX's internal request handling
htmx.config.beforeRequest = function (config) {
// Add request ID for tracking
const requestId = "req_" + Date.now();
config.headers["X-Wasm-Request-ID"] = requestId;
// If offline, handle through service worker
if (!navigator.onLine) {
return false; // Let service worker handle it
}
return true;
};
// Handle HTMX after request
htmx.config.afterRequest = function (config) {
// Additional processing after request if needed
};
// Handle HTMX errors
htmx.config.errorHandler = function (error) {
console.error("HTMX Error:", error);
};
} catch (error) {
console.error("Initialization failed:", error);
}
}
function handleWasmResponse(data) {
const { requestId, response } = data;
// Process the WASM response
// This might update the UI or trigger HTMX swaps
const targetElement = document.querySelector(
`[data-request-id="${requestId}"]`,
);
if (targetElement) {
htmx.process(targetElement);
}
}
function handleSyncComplete(data) {
const { url } = data;
// Handle successful sync
// Maybe refresh the relevant part of the UI
htmx.trigger("body", "sync:complete", { url });
}
// Handle offline status changes
window.addEventListener("online", () => {
document.body.classList.remove("offline");
// Trigger sync when back online
if (wasmPort) {
wasmPort.postMessage({ type: "SYNC_REQUEST" });
}
});
window.addEventListener("offline", () => {
document.body.classList.add("offline");
});
// Custom event handlers for HTMX
document.addEventListener("htmx:beforeRequest", (event) => {
const { elt, xhr } = event.detail;
// Add request tracking
const requestId = xhr.headers["X-Wasm-Request-ID"];
elt.setAttribute("data-request-id", requestId);
});
document.addEventListener("htmx:afterRequest", (event) => {
const { elt, successful } = event.detail;
if (successful) {
elt.removeAttribute("data-request-id");
}
});
// Initialize everything when the page loads
document.addEventListener("DOMContentLoaded", init);
// Export functions that might be needed by WASM
window.wasmBridge = {
triggerUIUpdate: function (selector, content) {
const target = document.querySelector(selector);
if (target) {
htmx.process(
htmx.parse(content).forEach((node) => target.appendChild(node)),
);
}
},
showNotification: function (message, type = "info") {
// Implement notification system
console.log(`${type}: ${message}`);
},
};

257
x/dwn/types/embed/sw.js Normal file
View File

@ -0,0 +1,257 @@
// Cache names for different types of resources
const CACHE_NAMES = {
wasm: "wasm-cache-v1",
static: "static-cache-v1",
dynamic: "dynamic-cache-v1",
};
// Import required scripts
importScripts(
"https://cdn.jsdelivr.net/gh/golang/go@go1.22.5/misc/wasm/wasm_exec.js",
"https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js",
);
// Initialize WASM HTTP listener
const wasmInstance = registerWasmHTTPListener(
"https://cdn.sonr.id/wasm/app.wasm",
);
// 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([
"https://cdn.sonr.id/wasm/app.wasm",
"https://cdn.jsdelivr.net/gh/golang/go@go1.22.5/misc/wasm/wasm_exec.js",
]),
),
]),
);
});
// 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) {
// Clone the response before processing
const responseClone = response.clone();
try {
// Process through WASM
const processedResponse = await wasmInstance.processResponse(responseClone);
// Notify client through message channel
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);
// Register for background sync
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);
// Notify client of successful sync
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());
}
});

View File

@ -0,0 +1,47 @@
package embed
import (
_ "embed"
"reflect"
"strings"
)
//go:embed index.html
var IndexHTML []byte
//go:embed main.js
var MainJS []byte
//go:embed sw.js
var WorkerJS []byte
func getSchema(structType interface{}) string {
t := reflect.TypeOf(structType)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return ""
}
var fields []string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldName := toCamelCase(field.Name)
fields = append(fields, fieldName)
}
// Add "++" at the beginning, separated by a comma
return "++, " + strings.Join(fields, ", ")
}
func toCamelCase(s string) string {
if s == "" {
return s
}
if len(s) == 1 {
return strings.ToLower(s)
}
return strings.ToLower(s[:1]) + s[1:]
}

View File

@ -0,0 +1,124 @@
package embed
import "encoding/json"
func NewWebManifest() ([]byte, error) {
return json.Marshal(baseWebManifest)
}
var baseWebManifest = WebManifest{
Name: "Sonr Vault",
ShortName: "Sonr.ID",
StartURL: "/index.html",
Display: "standalone",
DisplayOverride: []string{
"fullscreen",
"minimal-ui",
},
Icons: []IconDefinition{
{
Src: "/icons/icon-192x192.png",
Sizes: "192x192",
Type: "image/png",
},
},
ServiceWorker: ServiceWorker{
Scope: "/",
Src: "/sw.js",
UseCache: true,
},
ProtocolHandlers: []ProtocolHandler{
{
Scheme: "did.sonr",
URL: "/resolve/sonr/%s",
},
{
Scheme: "did.eth",
URL: "/resolve/eth/%s",
},
{
Scheme: "did.btc",
URL: "/resolve/btc/%s",
},
{
Scheme: "did.usdc",
URL: "/resolve/usdc/%s",
},
{
Scheme: "did.ipfs",
URL: "/resolve/ipfs/%s",
},
},
}
type WebManifest struct {
// Required fields
Name string `json:"name"` // Full name of the application
ShortName string `json:"short_name"` // Short version of the name
// Display and appearance
Description string `json:"description,omitempty"` // Purpose and features of the application
Display string `json:"display,omitempty"` // Preferred display mode: fullscreen, standalone, minimal-ui, browser
DisplayOverride []string `json:"display_override,omitempty"`
ThemeColor string `json:"theme_color,omitempty"` // Default theme color for the application
BackgroundColor string `json:"background_color,omitempty"` // Background color during launch
Orientation string `json:"orientation,omitempty"` // Default orientation: any, natural, landscape, portrait
// URLs and scope
StartURL string `json:"start_url"` // Starting URL when launching
Scope string `json:"scope,omitempty"` // Navigation scope of the web application
ServiceWorker ServiceWorker `json:"service_worker,omitempty"`
// Icons
Icons []IconDefinition `json:"icons,omitempty"`
// Optional features
RelatedApplications []RelatedApplication `json:"related_applications,omitempty"`
PreferRelatedApplications bool `json:"prefer_related_applications,omitempty"`
Shortcuts []Shortcut `json:"shortcuts,omitempty"`
// Experimental features (uncomment if needed)
FileHandlers []FileHandler `json:"file_handlers,omitempty"`
ProtocolHandlers []ProtocolHandler `json:"protocol_handlers,omitempty"`
}
type FileHandler struct {
Action string `json:"action"`
Accept map[string][]string `json:"accept"`
}
type LaunchHandler struct {
Action string `json:"action"`
}
type IconDefinition struct {
Src string `json:"src"`
Sizes string `json:"sizes"`
Type string `json:"type,omitempty"`
Purpose string `json:"purpose,omitempty"`
}
type ProtocolHandler struct {
Scheme string `json:"scheme"`
URL string `json:"url"`
}
type RelatedApplication struct {
Platform string `json:"platform"`
URL string `json:"url,omitempty"`
ID string `json:"id,omitempty"`
}
type Shortcut struct {
Name string `json:"name"`
ShortName string `json:"short_name,omitempty"`
Description string `json:"description,omitempty"`
URL string `json:"url"`
Icons []IconDefinition `json:"icons,omitempty"`
}
type ServiceWorker struct {
Scope string `json:"scope"`
Src string `json:"src"`
UseCache bool `json:"use_cache"`
}