Documentation
¶
Overview ¶
Package pawpado is the official Go SDK for Pawpado — the single-user AWS GPU gaming portal.
Pick one auth strategy:
- Session: bearer obtained via Huudis device-flow login (the pawpado CLI / portal stash); SDK does proactive + reactive refresh under a single-flight guard.
- APIKey: static access token (mirrors the Node SDK's apiKey option).
- Neither: only Health() works without auth.
Surface mirrors the Node and Python SDKs: sessions, credits, billing, settings, account, admin namespaces plus a top-level health() helper.
Index ¶
- func VerifyWebhookSignature(rawBody []byte, signatureHeader string, secret string, ...) bool
- type AccountResource
- type AccountSession
- type AdminReconcileResult
- type AdminResource
- type BillingResource
- type BillingStorage
- type Client
- type ClientOptions
- type ConnectInfo
- type Credits
- type CreditsResource
- type Error
- type Health
- type OrphanAMI
- type OrphanEBS
- type OrphanList
- type PairInfo
- type ProfileData
- type RefreshFunc
- type RefreshResult
- type Session
- type SessionStatus
- type SessionsResource
- func (r *SessionsResource) Connect(ctx context.Context, token string) (*ConnectInfo, error)
- func (r *SessionsResource) Pair(ctx context.Context, token string) (*PairInfo, error)
- func (r *SessionsResource) Poll(ctx context.Context, token string) (*SessionStatus, error)
- func (r *SessionsResource) Start(ctx context.Context, token string) (*SessionStatus, error)
- func (r *SessionsResource) Stop(ctx context.Context, input *StopInput, token string) (*SessionStatus, error)
- func (r *SessionsResource) TailscaleKey(ctx context.Context, token string) (*TailscaleKey, error)
- type Settings
- type SettingsResource
- type StopInput
- type TailscaleKey
- type TopupInput
- type TopupSession
- type UserProfile
- type VerifyWebhookSignatureOptions
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func VerifyWebhookSignature ¶
func VerifyWebhookSignature( rawBody []byte, signatureHeader string, secret string, options *VerifyWebhookSignatureOptions, ) bool
VerifyWebhookSignature returns true iff the X-Pawpado-Signature header over the given raw request body validates against the provided secret within the tolerance window. Mount your webhook handler such that you can see the raw bytes — a framework that parses + re-serializes JSON will break the HMAC.
Types ¶
type AccountResource ¶
type AccountResource struct {
// contains filtered or unexported fields
}
AccountResource is the /session + /account namespace.
func (*AccountResource) Delete ¶
func (r *AccountResource) Delete(ctx context.Context, token string) error
Delete deletes /api/v1/account/delete.
func (*AccountResource) Session ¶
func (r *AccountResource) Session(ctx context.Context, token string) (*AccountSession, error)
Session gets /api/v1/session.
type AccountSession ¶
type AccountSession struct {
User UserProfile `json:"user"`
SessionExpAt string `json:"sessionExpAt"`
}
AccountSession is the response from GET /session.
type AdminReconcileResult ¶
type AdminReconcileResult struct {
Reconciled int `json:"reconciled"`
Details map[string]any `json:"details,omitempty"`
}
AdminReconcileResult is the response from POST /admin/reconcile.
type AdminResource ¶
type AdminResource struct {
// contains filtered or unexported fields
}
AdminResource is the /admin namespace (operator-only).
func (*AdminResource) Orphans ¶
func (r *AdminResource) Orphans(ctx context.Context, token string) (*OrphanList, error)
Orphans gets /api/v1/admin/orphans.
func (*AdminResource) Reconcile ¶
func (r *AdminResource) Reconcile(ctx context.Context, token string) (*AdminReconcileResult, error)
Reconcile posts to /api/v1/admin/reconcile.
type BillingResource ¶
type BillingResource struct {
// contains filtered or unexported fields
}
BillingResource is the /billing namespace.
func (*BillingResource) Storage ¶
func (r *BillingResource) Storage(ctx context.Context, token string) (*BillingStorage, error)
Storage gets /api/v1/billing/storage.
type BillingStorage ¶
type BillingStorage struct {
StorageGB float64 `json:"storageGb"`
RatePerGB float64 `json:"ratePerGb"`
PeriodFrom string `json:"periodFrom"`
PeriodTo string `json:"periodTo"`
}
BillingStorage is the response from GET /billing/storage.
type Client ¶
type Client struct {
BaseURL string
HTTP *http.Client
// Auth — exactly one of these is typically set.
Session *Session
APIKey string
// RefreshBufferSec is how many seconds before ExpiresAt we proactively
// refresh. Defaults to 300 (5min) — matches the Node + Python SDKs.
RefreshBufferSec int
// RetryOn5xx is how many extra attempts we make on 5xx responses.
// Defaults to 1.
RetryOn5xx int
Sessions *SessionsResource
Credits *CreditsResource
Billing *BillingResource
Settings *SettingsResource
Account *AccountResource
Admin *AdminResource
}
Client is the high-level Pawpado SDK surface.
Construct via NewClient. Namespaces are exposed as fields so the call site reads like the Node SDK:
c.Sessions.Start(ctx, nil)
c.Credits.Topup(ctx, &pawpado.TopupInput{AmountCents: 50000}, nil)
func NewClient ¶
func NewClient(opts ClientOptions) (*Client, error)
NewClient constructs a Pawpado client. Reads PAWPADO_BASE_URL and PAWPADO_API_KEY from env when the corresponding fields are empty — same convenience the Node + Python SDKs offer.
type ClientOptions ¶
type ClientOptions struct {
// BaseURL defaults to PAWPADO_BASE_URL env var, then https://pawpado.com.
BaseURL string
// Session enables Bearer + proactive/reactive refresh.
Session *Session
// APIKey is a static access token; mutually exclusive with Session in
// practice (Session wins if both are set).
APIKey string
// HTTP overrides the default http.Client. Useful for tests.
HTTP *http.Client
// RefreshBufferSec — see Client.RefreshBufferSec.
RefreshBufferSec int
// RetryOn5xx — see Client.RetryOn5xx.
RetryOn5xx int
}
ClientOptions wires up NewClient. Mirrors the Node SDK's PawpadoClientOptions and the env-var defaults of the huudis-go SDK.
type ConnectInfo ¶
type ConnectInfo struct {
Host string `json:"host"`
Port int `json:"port"`
Password *string `json:"password,omitempty"`
}
ConnectInfo is returned by GET /sessions/connect.
type Credits ¶
type Credits struct {
BalanceCents int64 `json:"balanceCents"`
Currency string `json:"currency"` // IDR | USD
StorageGB *int `json:"storageGb,omitempty"`
FreeStorageGB *int `json:"freeStorageGb,omitempty"`
AsOf string `json:"asOf"`
}
Credits is the wallet view.
type CreditsResource ¶
type CreditsResource struct {
// contains filtered or unexported fields
}
CreditsResource is the /credits namespace.
func (*CreditsResource) Topup ¶
func (r *CreditsResource) Topup(ctx context.Context, input *TopupInput, token string) (*TopupSession, error)
Topup posts to /api/v1/credits/topup.
type Error ¶
type Error struct {
Code string
Message string
Status int // HTTP status code; 0 for transport / auth errors.
RequestID string // From response meta.requestId; empty if absent.
Details any // Server-supplied error.details, if any.
}
Error is the typed error returned by every operation in this package.
Three sub-kinds, distinguished by Code prefix:
- HTTP envelope errors from the Pawpado API surface this with the server-side .code (e.g. "INSUFFICIENT_CREDITS"), .Status set to the HTTP code, and .RequestID populated from the response meta.
- Auth / refresh failures use codes like "MISSING_TOKEN", "SESSION_NOT_FOUND", "NO_REFRESH_TOKEN", "REFRESH_FAILED".
- Transport failures use "NETWORK_ERROR".
Mirrors the Node SDK's PawpadoError / ApiError split and the Python SDK's PawpadoError / PawpadoAuthError / RefreshError / NetworkError. In Go a single typed error with .Code is more idiomatic than four classes — callers branch on Code or use errors.As.
type Health ¶
type Health struct {
Status string `json:"status"`
}
Health is the response from GET /health.
type OrphanAMI ¶
type OrphanAMI struct {
ImageID string `json:"imageId"`
SizeGB float64 `json:"sizeGb"`
CreatedAt string `json:"createdAt"`
}
OrphanAMI is one entry in OrphanList.AMIs.
type OrphanEBS ¶
type OrphanEBS struct {
VolumeID string `json:"volumeId"`
SizeGB float64 `json:"sizeGb"`
CreatedAt string `json:"createdAt"`
}
OrphanEBS is one entry in OrphanList.EBS.
type OrphanList ¶
OrphanList is the response from GET /admin/orphans.
type PairInfo ¶
type PairInfo struct {
PIN string `json:"pin"`
PairURL string `json:"pairUrl"`
ExpiresAt string `json:"expiresAt"`
}
PairInfo is returned by POST /sessions/pair.
type ProfileData ¶
type ProfileData struct {
AccessToken string
ExpiresAt int64 // Unix seconds.
RefreshToken string
Scope string
AccountID string
}
ProfileData is the in-memory bearer state.
type RefreshFunc ¶
type RefreshFunc func(ctx context.Context, refreshToken string) (*RefreshResult, error)
RefreshFunc is the swap-point for Session refresh. Called with the current refresh token; returns the new token-set or an error.
type RefreshResult ¶
type RefreshResult struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token,omitempty"`
Scope string `json:"scope,omitempty"`
TokenType string `json:"token_type,omitempty"`
}
RefreshResult is what RefreshFunc returns. Mirrors the OIDC token response.
type Session ¶
type Session struct {
// Issuer is the Huudis base URL (e.g. https://huudis.com). Required.
Issuer string
// ClientID is the OIDC client identifier. Required.
ClientID string
// HTTP overrides the http.Client used for the token endpoint. Defaults
// to http.DefaultClient.
HTTP *http.Client
// RefreshFunc, if set, replaces the default token-endpoint POST. Useful
// for tests; production code should leave it nil.
RefreshFunc RefreshFunc
// contains filtered or unexported fields
}
Session is the slim in-process credentials holder + single-flight refresh guard. Mirrors the Python SDK's pawpado.session.Session (which itself ports @forjio/sdk's Session). When a central forjio-sdk-go is extracted later, this moves there unchanged.
Single-flight is critical for pawpado: the frontend polls /sessions/poll, so a flurry of in-flight requests can each see an expired access_token and race to call the OIDC refresh endpoint. Huudis revokes the entire refresh-token family on reuse — see memory note feedback_huudis_refresh_singleflight — so without the guard you burn the user's session.
This implementation:
- Holds bearer state in memory only (no on-disk credentials store; the pawpado portal/CLI manages persistence outside the SDK).
- Guards refresh with a single-flight Event-style channel: concurrent callers all wait on one fetch and share the result.
- Defaults to the OIDC token endpoint at {Issuer}/api/v1/oidc/token; tests can inject a RefreshFunc.
func (*Session) Data ¶
func (s *Session) Data() *ProfileData
Data returns the current in-memory ProfileData. nil if SetData has not been called.
func (*Session) IsExpired ¶
IsExpired reports whether the current access token is past its ExpiresAt. Returns true when no data is loaded.
func (*Session) Refresh ¶
Refresh atomically refreshes the access token. Concurrent callers share the same in-flight refresh and all receive the same result/error.
Critical for pawpado's polling endpoints: see Session godoc.
func (*Session) SetData ¶
func (s *Session) SetData(d *ProfileData)
SetData installs an in-memory ProfileData. Useful for boot-from-disk or tests. Pass nil to clear.
func (*Session) WillExpireSoon ¶
WillExpireSoon reports whether the access token will expire within bufferSec. Returns true when no data is loaded.
type SessionStatus ¶
type SessionStatus struct {
State string `json:"state"` // idle|provisioning|starting|running|stopping|stopped|failed
InstanceID *string `json:"instanceId,omitempty"`
PublicIP *string `json:"publicIp,omitempty"`
TailnetIP *string `json:"tailnetIp,omitempty"`
StartedAt *string `json:"startedAt,omitempty"`
Ready *bool `json:"ready,omitempty"`
Message *string `json:"message,omitempty"`
}
SessionStatus mirrors the Node SDK's SessionStatus type.
type SessionsResource ¶
type SessionsResource struct {
// contains filtered or unexported fields
}
SessionsResource is the /sessions namespace.
All methods accept an optional per-call bearer token. Pass "" to use the client's Session / APIKey.
func (*SessionsResource) Connect ¶
func (r *SessionsResource) Connect(ctx context.Context, token string) (*ConnectInfo, error)
Connect gets /api/v1/sessions/connect.
func (*SessionsResource) Poll ¶
func (r *SessionsResource) Poll(ctx context.Context, token string) (*SessionStatus, error)
Poll gets /api/v1/sessions/poll. Designed to be called every few seconds — the Session's single-flight refresh exists specifically so this method can't burn a refresh-token family.
func (*SessionsResource) Start ¶
func (r *SessionsResource) Start(ctx context.Context, token string) (*SessionStatus, error)
Start posts to /api/v1/sessions/start.
func (*SessionsResource) Stop ¶
func (r *SessionsResource) Stop(ctx context.Context, input *StopInput, token string) (*SessionStatus, error)
Stop posts to /api/v1/sessions/stop. Pass nil for the default no-force behavior; pass &StopInput{Force: true} to force-stop a stuck instance.
func (*SessionsResource) TailscaleKey ¶
func (r *SessionsResource) TailscaleKey(ctx context.Context, token string) (*TailscaleKey, error)
TailscaleKey posts to /api/v1/sessions/tailscale-key.
type Settings ¶
type Settings struct {
StorageGB *int `json:"storageGb,omitempty"`
Region *string `json:"region,omitempty"`
PreferredHours []string `json:"preferredHours,omitempty"`
AutoStopMinutes *int `json:"autoStopMinutes,omitempty"`
}
Settings is the per-user pawpado settings document.
type SettingsResource ¶
type SettingsResource struct {
// contains filtered or unexported fields
}
SettingsResource is the /settings namespace.
type StopInput ¶
type StopInput struct {
Force bool `json:"force,omitempty"`
}
StopInput is the body for POST /sessions/stop.
type TailscaleKey ¶
TailscaleKey is the short-lived auth key from POST /sessions/tailscale-key.
type TopupInput ¶
type TopupInput struct {
AmountCents int64 `json:"amountCents"`
Currency string `json:"currency,omitempty"` // IDR | USD
}
TopupInput is the body for POST /credits/topup.
type TopupSession ¶
TopupSession is the hosted checkout URL returned by /credits/topup.
type UserProfile ¶
type UserProfile struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
EmailVerified *bool `json:"emailVerified,omitempty"`
}
UserProfile is the embedded user in /session response.
type VerifyWebhookSignatureOptions ¶
type VerifyWebhookSignatureOptions struct {
// ToleranceSeconds rejects signatures with a timestamp older than this
// many seconds. Zero or negative means "use the default" (300).
ToleranceSeconds int64
// Now is the clock used for the freshness check. Nil means time.Now.
// Useful in tests.
Now func() time.Time
}
VerifyWebhookSignatureOptions tunes the webhook verifier. Pass a pointer to a zero-value struct to use the defaults.
Pawpado's outbound webhook signing is identical to Huudis's: HMAC-SHA256 over "{unix}.{rawBody}" with the shared secret. Header looks like "t=<unix>,v1=<hex>".