mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
* feat: integrate Stories widget with backend endpoint
Add stories endpoint plumbing for HTTP and Wails, wire the Home Stories card
to fetch from /api/alby/stories, and keep it first in the right column.
Made-with: Cursor
* feat(home): story modal CTAs and preview fallback
- Add contextual actions in the story dialog (update hub with version,
open Alby Go in-app, install extension) keyed by kind or title
- Use preview stories when the stories API request fails
- Pass hub version from useInfo into the update link
Made-with: Cursor
* feat(stories): polish modal, drop preview fallback
- Widen modal and put video edge-to-edge with overlay close button
- Drop verbose header and 'Watch on YouTube' button
- Remove previewStories fallback so widget hides until upstream API ships
- Tighten title line-height
* feat(stories): render cta from API instead of mapping by kind
Move CTA copy and URLs into the API response. Hub renders story.cta
directly, so adding new story types no longer requires a hub release.
* chore(csp): allow cdn.getalby-assets.com in img-src
* feat(stories): bump avatar size and add ring gap
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): post-review cleanups
- Use react-router Link for in-tab CTA instead of plain <a>.
- Drop redundant www.youtube.com from frame-src (embeds always go through nocookie).
- Tighten stories endpoint status check from >= 300 to >= 400.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): address CodeRabbit feedback
- Switch StoriesWidget to useSWR + swrFetcher (project convention).
- Guard story iframe with isYouTubeUrl so non-YouTube urls never embed.
- Wrap GetStories errors with fmt.Errorf("...: %w", err).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): drop isYouTubeUrl guard
Stories are curated and always YouTube; the runtime check was
redundant. CSP frame-src still constrains the iframe source.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): drop getYouTubeEmbedUrl, embed videoUrl as-is
The Alby API now sends canonical youtube-nocookie embed URLs with
autoplay/rel query strings (getAlby/getalby.com#2568), so the
runtime normalization is no longer needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): use w-16 instead of arbitrary w-[73px]
Match the avatar's size token; no magic numbers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): take videoId from API and assemble embed url locally
Pairs with getAlby/getalby.com#2568. The API now sends just the
YouTube videoId; the hub composes the canonical embed URL so the
domain/query-string format stays in one place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): treat 3xx as non-success, matching file convention
The other status checks in alby_oauth_service.go all use >= 300;
align GetStories so redirects don't slip through.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): move viewed-storage key to constants, widen story button
Address review feedback:
- Centralize the localStorage key for viewed stories in localStorageKeys
alongside the other keys.
- Widen the story button from w-16 to w-20 so "Alby Extension" fits on
one line and matches the other titles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): bump story button to w-24 so titles fit one line
w-20 still wrapped "Alby Extension"; w-24 fits all current titles
without truncation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(stories): bump story button to w-24 so titles fit one line"
This reverts commit 0b47438f50.
* chore(stories): split title words onto separate lines
Reserve two lines for every story title so avatars align regardless of
title length.
* chore(stories): align homeStoriesViewed key with sibling pattern
* chore(stories): fit titles on one line
* chore(stories): widen story button to w-21 for one-line titles
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
204 lines
7.2 KiB
Go
204 lines
7.2 KiB
Go
package alby
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/getAlby/hub/events"
|
|
"github.com/getAlby/hub/lnclient"
|
|
)
|
|
|
|
type AlbyService interface {
|
|
GetInfo(ctx context.Context) (*AlbyInfo, error)
|
|
GetBitcoinRate(ctx context.Context, currency string) (*BitcoinRate, error)
|
|
GetCurrencies(ctx context.Context) ([]Currency, error)
|
|
GetChannelPeerSuggestions(ctx context.Context) ([]ChannelPeerSuggestion, error)
|
|
}
|
|
|
|
type AlbyOAuthService interface {
|
|
events.EventSubscriber
|
|
GetLSPChannelOffer(ctx context.Context) (*LSPChannelOffer, error)
|
|
GetLSPInfo(ctx context.Context, lsp, network string) (*LSPInfo, error)
|
|
CreateLSPOrder(ctx context.Context, lsp, network string, lspChannelRequest *LSPChannelRequest) (*LSPChannelResponse, error)
|
|
GetAuthUrl() string
|
|
GetUserIdentifier() (string, error)
|
|
GetLightningAddress() (string, error)
|
|
IsConnected(ctx context.Context) bool
|
|
LinkAccount(ctx context.Context, lnClient lnclient.LNClient, budgetSat uint64, renewal string) error
|
|
CallbackHandler(ctx context.Context, code string) error
|
|
GetMe(ctx context.Context) (*AlbyMe, error)
|
|
UnlinkAccount(ctx context.Context) error
|
|
RequestAutoChannel(ctx context.Context, lnClient lnclient.LNClient, isPublic bool) (*AutoChannelResponse, error)
|
|
GetVssAuthToken(ctx context.Context, nodeIdentifier string) (string, error)
|
|
RemoveOAuthAccessToken() error
|
|
CreateLightningAddress(ctx context.Context, address string, appId uint) (*CreateLightningAddressResponse, error)
|
|
DeleteLightningAddress(ctx context.Context, address string) error
|
|
GetStories(ctx context.Context) ([]Story, error)
|
|
}
|
|
|
|
type CreateLightningAddressResponse struct {
|
|
Address string `json:"address"`
|
|
FullAddress string `json:"full_address"`
|
|
}
|
|
|
|
type AlbyLinkAccountRequest struct {
|
|
Budget uint64 `json:"budget"`
|
|
Renewal string `json:"renewal"`
|
|
}
|
|
|
|
type AutoChannelRequest struct {
|
|
IsPublic bool `json:"isPublic"`
|
|
}
|
|
|
|
type AutoChannelResponse struct {
|
|
Invoice string `json:"invoice"`
|
|
ChannelSize uint64 `json:"channelSize"` // deprecated
|
|
ChannelSizeSat uint64 `json:"channelSizeSat"`
|
|
Fee uint64 `json:"fee"` // deprecated
|
|
FeeSat uint64 `json:"feeSat"`
|
|
}
|
|
|
|
type AlbyInfoHub struct {
|
|
LatestVersion string `json:"latestVersion"`
|
|
LatestReleaseNotes string `json:"latestReleaseNotes"`
|
|
}
|
|
|
|
type AlbyInfoIncident struct {
|
|
Name string `json:"name"`
|
|
Started string `json:"started"`
|
|
Status string `json:"status"`
|
|
Impact string `json:"impact"`
|
|
Url string `json:"url"`
|
|
}
|
|
|
|
type AlbyInfo struct {
|
|
Hub AlbyInfoHub `json:"hub"`
|
|
Status string `json:"status"`
|
|
Healthy bool `json:"healthy"`
|
|
AccountAvailable bool `json:"accountAvailable"` // false if country is blocked (can still use Alby Hub without an Alby Account)
|
|
Incidents []AlbyInfoIncident `json:"incidents"`
|
|
}
|
|
|
|
type AlbyMeHub struct {
|
|
Name string `json:"name"`
|
|
Config map[string]interface{} `json:"config"`
|
|
}
|
|
|
|
type AlbyMeSubscription struct {
|
|
PlanCode string `json:"plan_code"`
|
|
}
|
|
|
|
type AlbyMe struct {
|
|
Identifier string `json:"identifier"`
|
|
NPub string `json:"nostr_pubkey"`
|
|
LightningAddress string `json:"lightning_address"`
|
|
Email string `json:"email"`
|
|
Name string `json:"name"`
|
|
Avatar string `json:"avatar"`
|
|
KeysendPubkey string `json:"keysend_pubkey"`
|
|
SharedNode bool `json:"shared_node"`
|
|
Hub AlbyMeHub `json:"hub"`
|
|
Subscription AlbyMeSubscription `json:"subscription"`
|
|
}
|
|
|
|
type ChannelPeerSuggestion struct {
|
|
Network string `json:"network"`
|
|
PaymentMethod string `json:"paymentMethod"`
|
|
Pubkey string `json:"pubkey"`
|
|
Host string `json:"host"`
|
|
MinimumChannelSize uint64 `json:"minimumChannelSize"` // deprecated
|
|
MinimumChannelSizeSat uint64 `json:"minimumChannelSizeSat"`
|
|
MaximumChannelSize uint64 `json:"maximumChannelSize"` // deprecated
|
|
MaximumChannelSizeSat uint64 `json:"maximumChannelSizeSat"`
|
|
MaximumChannelExpiryBlocks *uint32 `json:"maximumChannelExpiryBlocks"`
|
|
Name string `json:"name"`
|
|
Image string `json:"image"`
|
|
Identifier string `json:"identifier"`
|
|
ContactUrl string `json:"contactUrl"`
|
|
Type string `json:"type"`
|
|
Terms string `json:"terms"`
|
|
Description string `json:"description"`
|
|
Note string `json:"note"`
|
|
PublicChannelsAllowed bool `json:"publicChannelsAllowed"`
|
|
FeeTotalSat1m *uint32 `json:"feeTotalSat1m"`
|
|
FeeTotalSat2m *uint32 `json:"feeTotalSat2m"`
|
|
FeeTotalSat3m *uint32 `json:"feeTotalSat3m"`
|
|
}
|
|
|
|
type LSPChannelOffer struct {
|
|
LspName string `json:"lspName"`
|
|
LspContactUrl string `json:"lspContactUrl"`
|
|
LspBalanceSat uint64 `json:"lspBalanceSat"`
|
|
FeeTotalSat uint64 `json:"feeTotalSat"`
|
|
FeeTotalUsd uint64 `json:"feeTotalUsd"` // in cents
|
|
CurrentPaymentMethod string `json:"currentPaymentMethod"`
|
|
Terms string `json:"terms"`
|
|
LspDescription string `json:"lspDescription"`
|
|
}
|
|
|
|
type Currency struct {
|
|
IsoCode string `json:"iso_code"`
|
|
Symbol string `json:"symbol"`
|
|
Name string `json:"name"`
|
|
Priority int `json:"priority"`
|
|
}
|
|
|
|
type BitcoinRate struct {
|
|
Code string `json:"code"`
|
|
Symbol string `json:"symbol"`
|
|
Rate string `json:"rate"`
|
|
RateFloat float64 `json:"rate_float"`
|
|
RateCents int64 `json:"rate_cents"`
|
|
}
|
|
|
|
type ErrorResponse struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type StoryCta struct {
|
|
Label string `json:"label"`
|
|
URL string `json:"url"`
|
|
OpenInNewTab bool `json:"openInNewTab"`
|
|
}
|
|
|
|
type Story struct {
|
|
ID int `json:"id"`
|
|
Title string `json:"title"`
|
|
Avatar string `json:"avatar"`
|
|
VideoID string `json:"videoId,omitempty"`
|
|
Cta *StoryCta `json:"cta,omitempty"`
|
|
}
|
|
|
|
type LSPChannelPaymentBolt11 struct {
|
|
Invoice string `json:"invoice"`
|
|
FeeTotalSat string `json:"fee_total_sat"`
|
|
}
|
|
|
|
type LSPChannelPayment struct {
|
|
Bolt11 LSPChannelPaymentBolt11 `json:"bolt11"`
|
|
// TODO: add onchain
|
|
}
|
|
|
|
type LSPChannelResponse struct {
|
|
Payment *LSPChannelPayment `json:"payment"`
|
|
}
|
|
|
|
type LSPChannelRequest struct {
|
|
PublicKey string `json:"public_key"`
|
|
LSPBalanceSat string `json:"lsp_balance_sat"`
|
|
ClientBalanceSat string `json:"client_balance_sat"`
|
|
RequiredChannelConfirmations uint64 `json:"required_channel_confirmations"`
|
|
FundingConfirmsWithinBlocks uint64 `json:"funding_confirms_within_blocks"`
|
|
ChannelExpiryBlocks uint64 `json:"channel_expiry_blocks"`
|
|
Token string `json:"token"`
|
|
RefundOnchainAddress string `json:"refund_onchain_address"`
|
|
AnnounceChannel bool `json:"announce_channel"`
|
|
}
|
|
|
|
type LSPInfo struct {
|
|
Pubkey string
|
|
Address string
|
|
Port uint16
|
|
MaxChannelExpiryBlocks uint64
|
|
MinRequiredChannelConfirmations uint64
|
|
MinFundingConfirmsWithinBlocks uint64
|
|
}
|