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.
Edge response caching and the edge-first fallback flow are designed to work correctly when clients use an official faynoSync SDK (this Go SDK or the JS SDK). Raw /checkVersion calls do not provide the same edge orchestration, Source semantics, or SDK-optimized telemetry paths. See the SDK overview for context.
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)
}
}
}
Client Configuration
client := faynosync.NewClient(faynosync.Config{
BaseURL: "https://api.example.com",
EdgeURL: "https://cdn.example.com",
HTTPClient: &http.Client{
Timeout: 10 * time.Second,
},
})
| Field | Type | Required | Description |
|---|---|---|---|
BaseURL | string | Yes | Base faynoSync API URL used for update checks. |
EdgeURL | string | No | Optional edge endpoint used first for static JSON responses. |
HTTPClient | *http.Client | No | Custom HTTP client for timeouts, transports, proxy, and pooling policies. |
Notes:
BaseURLis required.- If
HTTPClientis omitted, the SDK creates a default client with timeout. - The client is safe for concurrent use.
CheckForUpdates Contract
CheckForUpdates sends a context-aware request and returns a typed UpdateResponse.
CheckOptions
| Field | Type | Required | Description |
|---|---|---|---|
Owner | string | Yes | Application owner username. |
AppName | string | Yes | Application name. |
Version | string | Yes | Current client version. |
Channel | string | Yes | Release channel value used by your faynoSync setup. |
Platform | string | Yes | Platform value used by your faynoSync setup. |
Arch | string | Yes | Architecture value used by your faynoSync setup. |
DeviceID | string | No | Device identifier sent as X-Device-ID header when provided. Required to participate in staged rollouts. |
DownloadToken | string | No | Download token of a private app in strict mode, sent as the X-Download-Token header. Requires SDK v0.4.0 or later. |
For a strict private app, a check without DownloadToken gets the same answer as an app that does not exist. When DownloadToken is set, the SDK skips EdgeURL and asks the API directly, because private apps are never published to the edge. If your application downloads the artifact itself, send the same header to /download and use faynosync.StripDownloadTokenOnRedirect as the http.Client's CheckRedirect, so the token is not forwarded to the storage host.
Request Mapping
Base API endpoint:
GET /checkVersion?app_name=test&version=0.0.0.5&channel=nightly&platform=darwin&arch=arm64&owner=admin&updater=manual
X-Device-ID: optional-device-id
Query parameters are built from the named fields on CheckOptions (Owner, AppName, Version, and so on). You pass a struct with compile-time types instead of a generic key-value map such as map[string]string, which keeps the API explicit and easier to validate.
Response Contract
Core Response Fields
| Field | Type | Description |
|---|---|---|
UpdateAvailable | bool | Whether an update is available for the request scope. |
UpdateURL | string | Direct binary update URL (single artifact flow). |
PackageURLs | []PackageURL | Package-specific update URLs extracted from dynamic response keys. |
Changelog | string | Optional release notes or markdown changelog. |
Critical | bool | Indicates a critical update. |
IsIntermediateRequired | bool | Indicates whether an intermediate update is required. |
PossibleRollback | bool | Indicates rollback metadata from server response. |
Source | enum | Response source: SourceEdge or SourceAPI. |
Rollout | *RolloutInfo | Present (non-nil) only when the server offered a staged rollout for the version. See Staged Rollout. |
Dynamic Package URL Mapping
faynoSync can return package-specific URL fields with dynamic suffixes. For example:
update_url_debupdate_url_rpmupdate_url_msi
Other package types use the same update_url_<package> pattern (for example, update_url_appimage or update_url_pkg).
The SDK maps these dynamic fields into a typed collection:
for _, packageURL := range resp.PackageURLs {
fmt.Println(packageURL.Package, packageURL.URL)
}
Source Semantics
| Value | Meaning |
|---|---|
SourceEdge | The response was served from the edge JSON endpoint. |
SourceAPI | The response was served by the BaseURL API after direct call or fallback. |
Staged Rollout
faynoSync can ship a version to a controlled percentage of the fleet first (a staged/canary rollout). The server stays stateless — it only issues a target percentage and a stable seed — and the SDK decides client-side whether a given install is included. See Staged Rollout for the full design.
When the offered version's rollout is below 100%, /checkVersion includes a rollout object:
{
"update_available": true,
"update_url": "https://downloads.example.com/app",
"rollout": { "percent": 20, "seed": "badadc23b08e3943" }
}
At 100% the field is omitted and the SDK treats the response as a normal full update. 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 checks the URLs instead of UpdateAvailable still cannot pull an update the device was not offered. A normal if resp.UpdateAvailable gate is all you need; the Rollout field 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)
}
RolloutInfo
| Field | Type | Description |
|---|---|---|
Percent | int | The rollout target percentage the server issued for the version. |
Seed | string | Stable per-version seed used to bucket the device. |
Bucket | *int | Deterministic bucket in [0, 99], or nil when no DeviceID was supplied. |
Eligible | bool | Whether the install is inside the rollout. When false, the SDK has already gated the response. |
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 — an install in the 20% bucket stays in when you move to 50%.
A malformed rollout object (missing/non-numeric percent, or missing/empty seed) is ignored and treated as no rollout at all — the full update is served. Rollout works identically in edge/CDN mode, since the same JSON body is served from the cached manifest and the split happens in the SDK after the (cacheable) response is read.
The faynosync.RolloutBucket(deviceID, seed) helper is exported if you need to compute a bucket yourself.
Edge Fallback Behavior
Edge delivery only works as intended when update checks go through an official faynoSync SDK. The server can publish cached JSON for CDN/object storage, but the client must implement edge-first requests, API fallback, and optional telemetry flows that the SDK provides.
When EdgeURL is configured, the SDK first requests:
GET /responses/{owner}/{app_name}/{channel}/{platform}/{arch}/manual/{version}.json
Example (manual is the updater the SDK checks with):
GET /responses/admin/test/nightly/darwin/arm64/manual/0.0.0.5.json
Empty Channel, Platform and Arch are left out of the path, the manual segment is dropped when Platform is empty, and - in the version becomes ., matching the object key the server writes.
Fallback Decision Matrix
| Edge Result | Fallback to BaseURL API | Final Source |
|---|---|---|
| HTTP 200 + valid JSON | No | SourceEdge |
| Network error | Yes | SourceAPI when API succeeds |
| Timeout | Yes | SourceAPI when API succeeds |
| Invalid JSON | Yes | SourceAPI when API succeeds |
| HTTP 404 | Yes | SourceAPI when API succeeds |
| Any non-200 status | Yes | SourceAPI when API succeeds |
If both edge and API fail, CheckForUpdates returns an error.
Platform, Channel, and Architecture Values
faynoSync supports custom values for platform, channel, and architecture. The SDK never normalizes or remaps them.
Examples of values that are not automatically transformed:
macostodarwinosxtodarwinstabletodefault
Whatever you set in CheckOptions.Channel, CheckOptions.Platform, and CheckOptions.Arch is sent as-is.
Optional System Helpers
The SDK provides optional helpers:
platform := faynosync.SystemPlatform() // runtime.GOOS
arch := faynosync.SystemArch() // runtime.GOARCH
These helpers are never applied automatically. Use them only when runtime values match your faynoSync configuration.
Error Handling
The SDK validates required fields and returns 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 wrapped endpoint failure.
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 live in the faynosync-sdk-go repository:
| Example | Description | Source |
|---|---|---|
| Basic | Minimal update check against BaseURL | examples/basic/main.go |
| Edge fallback | EdgeURL first, then API fallback | examples/edge-fallback/main.go |
| Custom HTTP client | Custom timeouts, transport, and pooling | examples/custom-http-client/main.go |
Security Scope
This SDK version performs update-check transport requests and typed response decoding only.