faynosync

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

faynoSync Go SDK

Production-oriented Go SDK for checking application updates with faynoSync.

This package is a small typed transport and developer experience layer. It does not implement update installation, platform normalization, metadata verification, caching, or business rules.

Installation

go get github.com/ku9nov/faynosync-sdk-go

Quick Start

package main

import (
	"context"
	"fmt"
	"log"

	faynosync "github.com/ku9nov/faynosync-sdk-go"
)

func main() {
	client := faynosync.NewClient(faynosync.Config{
		BaseURL: "https://api.example.com",
	})

	resp, err := client.CheckForUpdates(context.Background(), faynosync.CheckOptions{
		Owner:    "admin",
		AppName:  "test",
		Version:  "0.0.0.5",
		Channel:  "nightly",
		Platform: "darwin",
		Arch:     "arm64",
	})
	if err != nil {
		log.Fatal(err)
	}

	if resp.UpdateAvailable {
		if resp.UpdateURL != "" {
			fmt.Printf("Update is available: %s\n", resp.UpdateURL)
		}
		for _, packageURL := range resp.PackageURLs {
			fmt.Printf("%s update is available: %s\n", packageURL.Package, packageURL.URL)
		}
	}
}

Configuration

client := faynosync.NewClient(faynosync.Config{
	BaseURL: "https://api.example.com",
	EdgeURL: "https://cdn.example.com",
	HTTPClient: &http.Client{
		Timeout: 10 * time.Second,
	},
})

BaseURL is required. It points to the faynoSync API.

EdgeURL is optional. When configured, the SDK tries a static edge JSON response before falling back to the API.

HTTPClient is optional. When omitted, the SDK creates an http.Client with a default timeout. Custom clients are useful for timeouts, proxies, custom transports, and connection pooling policies.

The client is safe for concurrent use.

Update Checks

CheckForUpdates sends a context-aware request and returns a typed response:

resp, err := client.CheckForUpdates(ctx, faynosync.CheckOptions{
	Owner:    "admin",
	AppName:  "test",
	Version:  "0.0.0.5",
	Channel:  "nightly",
	Platform: "darwin",
	Arch:     "arm64",
	DeviceID: "optional-device-id",
})

DeviceID is optional. When set, the SDK sends it as the X-Device-ID header.

Staged Rollout

faynoSync can ship a version to a controlled percentage of the fleet first (a staged/canary rollout). When the offered version's rollout is below 100%, /checkVersion includes a rollout object and the SDK decides — client-side — whether this install is included:

{
  "update_available": true,
  "update_url": "https://downloads.example.com/app",
  "rollout": { "percent": 20, "seed": "badadc23b08e3943" }
}

The decision is deterministic and sticky, using the reference algorithm shared by every faynoSync SDK:

bucket = sha256(deviceID + ":" + seed) → first 8 bytes, big-endian uint64, % 100
included if bucket < rollout.percent

When the install is not in the bucket, the SDK forces UpdateAvailable to false and clears UpdateURL/PackageURLs — so a caller that inspects the URLs instead of UpdateAvailable still cannot pull an update the device was not offered. The Rollout field on the response exposes the decision for logging:

resp, err := client.CheckForUpdates(ctx, faynosync.CheckOptions{ /* ... */ DeviceID: "stable-device-id"})
if err != nil {
	log.Fatal(err)
}
if resp.Rollout != nil {
	fmt.Println(resp.Rollout.Percent, resp.Rollout.Bucket, resp.Rollout.Eligible)
}

DeviceID is required to participate: it must be the same stable value used for telemetry (X-Device-ID). Without it the bucket cannot be computed, so the install stays out of the rollout (Eligible: false, Bucket: nil) until a DeviceID is provided. Raising the percentage on the same version only ever adds installs. Rollout works identically in edge/CDN mode, since the same JSON body is served from the cached manifest.

The faynosync.RolloutBucket(deviceID, seed) helper is exported if you need to compute a bucket yourself.

Base API Request

The BaseURL API request uses GET /checkVersion:

GET /checkVersion?app_name=test&version=0.0.0.5&channel=nightly&platform=darwin&arch=arm64&owner=admin
X-Device-ID: optional

Query parameters are built from CheckOptions with typed fields. The SDK does not use untyped maps in its public API.

Response Model

faynoSync may return a direct binary update URL:

{
  "update_available": true,
  "update_url": "https://downloads.example.com/app"
}

It may also return package-specific URLs with dynamic field names:

{
  "update_available": true,
  "update_url_deb": "https://downloads.example.com/app.deb",
  "update_url_rpm": "https://downloads.example.com/app.rpm",
  "changelog": "### Changelog\n\n- Added feature X",
  "critical": true,
  "is_intermediate_required": true,
  "possible_rollback": true
}

When a version is under a staged rollout, the response also carries a rollout object ({ percent, seed }), decoded into resp.Rollout — see Staged Rollout.

The SDK decodes these into a typed response:

if resp.UpdateURL != "" {
	fmt.Println(resp.UpdateURL)
}

for _, packageURL := range resp.PackageURLs {
	fmt.Println(packageURL.Package, packageURL.URL)
}

EdgeURL Fallback

When EdgeURL is configured, the SDK first tries a static JSON response:

GET /responses/{owner}/{app_name}/{channel}/{platform}/{arch}/manual/{version}.json

For example:

GET /responses/admin/test/nightly/darwin/arm64/manual/0.0.0.5.json

If the edge response succeeds with HTTP 200 and valid JSON, UpdateResponse.Source is SourceEdge.

The SDK falls back to the BaseURL API when the edge request has:

  • a network error;
  • a timeout;
  • invalid JSON;
  • HTTP 404;
  • any other non-200 response.

If the fallback API succeeds, UpdateResponse.Source is SourceAPI.

Platform, Channel, And Architecture Values

faynoSync supports fully custom platform, channel, and architecture values. This SDK never normalizes or remaps them.

The SDK will not change values such as:

  • macos to darwin;
  • osx to darwin;
  • stable to default.

Whatever string you pass in CheckOptions.Channel, CheckOptions.Platform, and CheckOptions.Arch is the string sent to faynoSync.

Optional System Helpers

The SDK provides optional helpers:

platform := faynosync.SystemPlatform() // runtime.GOOS
arch := faynosync.SystemArch()         // runtime.GOARCH

These helpers are never used automatically. Use them only when Go runtime values match your faynoSync configuration.

Error Handling

The SDK validates required fields and returns typed sentinel errors:

resp, err := client.CheckForUpdates(ctx, opts)
if err != nil {
	switch {
	case errors.Is(err, faynosync.ErrMissingBaseURL):
		// Configure Config.BaseURL.
	case errors.Is(err, faynosync.ErrMissingOwner):
		// Set CheckOptions.Owner.
	case errors.Is(err, faynosync.ErrMissingAppName):
		// Set CheckOptions.AppName.
	case errors.Is(err, faynosync.ErrMissingVersion):
		// Set CheckOptions.Version.
	case errors.Is(err, faynosync.ErrRequestFailed):
		// Inspect the wrapped endpoint error.
	default:
		// Handle any other error.
	}
}

Request failures preserve underlying causes for errors.Is and errors.As:

var endpointErr *faynosync.EndpointError
if errors.As(err, &endpointErr) {
	fmt.Println(endpointErr.URL)
	fmt.Println(endpointErr.StatusCode)
}

Examples

Runnable examples are available in:

  • examples/basic
  • examples/edge-fallback
  • examples/custom-http-client

Security Scope

This SDK version performs update-check transport requests and typed response decoding only.

It does not verify TUF metadata, signatures, thresholds, expiration, rollback protection, or cache safety. Applications that need secure update metadata verification must perform that verification in the appropriate faynoSync component or a future SDK layer that explicitly implements it.

No signature, threshold, expiration, rollback, freeze, root-of-trust, or cache protection is weakened by this transport-only SDK.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrMissingBaseURL is returned when Config.BaseURL is empty.
	ErrMissingBaseURL = errors.New("faynosync: missing base URL")

	// ErrInvalidBaseURL is returned when Config.BaseURL cannot be used as an absolute URL.
	ErrInvalidBaseURL = errors.New("faynosync: invalid base URL")

	// ErrInvalidEdgeURL is returned when Config.EdgeURL cannot be used as an absolute URL.
	ErrInvalidEdgeURL = errors.New("faynosync: invalid edge URL")

	// ErrMissingOwner is returned when CheckOptions.Owner is empty.
	ErrMissingOwner = errors.New("faynosync: missing owner")

	// ErrMissingAppName is returned when CheckOptions.AppName is empty.
	ErrMissingAppName = errors.New("faynosync: missing app name")

	// ErrMissingVersion is returned when CheckOptions.Version is empty.
	ErrMissingVersion = errors.New("faynosync: missing version")

	// ErrRequestFailed is returned when an update check request fails.
	ErrRequestFailed = errors.New("faynosync: request failed")
)

Functions

func RolloutBucket added in v0.3.0

func RolloutBucket(deviceID, seed string) int

RolloutBucket maps a device to a deterministic bucket in [0, 99]: sha256(deviceID + ":" + seed), first 8 bytes as a big-endian uint64, modulo 100.

This is the reference algorithm every faynoSync SDK must replicate byte-for-byte so a device's rollout decision matches across SDKs.

func SystemArch

func SystemArch() string

SystemArch returns runtime.GOARCH.

The SDK never calls this automatically. It is provided only for callers that choose to use Go runtime architecture names as their faynoSync arch values.

func SystemPlatform

func SystemPlatform() string

SystemPlatform returns runtime.GOOS.

The SDK never calls this automatically. It is provided only for callers that choose to use Go runtime platform names as their faynoSync platform values.

Types

type CheckError

type CheckError struct {
	EdgeErr error
	APIErr  error
}

CheckError describes a failed update check after all configured endpoints fail.

func (*CheckError) Error

func (e *CheckError) Error() string

Error returns a human-readable update check error message.

func (*CheckError) Is

func (e *CheckError) Is(target error) bool

Is reports whether the check error matches a sentinel error.

func (*CheckError) Unwrap

func (e *CheckError) Unwrap() []error

Unwrap returns all endpoint errors that contributed to the failed check.

type CheckOptions

type CheckOptions struct {
	Owner   string
	AppName string
	Version string

	Channel  string
	Platform string
	Arch     string

	// DeviceID optionally enables server-side telemetry when supported by the API.
	// When empty, the X-Device-ID header is omitted.
	DeviceID string
}

CheckOptions contains the typed parameters used to check for updates.

Channel, Platform, and Arch are intentionally user-controlled values. The SDK does not detect, normalize, remap, or default them.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a concurrency-safe faynoSync SDK client.

func NewClient

func NewClient(cfg Config) *Client

NewClient creates a new faynoSync SDK client.

The returned client is safe for concurrent use. If cfg.HTTPClient is nil, the SDK creates an HTTP client with a default timeout and reusable connections.

func (*Client) CheckForUpdates

func (c *Client) CheckForUpdates(ctx context.Context, opts CheckOptions) (*UpdateResponse, error)

CheckForUpdates checks whether an update is available for the provided app.

If Config.EdgeURL is configured, the client first tries the static edge JSON response and falls back to the BaseURL API when the edge misses or fails.

type Config

type Config struct {
	// BaseURL is the required faynoSync API base URL.
	BaseURL string

	// EdgeURL is an optional static response edge base URL.
	// When configured, the client tries EdgeURL before falling back to BaseURL.
	EdgeURL string

	// HTTPClient is an optional HTTP client.
	// When nil, the SDK creates a client with a reasonable default timeout.
	HTTPClient *http.Client
}

Config configures a faynoSync SDK client.

type EndpointError

type EndpointError struct {
	Source     UpdateSource
	URL        string
	StatusCode int
	Err        error
}

EndpointError describes a failed request to one faynoSync endpoint.

func (*EndpointError) Error

func (e *EndpointError) Error() string

Error returns a human-readable endpoint error message.

func (*EndpointError) Is

func (e *EndpointError) Is(target error) bool

Is reports whether the endpoint error matches a sentinel error.

func (*EndpointError) Unwrap

func (e *EndpointError) Unwrap() error

Unwrap returns the underlying endpoint error.

type PackageUpdateURL

type PackageUpdateURL struct {
	Package string
	URL     string
}

PackageUpdateURL contains one package-specific update URL.

type RolloutInfo added in v0.3.0

type RolloutInfo struct {
	Percent int    `json:"percent"`
	Seed    string `json:"seed"`

	// Bucket is the deterministic bucket in [0, 99] for this device, or nil when no
	// DeviceID was supplied and the bucket could not be computed.
	Bucket   *int `json:"-"`
	Eligible bool `json:"-"`
}

RolloutInfo describes a staged (canary) rollout decision for the offered version.

It is present on UpdateResponse only when the server offered a rollout below 100%. When Eligible is false the SDK has already forced UpdateAvailable to false and cleared the download URLs.

type UpdateResponse

type UpdateResponse struct {
	UpdateAvailable        bool   `json:"update_available"`
	UpdateURL              string `json:"update_url,omitempty"`
	Changelog              string `json:"changelog,omitempty"`
	Critical               bool   `json:"critical,omitempty"`
	IsIntermediateRequired bool   `json:"is_intermediate_required,omitempty"`
	PossibleRollback       bool   `json:"possible_rollback,omitempty"`

	// Rollout is set only when the server offered a staged (canary) rollout for the
	// version. When Rollout.Eligible is false the SDK has already forced
	// UpdateAvailable to false and cleared UpdateURL/PackageURLs. It is decoded
	// manually from the raw rollout object, so a malformed one is ignored rather than
	// failing the whole response.
	Rollout *RolloutInfo `json:"-"`

	// PackageURLs contains package-specific URLs decoded from fields such as
	// update_url_deb, update_url_rpm, or any future update_url_<package> key.
	PackageURLs []PackageUpdateURL `json:"-"`

	// Source identifies whether the response came from the edge or API fallback.
	Source UpdateSource `json:"-"`
}

UpdateResponse contains the typed faynoSync update check response.

func (*UpdateResponse) UnmarshalJSON

func (r *UpdateResponse) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes fixed response fields and dynamic update_url_<package> fields into a typed representation.

type UpdateSource

type UpdateSource int

UpdateSource identifies where an update response was loaded from.

const (
	// SourceUnknown indicates that the response source is unknown.
	SourceUnknown UpdateSource = iota

	// SourceEdge indicates that the response came from the configured EdgeURL.
	SourceEdge

	// SourceAPI indicates that the response came from the configured BaseURL API.
	SourceAPI
)

Directories

Path Synopsis
examples
basic command
edge-fallback command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL