//go:build js && wasm package wasmdb import ( "bytes" "encoding/binary" "errors" "time" "github.com/aperturerobotics/go-indexeddb/idb" "github.com/hack-pad/safejs" "next.orly.dev/pkg/database" ) const ( // NIP43StoreName is the object store for NIP-43 membership NIP43StoreName = "nip43" // InvitesStoreName is the object store for invite codes InvitesStoreName = "invites" ) // AddNIP43Member adds a pubkey as a NIP-43 member with the given invite code func (w *W) AddNIP43Member(pubkey []byte, inviteCode string) error { if len(pubkey) != 32 { return errors.New("invalid pubkey length") } // Create membership record membership := &database.NIP43Membership{ Pubkey: make([]byte, 32), InviteCode: inviteCode, AddedAt: time.Now(), } copy(membership.Pubkey, pubkey) // Serialize membership data := w.serializeNIP43Membership(membership) // Store using pubkey as key return w.setStoreValue(NIP43StoreName, string(pubkey), data) } // RemoveNIP43Member removes a pubkey from NIP-43 membership func (w *W) RemoveNIP43Member(pubkey []byte) error { return w.deleteStoreValue(NIP43StoreName, string(pubkey)) } // IsNIP43Member checks if a pubkey is a NIP-43 member func (w *W) IsNIP43Member(pubkey []byte) (isMember bool, err error) { data, err := w.getStoreValue(NIP43StoreName, string(pubkey)) if err != nil { return false, nil // Not found is not an error, just not a member } return data != nil, nil } // GetNIP43Membership returns the full membership details for a pubkey func (w *W) GetNIP43Membership(pubkey []byte) (*database.NIP43Membership, error) { data, err := w.getStoreValue(NIP43StoreName, string(pubkey)) if err != nil { return nil, err } if data == nil { return nil, errors.New("membership not found") } return w.deserializeNIP43Membership(data) } // GetAllNIP43Members returns all NIP-43 member pubkeys func (w *W) GetAllNIP43Members() ([][]byte, error) { tx, err := w.db.Transaction(idb.TransactionReadOnly, NIP43StoreName) if err != nil { return nil, err } store, err := tx.ObjectStore(NIP43StoreName) if err != nil { return nil, err } var members [][]byte cursorReq, err := store.OpenCursor(idb.CursorNext) if err != nil { return nil, err } err = cursorReq.Iter(w.ctx, func(cursor *idb.CursorWithValue) error { keyVal, keyErr := cursor.Key() if keyErr != nil { return keyErr } // Key is the pubkey stored as string keyBytes := safeValueToBytes(keyVal) if len(keyBytes) == 32 { pubkey := make([]byte, 32) copy(pubkey, keyBytes) members = append(members, pubkey) } return cursor.Continue() }) if err != nil && err.Error() != "found" { return nil, err } return members, nil } // StoreInviteCode stores an invite code with expiration time func (w *W) StoreInviteCode(code string, expiresAt time.Time) error { // Serialize expiration time as unix timestamp data := make([]byte, 8) binary.BigEndian.PutUint64(data, uint64(expiresAt.Unix())) return w.setStoreValue(InvitesStoreName, code, data) } // ValidateInviteCode checks if an invite code is valid (exists and not expired) func (w *W) ValidateInviteCode(code string) (valid bool, err error) { data, err := w.getStoreValue(InvitesStoreName, code) if err != nil { return false, nil } if data == nil || len(data) < 8 { return false, nil } // Check expiration expiresAt := time.Unix(int64(binary.BigEndian.Uint64(data)), 0) if time.Now().After(expiresAt) { return false, nil } return true, nil } // DeleteInviteCode removes an invite code func (w *W) DeleteInviteCode(code string) error { return w.deleteStoreValue(InvitesStoreName, code) } // PublishNIP43MembershipEvent is a no-op in WASM (events are handled by the relay) func (w *W) PublishNIP43MembershipEvent(kind int, pubkey []byte) error { // In WASM context, this would typically be handled by the client // This is a no-op implementation return nil } // serializeNIP43Membership converts a membership to bytes for storage func (w *W) serializeNIP43Membership(m *database.NIP43Membership) []byte { buf := new(bytes.Buffer) // Write pubkey (32 bytes) buf.Write(m.Pubkey) // Write AddedAt as unix timestamp (8 bytes) ts := make([]byte, 8) binary.BigEndian.PutUint64(ts, uint64(m.AddedAt.Unix())) buf.Write(ts) // Write invite code length (4 bytes) + invite code codeBytes := []byte(m.InviteCode) codeLen := make([]byte, 4) binary.BigEndian.PutUint32(codeLen, uint32(len(codeBytes))) buf.Write(codeLen) buf.Write(codeBytes) return buf.Bytes() } // deserializeNIP43Membership converts bytes back to a membership func (w *W) deserializeNIP43Membership(data []byte) (*database.NIP43Membership, error) { if len(data) < 44 { // 32 + 8 + 4 minimum return nil, errors.New("invalid membership data") } m := &database.NIP43Membership{} // Read pubkey m.Pubkey = make([]byte, 32) copy(m.Pubkey, data[:32]) // Read AddedAt m.AddedAt = time.Unix(int64(binary.BigEndian.Uint64(data[32:40])), 0) // Read invite code codeLen := binary.BigEndian.Uint32(data[40:44]) if len(data) < int(44+codeLen) { return nil, errors.New("invalid invite code length") } m.InviteCode = string(data[44 : 44+codeLen]) return m, nil } // Helper to convert safejs.Value to string for keys func safeValueToString(v safejs.Value) string { if v.IsUndefined() || v.IsNull() { return "" } str, err := v.String() if err != nil { return "" } return str }