package httpmanifest import ( "context" "encoding/json" "fmt" "io" "net/http" "github.com/Eriyc/rules_wails/pkg/wails3kit/updates" ) type Config struct { ManifestURL string HTTPClient *http.Client PrepareRequest func(*http.Request) error } type Provider struct { config Config client *http.Client } func New(cfg Config) (*Provider, error) { if cfg.ManifestURL == "" { return nil, updates.ErrInvalidConfig } client := cfg.HTTPClient if client == nil { client = http.DefaultClient } return &Provider{config: cfg, client: client}, nil } type manifestDocument struct { SchemaVersion int `json:"schemaVersion"` ProductID string `json:"productID"` Releases []manifestRelease `json:"releases"` } type manifestRelease struct { ID string `json:"id"` Version string `json:"version"` Channel updates.Channel `json:"channel"` PublishedAt string `json:"publishedAt"` NotesMarkdown string `json:"notesMarkdown"` Artifacts []manifestArtifact `json:"artifacts"` } type manifestArtifact struct { OS string `json:"os"` Arch string `json:"arch"` Kind updates.ArtifactKind `json:"kind"` Format updates.ArtifactFormat `json:"format"` URL string `json:"url"` SHA256 string `json:"sha256"` Size int64 `json:"size"` } func (provider *Provider) Resolve(ctx context.Context, req updates.ResolveRequest) (*updates.Release, error) { request, err := http.NewRequestWithContext(ctx, http.MethodGet, provider.config.ManifestURL, nil) if err != nil { return nil, err } if provider.config.PrepareRequest != nil { if err := provider.config.PrepareRequest(request); err != nil { return nil, err } } response, err := provider.client.Do(request) if err != nil { return nil, err } defer response.Body.Close() if response.StatusCode < 200 || response.StatusCode >= 300 { return nil, fmt.Errorf("unexpected manifest status: %s", response.Status) } var document manifestDocument if err := json.NewDecoder(response.Body).Decode(&document); err != nil { return nil, err } if document.ProductID != req.App.ProductID { return nil, fmt.Errorf("manifest product mismatch: %s", document.ProductID) } var best *updates.Release for _, release := range document.Releases { if release.Channel != req.App.Channel { continue } comparison, err := updates.CompareVersions(release.Version, req.App.CurrentVersion) if err != nil || comparison <= 0 { continue } for _, artifact := range release.Artifacts { if artifact.OS != req.App.OS || artifact.Arch != req.App.Arch { continue } parsedPublishedAt, err := parseReleaseTime(release.PublishedAt) if err != nil { return nil, err } candidate := &updates.Release{ ID: release.ID, Version: release.Version, Channel: release.Channel, NotesMarkdown: release.NotesMarkdown, PublishedAt: parsedPublishedAt, Artifact: updates.Artifact{ Kind: artifact.Kind, Format: artifact.Format, URL: artifact.URL, SHA256: artifact.SHA256, Size: artifact.Size, }, } if best == nil { best = candidate continue } better, err := updates.CompareVersions(candidate.Version, best.Version) if err != nil { return nil, err } if better > 0 { best = candidate } } } return best, nil } func (provider *Provider) OpenArtifact(ctx context.Context, release updates.Release) (io.ReadCloser, error) { request, err := http.NewRequestWithContext(ctx, http.MethodGet, release.Artifact.URL, nil) if err != nil { return nil, err } if provider.config.PrepareRequest != nil { if err := provider.config.PrepareRequest(request); err != nil { return nil, err } } response, err := provider.client.Do(request) if err != nil { return nil, err } if response.StatusCode < 200 || response.StatusCode >= 300 { defer response.Body.Close() return nil, fmt.Errorf("unexpected artifact status: %s", response.Status) } return response.Body, nil }