@shiftsync/sdk
v0.5.1
Published
The **public**, third-party / OAuth-facing ShiftSync client and domain types.
Downloads
363
Readme
@shiftsync/sdk
The public, third-party / OAuth-facing ShiftSync client and domain types.
It exposes only the surface reachable with a requestable OAuth scope —
shifts:*, templates:*, friends:*, invites:*, groups:*, pay:*, ics:*,
user:* — plus the token-lifecycle primitives (refreshToken / logout /
setAuthToken / onTokenRefresh / onAuthenticationFailed).
import { ApiClient } from '@shiftsync/sdk'
const client = new ApiClient('https://api.shiftsync.app', {
accessToken,
refreshToken,
expiresAt,
})
const { items: shifts } = await client.getShifts({ start, end })Pass the bare API host — ApiClient targets the versioned /v1 API automatically (e.g.
getShifts → GET /v1/shifts, refreshToken/logout → /v1/auth/*). Only the spec-fixed
OAuth protocol paths (/oauth/*, /.well-known/*) stay unversioned — the OAuth helpers
below hit those directly.
ApiClient— the HTTP client. Itsget/post/delete/endpointhelpers areprotectedso the first-partyInternalApiClient(in@shiftsync/sdk-internal) can extend it and reuse the plumbing.src/types/*— domain types, each derived from a co-located zod schema that is the validation single source of truth the API enforces.@shiftsync/sdk/testing— fixture factories for the public entities (makeShift,makeTemplate,makeFriend,makeUser,makeInvite,makeGroup,makePaySettings,makePayRule).
First-party-only machinery (the first-party login/2FA flow, push, avatar uploads, the
OAuth consent screen, account deletion, the startup bundle, the realtime/WebSocket
event contract) lives in the private @shiftsync/sdk-internal package, not here. The
public OAuth client dance (below) — what a third-party developer uses to obtain a
token — does live here.
OAuth helpers
ShiftSync is a public-client, PKCE-S256 OAuth 2.0 server (no client secret). These
standalone helpers drive the authorization-code flow against an issuer base URL — no
host is hard-coded:
import {
discoverAuthorizationServer,
generateCodeVerifier,
generateCodeChallenge,
generateState,
buildAuthorizationUrl,
exchangeAuthorizationCode,
refreshAccessToken,
revokeToken,
registerClient,
toAuthToken,
ApiClient,
} from '@shiftsync/sdk'
const issuer = 'https://api.shiftsync.app'
// (optional) one-time: register a public client via Dynamic Client Registration (RFC 7591)
const { client_id } = await registerClient({ issuer, redirectUris: ['myapp://cb'] })
// 1. PKCE + state, then send the user to the authorize URL
const codeVerifier = generateCodeVerifier()
const state = generateState()
const url = buildAuthorizationUrl({
issuer,
clientId: client_id,
redirectUri: 'myapp://cb',
scopes: ['shifts:read', 'templates:read'],
state,
codeChallenge: await generateCodeChallenge(codeVerifier),
})
// 2. after the redirect back (verify `state` first), exchange the code
const tokenResponse = await exchangeAuthorizationCode({
issuer,
clientId: client_id,
code,
codeVerifier,
redirectUri: 'myapp://cb',
})
// 3. map the wire response to an AuthToken and drive the API
const client = new ApiClient(issuer, toAuthToken(tokenResponse))
// later: rotate or revoke
const next = await refreshAccessToken({ issuer, clientId: client_id, refreshToken })
await revokeToken({ issuer, token: refreshToken })discoverAuthorizationServer(issuer) returns the validated RFC 8414 metadata
(token_endpoint, scopes_supported, …) for clients that prefer discovery over the
fixed /oauth/* paths. toAuthToken converts the relative expires_in to an absolute
expiresAt — call it immediately on receiving a token (see its JSDoc).
Build
npm run build -w @shiftsync/sdk # or, with the internal companion: npm run build:sdkBuilds a single self-contained ESM bundle + bundled .d.ts via Vite + vite-plugin-dts.
dayjs and zod are runtime dependencies, externalised from the bundle.
