@buildbase/sdk 0.0.82 → 0.0.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +11 -3
- package/CHANGELOG.md +95 -0
- package/README.md +138 -41
- package/dist/css/styles.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +288 -8
- package/dist/index.mjs +1 -1
- package/dist/mcp/index.cjs +1 -1
- package/dist/mcp/index.d.ts +40 -0
- package/dist/mcp/index.mjs +1 -1
- package/dist/react/chunks/SettingsDialog-DImZc0fo.cjs +2 -0
- package/dist/react/chunks/SettingsDialog-DuqMmX00.mjs +2 -0
- package/dist/react/chunks/{SubscriptionDialog-CuUoM9hd.cjs → SubscriptionDialog-C4I66PXv.cjs} +1 -1
- package/dist/react/chunks/{SubscriptionDialog-ClWD2NgN.mjs → SubscriptionDialog-hXscplmw.mjs} +1 -1
- package/dist/react/chunks/ar-Bv7MJdID.mjs +2 -0
- package/dist/react/chunks/ar-UyL2IcEN.cjs +2 -0
- package/dist/react/chunks/de-BjV4j4L8.mjs +2 -0
- package/dist/react/chunks/de-BpFTP77W.cjs +2 -0
- package/dist/react/chunks/es-BD83AlAI.mjs +2 -0
- package/dist/react/chunks/es-Cg0WHGyt.cjs +2 -0
- package/dist/react/chunks/fr-DC4nKToc.cjs +2 -0
- package/dist/react/chunks/fr-DUSYz0eM.mjs +2 -0
- package/dist/react/chunks/hi-BUaHa-qu.mjs +2 -0
- package/dist/react/chunks/hi-LZW4ZZQj.cjs +2 -0
- package/dist/react/chunks/ja-BYVJft1g.cjs +2 -0
- package/dist/react/chunks/ja-CXjz9Ptf.mjs +2 -0
- package/dist/react/chunks/react-BW__KLHg.mjs +2 -0
- package/dist/react/chunks/react-BoHqKQID.cjs +2 -0
- package/dist/react/chunks/zh-BBVxXvHQ.mjs +2 -0
- package/dist/react/chunks/zh-BtxE6oq5.cjs +2 -0
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +189 -6
- package/dist/react/index.mjs +1 -1
- package/docs/ARCHITECTURE.md +7 -6
- package/docs/ERROR_CODES.md +35 -0
- package/docs/MCP-AND-AGENT-READINESS.md +1 -1
- package/docs/SERVER-CONTENT.md +10 -5
- package/package.json +2 -1
- package/dist/react/chunks/SettingsDialog-B0s01DfC.cjs +0 -2
- package/dist/react/chunks/SettingsDialog-BDzOpoTW.mjs +0 -2
- package/dist/react/chunks/ar-4O9xrkV6.mjs +0 -2
- package/dist/react/chunks/ar-Dcph0Rl-.cjs +0 -2
- package/dist/react/chunks/de-D0wYkKfr.cjs +0 -2
- package/dist/react/chunks/de-ITC4I4gE.mjs +0 -2
- package/dist/react/chunks/es-D4d7IQzB.cjs +0 -2
- package/dist/react/chunks/es-DDUCoxrg.mjs +0 -2
- package/dist/react/chunks/fr-BomsBzOa.cjs +0 -2
- package/dist/react/chunks/fr-BsWwwise.mjs +0 -2
- package/dist/react/chunks/hi-BmpRGcI3.cjs +0 -2
- package/dist/react/chunks/hi-D5uXZGQc.mjs +0 -2
- package/dist/react/chunks/ja-D5amIGZT.cjs +0 -2
- package/dist/react/chunks/ja-DkMcdOxv.mjs +0 -2
- package/dist/react/chunks/react-Cs1KN_wQ.mjs +0 -2
- package/dist/react/chunks/react-DWVzbgNV.cjs +0 -2
- package/dist/react/chunks/zh-BRYcvEMm.cjs +0 -2
- package/dist/react/chunks/zh-C8cgAVXA.mjs +0 -2
package/AGENTS.md
CHANGED
|
@@ -82,7 +82,7 @@ export const {
|
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
Express / no cookie context: omit `getSessionId`, call `withSession(sessionId)` per request.
|
|
85
|
-
Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err, ctx)`, `fetch
|
|
85
|
+
Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err, ctx)`, `fetch`, `region` (informational, read back as `client.region`; type `Region`).
|
|
86
86
|
|
|
87
87
|
## Export map (what lives where)
|
|
88
88
|
|
|
@@ -92,7 +92,7 @@ Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err,
|
|
|
92
92
|
- **API classes:** `BaseApi` (extend for custom APIs), `AuthApi`, `UserApi`, `WorkspaceApi`, `SettingsApi`, `PushApi`.
|
|
93
93
|
- **Webhook verification (runtime-agnostic — Node, edge, Deno, Bun, browsers):** `verifyWebhookSignature`, `parseWebhookEvent`. Verify against the **raw** request body (`await req.text()`), not the parsed JSON. Headers: `x-buildbase-signature` (`sha256=` HMAC over `{timestamp}.{body}`), `x-buildbase-timestamp` (Unix seconds), `x-buildbase-event`.
|
|
94
94
|
- **Webhook events:** `WEBHOOK_EVENTS` (112 `domain.action` names), `WEBHOOK_EVENTS_BY_CATEGORY`, `WebhookEventName`, `isWebhookEvent(name)`, vendored from the platform catalog. A failed payment is `payment.failed`, with `workspaceId, subscriptionId, invoiceId, dunningState, amount, currency, failedAt, nextRetryAt`. Full list: https://docs.buildbase.app/webhooks/overview. Never invent an event name; narrow with `isWebhookEvent` instead.
|
|
95
|
-
- **Agent readiness (framework-agnostic, returns plain `DiscoveryDocument`):** **App owns discovery content, platform owns auth.** All content — `llms.txt`, `robots.txt`, `sitemap`, `protectedResources` (RFC 9728), API catalog, skills — is defined locally in `AgentReadyConfig` and served with no platform round-trip. The platform bundle (`fetchAgentReadiness`, fail-soft/cached) supplies ONLY `{ enabled, authorizationServer }` (the OAuth AS pointer). Functions: `resolveAgentPath` (dispatch ALL agent paths — root docs + `.well-known/*`; prefer for new wiring), `resolveWellKnown` (`.well-known/*` only), `clearAgentReadinessCache`, `buildAgentCard`, `buildA2AAgentCard` (A2A card at `/.well-known/agent-card.json`; served by DEFAULT, derived from `site`+`skills`; `a2aCard: false` disables, object overrides), `buildProtectedResourceMetadata(path, config)` (local), `buildAgentSkillsIndex`, `buildSkillMd`, `buildSecurityTxt`, `buildLlmsTxt(config)` (from `config.llmsTxt`), `buildLlmsFullTxt` (from `config.llmsFullTxt`), `buildRobotsTxt`, `buildSitemap`, `buildApiCatalog`, `buildMcpServerCard`, `buildAuthMd`, `buildWebBotAuthDirectory` (JWKS at `/.well-known/http-message-signatures-directory` from `config.webBotAuth.keys`; opt-in), `buildDnsAidRecords` (suggested `_agents` DNS records — publish at your DNS provider, can't be served over HTTP), `buildDiscoveryLinkHeader`, `wantsMarkdown`/`negotiateMarkdown`, `sha256Digest`, `AI_BOT_USER_AGENTS`. `config.extraPaths` serves ANY literal document by exact path (commerce discovery: x402/UCP/ACP/MPP, `/openapi.json`, future `.well-known` docs) and takes precedence over built-ins. Types: `AgentReadyConfig`, `AgentReadinessBundle`, `AgentSkill`, `ApiCatalogApi`, `A2ACardConfig`, `A2ACardSkill`, `DnsAidRecord`, `McpServerCard`, `RobotsConfig`, `RobotsPolicy`, `ContentSignals`, `SitemapUrl`, `DiscoveryDocument`.
|
|
95
|
+
- **Agent readiness (framework-agnostic, returns plain `DiscoveryDocument`):** **App owns discovery content, platform owns auth.** All content — `llms.txt`, `robots.txt`, `sitemap`, `protectedResources` (RFC 9728), API catalog, skills — is defined locally in `AgentReadyConfig` and served with no platform round-trip. The platform bundle (`fetchAgentReadiness`, fail-soft/cached) supplies ONLY `{ enabled, authorizationServer }` (the OAuth AS pointer). Functions: `resolveAgentPath` (dispatch ALL agent paths — root docs + `.well-known/*`; prefer for new wiring), `resolveWellKnown` (`.well-known/*` only), `clearAgentReadinessCache`, `buildAgentCard`, `buildA2AAgentCard` (A2A card at `/.well-known/agent-card.json`; served by DEFAULT, derived from `site`+`skills`; `a2aCard: false` disables, object overrides), `buildProtectedResourceMetadata(path, config)` (local), `buildAgentSkillsIndex`, `buildSkillMd`, `buildSecurityTxt`, `buildLlmsTxt(config)` (from `config.llmsTxt`), `buildLlmsFullTxt` (from `config.llmsFullTxt`), `buildRobotsTxt`, `buildSitemap`, `buildApiCatalog`, `buildMcpServerCard`, `buildAuthMd`, `buildWebBotAuthDirectory` (JWKS at `/.well-known/http-message-signatures-directory` from `config.webBotAuth.keys`; opt-in), `buildDnsAidRecords` (suggested `_agents` DNS records — publish at your DNS provider, can't be served over HTTP), `buildDiscoveryLinkHeader`, `wantsMarkdown`/`negotiateMarkdown`, `sha256Digest`, `AI_BOT_USER_AGENTS`, `DISCOVERY_CACHE_TIMEOUT_MS`. `config.extraPaths` serves ANY literal document by exact path (commerce discovery: x402/UCP/ACP/MPP, `/openapi.json`, future `.well-known` docs) and takes precedence over built-ins. `config.cache` (a `DiscoveryCacheStore`: string `get`/`set`, optional `delete`) shares the platform fetches across replicas instead of caching per process; the default is process memory. Types: `AgentReadyConfig`, `AgentReadinessBundle`, `AgentSkill`, `DiscoveryCacheStore`, `ApiCatalogApi`, `A2ACardConfig`, `A2ACardSkill`, `DnsAidRecord`, `McpServerCard`, `RobotsConfig`, `RobotsPolicy`, `ContentSignals`, `SitemapUrl`, `DiscoveryDocument`.
|
|
96
96
|
- **OAuth2 app-bridge:** `handleAppTokenRequest`, `handleAppRevokeRequest` (one-call handlers), `bearerChallenge` (RFC 9728/6750 401; header values are quoted-string-escaped + CR/LF-stripped), `verifyAppTokenRequest`, `verifyAppRevokeRequest`, `verifyClientJwt`, `signClientJwt` (HS256 mint — symmetric counterpart of verify), `extractBearerToken`, `appTokenSuccess`, `appTokenFailure`, `AppBridgeError`. Types: `AppTokenRequestClaims`, `AppRevokeRequestClaims`, `AppTokenResult`, `AppTokenResponseBody`, `HandlerResult`, `VerifyClientJwtOptions`. `verifyClientJwt(token, secret, opts?: VerifyClientJwtOptions)` **requires a numeric `exp` by default** (`requireExp: false` to allow non-expiring tokens); optional `clockToleranceSec` / `issuer` / `audience`. **Presets** (also in core): `mintAgentToken` (the whole mint in one call — HS256 with the app's secret, `aud` from the grant's resource, encrypted `sid`) + `buildbaseAuth` (the whole verify) + `createSessionRefCrypto`. Security invariant: the platform relays the minted token but never sees the app secret — it can neither forge nor decode app tokens, and the BuildBase session is never stored and never plaintext in a token.
|
|
97
97
|
- **Currency & pricing utils (pure):** `formatCents`, `formatOverageRate`, `formatOverageRateWithLabel`, `formatQuotaIncludedOverage`, `formatQuotaWithPrice`, `getCurrencySymbol`, `getCurrencyFlag`, `getQuotaUnitLabelFromName`, `getQuotaDisplayValue`, `CURRENCY_DISPLAY`, `CURRENCY_FLAG`, `PLAN_CURRENCY_CODES`, `PLAN_CURRENCY_OPTIONS`.
|
|
98
98
|
- **Pricing variants (multi-currency):** `getPricingVariant`, `getBasePriceCents`, `getStripePriceIdForInterval`, `getQuotaOverageCents`, `getQuotaDisplayWithVariant`, `getAvailableCurrenciesFromPlans`, `getDisplayCurrency`, `getBillingIntervalAndCurrencyFromPriceId`.
|
|
@@ -100,16 +100,19 @@ Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err,
|
|
|
100
100
|
- **RBAC (pure):** `hasPermission`, `resolvePermissions`, `resolveMaxUsers`, `validateInvite`, `DEFAULT_ROLE_PERMISSIONS`, `Permission` (enum).
|
|
101
101
|
- **URL / auth-intent / redirects:** `BB_PARAM`, `BBAction`, `BBScreen`, `BBStatus`, `createBBUrl`, `readBBParams`, `cleanBBParams`, `createCheckoutRedirectUrls`, `createCreditPurchaseRedirectUrls`, `safeRedirect`, `validateRedirectUrl`, `saveAuthIntent`, `consumeAuthIntent`, `clearAuthIntent`.
|
|
102
102
|
- **Context invalidation (call after server mutations to refetch client contexts):** `invalidateSubscription`, `invalidateQuotaUsage`, `invalidateCreditBalance`.
|
|
103
|
+
- **Regional resolution (server-side, for apps hosting many orgs):** `resolveServerUrl({ orgId, centralUrl, apiKey, cache? })` asks central which cell an organisation lives in and caches the answer for central's own `max-age`; `clearResolvedServerUrl({ orgId, centralUrl, cache? })` forgets it, which is the documented recovery from a `421 misdirected_request`. Types: `RegionAnswer`, `ResolveServerUrlOptions`, `ResolveCacheStore`. Throws on failure rather than falling back to the default cell, because that fallback would send an EU organisation's request to the US. **Pass `apiKey`**: without it central answers the default cell for every id, known or not, on purpose (organisation ids are ObjectIds, so a distinguishable 404 would be an enumeration oracle).
|
|
103
104
|
- **Events:** `EventEmitter`, `eventEmitter` (singleton), `SDKEvent` (enum).
|
|
104
105
|
- **Workspace invitations (pure):** `INVITATION_ERROR_CODE`, `isInvitationError`, `sortInvitations`, `actionableInvitations`, `isInvitationActionable`, `isInvitationLapsed`, `resendCooldownSeconds`, `parkPendingInvite`, `readPendingInvite`, `clearPendingInvite`; server factory namespace `invitations` (`list`, `create`, `resend`, `revoke`).
|
|
105
106
|
- **Push:** `PushApi`, `PUSH_SERVICE_WORKER_SCRIPT`.
|
|
107
|
+
- **Uploads (an end user's image, counted as the org's storage):** `UploadsApi` (`upload(file, { purpose, signal? })`: intent, signed PUT straight to storage with a 2-minute timeout, complete), `UPLOAD_ACCEPT`, `UPLOAD_MAX_BYTES` (the platform's 5 MB; the org may set less), `uploadFailureOf(error)` (a refusal's reason - `tooLarge`, `invalid`, `disabled`, `userLimit`, `unavailable`, `failed` - for translated copy; the thrown message is the server's English sentence), `uploadPolicyOf(settings)` (the org's `{ enabled, maxFileBytes }` from `settings.uploads`), `preflightUpload(file, policy)` (the refusal known before a request), `formatUploadLimit(bytes, locale)`. Types: `IUploadedFile`, `UploadFailure`, `UploadOptions`, `UploadPolicy`, `UploadPurpose` (`'workspace-image' | 'profile-image'`). Server-side: `client.forSession(id).uploads`. A profile picture is saved with `updateUserProfile({ image: file.url })`.
|
|
106
108
|
- **Notification inbox:** `NotificationsApi` (`list`, `counts`, `markRead`, `markUnread`, `archive`, `unarchive`, `markAllRead`, `markSeen`).
|
|
107
109
|
- **Enums:** `ApiVersion`, `AuthStatus`, `BillingIntervals`, `CreditBucketSource`, `CreditBucketStatus`, `CreditTransactionType`, `DunningState`, `InvoiceStatuses`, `SubscriptionItemType`, `SubscriptionStatus`.
|
|
108
110
|
|
|
109
111
|
### `@buildbase/sdk/react` — client-only additions (core is re-exported here too)
|
|
110
112
|
|
|
111
113
|
- **Providers:** `SaaSOSProvider` (root), `SubscriptionContextProvider`, `QuotaUsageContextProvider`, `CreditBalanceContextProvider`, `PushNotificationProvider`.
|
|
112
|
-
- **State hooks:** `useSaaSAuth`, `useSaaSWorkspaces`, `useSaaSOs
|
|
114
|
+
- **State hooks:** `useSaaSAuth`, `useSaaSWorkspaces`, `useSaaSOs` (exposes `region` when the app passes it to `SaaSOSProvider`; informational, for badges and support tooling), `useSaaSSettings`, `useUserAttributes`, `useUserFeatures`, `useTranslation`, `usePermissions`, `useSeatStatus`, `useTrialStatus`, `useFullScreenLoader`, `useMyInvitations`, `useWorkspaceInvitations`, `useNotifications`.
|
|
115
|
+
- **Upload hooks:** `useUpload(purpose)` (`{ upload, cancel, loading, error, failure, enabled, maxBytes, maxLabel }`; reads the org's upload settings, refuses a file over its limit before a request, `error` is translated into the active locale and `failure` is the reason; a new upload, `cancel()` or unmount aborts the one in flight), `useUploadsApi`.
|
|
113
116
|
- **Subscription/billing hooks:** `usePublicPlans`, `useSubscription`, `useSubscriptionContext`, `useSubscriptionManagement`, `usePlanGroup`, `usePlanGroupVersions`, `usePublicPlanGroupVersion`, `useCreateCheckoutSession`, `useUpdateSubscription`, `useCancelSubscription`, `useResumeSubscription`, `useBillingPortal`, `useInvoices`, `useInvoice`.
|
|
114
117
|
- **Quota hooks:** `useQuotaUsageContext`, `useRecordUsage`, `useQuotaUsageStatus`, `useAllQuotaUsage`, `useUsageLogs`.
|
|
115
118
|
- **Credit hooks:** `useCreditBalance`, `useCreditBalanceContext`, `useConsumeCredits`, `usePurchaseCredits`, `useCreditPackages`, `usePublicCreditPackages`, `useCreditTransactions`, `useExpiringCredits`.
|
|
@@ -167,6 +170,11 @@ Expose a live MCP server (stateless Streamable HTTP; Node 18+/edge) with built-i
|
|
|
167
170
|
- **Agent-readiness fetches are fail-soft** — a platform outage yields `{ enabled: false }`, never a 500. Don't add your own error handling expecting throws.
|
|
168
171
|
- **New crypto is dependency-free pure JS** (`src/lib/sha256.ts`) so it behaves identically on browser/edge/Node — don't swap in Web Crypto / Node `crypto` without matching outputs.
|
|
169
172
|
- **Prefer SDK hooks** (`useSaaS*`, `useSubscription*`, etc.) over `useAppSelector`/internal context so consumer code stays stable across internal refactors.
|
|
173
|
+
- **Pass `idempotencyKey` on `usage.record`, `usage.recordBatch` and `credits.consume`.** It is optional in the type and should be treated as required in anything that meters or spends. The SDK retries only idempotent requests, so a `POST` that fails mid-flight is not retried for you: the caller retries, and without a key the second attempt bills twice. Use a key derived from the unit of work (`gen-<requestId>`, `email-<messageId>`), not a fresh UUID per attempt. This matters more against a load-balanced API with several replicas than against one server, because a retry landing on a different replica is the normal case.
|
|
174
|
+
- **`serverUrl` must be the organisation's REGIONAL URL.** Today every organisation is on the default cell, `https://api.console.buildbase.app`; an organisation in another region uses the URL central answers for it, never a guessed hostname. There is no auto-discovery in the client bundle and there will not be: resolving in the browser costs a round trip before the first API call on every page load. An app serving one organisation bakes the URL into the build; one serving many resolves per request on the server with `resolveServerUrl`. Sending an organisation's request to the wrong cell returns `421 misdirected_request` with the right `serverUrl`, never a silent cross-region read - treat it as the signal to re-resolve once.
|
|
175
|
+
- **The MCP server card and protected-resource metadata advertise the regional endpoint.** `createAgentStack` derives both from `serverUrl`, so getting `serverUrl` right is the whole of it; there is nothing extra to configure per cell. An agent that discovered a US endpoint for an EU organisation would authenticate against the wrong cell and be refused by the audience check.
|
|
176
|
+
- **`region` is informational.** `BuildBase({ region })`/`client.region` and `SaaSOSProvider region=` are for logs, metrics and UI badges. What decides which cell answers is `serverUrl`, and on the server the audience the token is bound to (`buildbaseAuth({ resource })`). A wrong `region` mislabels a badge; it cannot misroute a request.
|
|
177
|
+
- **Agent-readiness caching is per process by default.** `fetchAgentReadiness` and the authorization-server metadata fetch keep their results in process memory, so N replicas each fetch their own copy and `clearAgentReadinessCache()` clears only the process it runs in. Pass `cache` (a `DiscoveryCacheStore`, same idea as the content client's `CacheStore`) to share one copy across replicas. A store that throws, or takes longer than `DISCOVERY_CACHE_TIMEOUT_MS` (500 ms), degrades to a cache miss, never to a failed or hung discovery response.
|
|
170
178
|
|
|
171
179
|
## Working on this repo
|
|
172
180
|
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,101 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.0.84] - 2026-10-17
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- `docs/SERVER-CONTENT.md` names the console's menus as they read now: **Settings > API keys**, **Settings > Webhooks** and **Settings > Deploy hooks** (it said "API tokens" and "Deployments"). The build-time pull section says how a console deploy starts (from the publish banner or **Deploy** on a hook, not on every publish) and links the deploy hooks setup guide at docs.buildbase.app.
|
|
13
|
+
- The README's tracking section names **Settings > Tracking & tags**.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Uploads.** `useUpload(purpose)` and `UploadsApi` let a signed-in user
|
|
18
|
+
upload an image - a workspace's or a profile picture - straight to storage
|
|
19
|
+
on a signed URL: the server checks type, size and the organization's
|
|
20
|
+
storage before a byte is sent, and the file it stores counts toward the
|
|
21
|
+
organization's storage like any other. Images only, up to 5 MB, or the
|
|
22
|
+
lower limit the organization sets.
|
|
23
|
+
- `useUpload` returns
|
|
24
|
+
`{ upload, cancel, loading, error, failure, enabled, maxBytes, maxLabel }`.
|
|
25
|
+
- It reads the organization's upload settings (`settings.uploads`:
|
|
26
|
+
`{ enabled, maxFileBytes }`) and refuses a file over the limit, or any
|
|
27
|
+
file while uploads are off, before a request. A server that does not send
|
|
28
|
+
`uploads` has no end-user uploads, so they are off there, and the upload
|
|
29
|
+
and picture controls do not show.
|
|
30
|
+
- `error` is translated into the active locale (`uploads.*` keys in all
|
|
31
|
+
eight locales, `{max}` filled with the limit); `failure` names the reason.
|
|
32
|
+
- The upload in flight is aborted on a new one, on `cancel()` and on
|
|
33
|
+
unmount, and the PUT to storage has its own two-minute timeout.
|
|
34
|
+
- `uploadFailureOf`, `uploadPolicyOf`, `preflightUpload`,
|
|
35
|
+
`formatUploadLimit` and the type `UploadPolicy` are exported for an app's
|
|
36
|
+
own UI. Server-side, `client.forSession(id).uploads` is the same client.
|
|
37
|
+
- **The workspace settings screen gains an Upload image option** next to the
|
|
38
|
+
emoji and URL ones, shown only when the organization allows uploads and
|
|
39
|
+
stating its limit; **the profile screen gains a picture**: upload one,
|
|
40
|
+
remove it, saved with the profile (`updateUserProfile({ image })`, which
|
|
41
|
+
needs a server that accepts `image` on `PATCH /profile`). New keys
|
|
42
|
+
`general.uploadImage`, `general.chooseImage`, `profile.changePicture`,
|
|
43
|
+
`profile.removePicture` and the `uploads` namespace, in all eight locales.
|
|
44
|
+
- **Regional resolution, for apps that host many organisations.**
|
|
45
|
+
`resolveServerUrl({ orgId, centralUrl, apiKey, cache? })` asks central which
|
|
46
|
+
cell an organisation lives in and caches the answer for central's own
|
|
47
|
+
`max-age`; `clearResolvedServerUrl` forgets it, the recovery from a
|
|
48
|
+
`421 misdirected_request`. It throws rather than falling back to the default
|
|
49
|
+
cell. Pass `apiKey`: without it central answers the default cell for every
|
|
50
|
+
id, and that answer (sent `no-store`) is never cached. Types `RegionAnswer`,
|
|
51
|
+
`ResolveServerUrlOptions`, `ResolveCacheStore`.
|
|
52
|
+
Needs a central server that serves `GET /api/organizations/orgs/:orgId/region`.
|
|
53
|
+
- **`region` on `BuildBase({ region })`, `client.region` and
|
|
54
|
+
`SaaSOSProvider region=`** (read back from `useSaaSOs()`). Informational
|
|
55
|
+
only, for logs and badges: it is never sent and never changes which cell
|
|
56
|
+
answers; `serverUrl` does.
|
|
57
|
+
- **A shared cache for agent-readiness discovery.** `AgentReadyConfig.cache`
|
|
58
|
+
takes a `DiscoveryCacheStore` (string `get`/`set`, optional `delete`) so
|
|
59
|
+
replicas share the platform fetches instead of each caching in process
|
|
60
|
+
memory, which stays the default. A store that throws, or takes longer than
|
|
61
|
+
`DISCOVERY_CACHE_TIMEOUT_MS` (500 ms, exported), is treated as a cache
|
|
62
|
+
miss, so a hung store cannot hang a discovery response.
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- **Types:** `SDKMessages` gains the `uploads` namespace and
|
|
67
|
+
`profile.changePicture` / `profile.removePicture`, and `ISettings` gains an
|
|
68
|
+
optional `uploads`. `messageOverrides` takes `PartialSDKMessages`, so
|
|
69
|
+
existing overrides keep compiling; only code that builds a complete
|
|
70
|
+
`SDKMessages` object by hand needs the new keys.
|
|
71
|
+
- `package-lock.json` carries the package's version again.
|
|
72
|
+
|
|
73
|
+
## [0.0.83] - 2026-10-14
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- **⚠️ The push service worker now shows rich notifications, and a `tag`
|
|
78
|
+
replaces instead of stacking.** `PUSH_SERVICE_WORKER_SCRIPT` showed only a
|
|
79
|
+
title, body and icon (the icon doubling as the badge), and gave every
|
|
80
|
+
message its own `buildbase-push-<timestamp>` tag. The server sends image,
|
|
81
|
+
badge, action buttons, `tag`, `requireInteraction`, `silent`, `renotify`,
|
|
82
|
+
`timestamp` and `dir`, and the worker dropped all of them, so a notification
|
|
83
|
+
never kept its image or buttons, never stayed on screen when asked to, and
|
|
84
|
+
could not replace an earlier one with the same tag. All of them now reach the
|
|
85
|
+
browser. A sent `badge` is used and the icon is still the fallback,
|
|
86
|
+
`renotify` is only set with a tag (the browser refuses it without one), and
|
|
87
|
+
an unknown `dir` or a malformed action is dropped rather than failing the
|
|
88
|
+
notification. If the browser still refuses an option, the worker shows the
|
|
89
|
+
notification again with only its title, body, icon, tag and click link. The
|
|
90
|
+
tracked click URL now wins over a `url` inside `payload.data`, so that `url`
|
|
91
|
+
can no longer bypass click tracking; a `data.url` is still opened when the
|
|
92
|
+
payload has no click URL. A click on an action button opens the same link as
|
|
93
|
+
a click on the notification. Apps that write `public/push-sw.js` from the
|
|
94
|
+
export at build time get this on upgrade.
|
|
95
|
+
- **The README's push section no longer carries its own copy of the worker.**
|
|
96
|
+
It now shows the same build step as the docs,
|
|
97
|
+
`fs.writeFileSync('public/push-sw.js', PUSH_SERVICE_WORKER_SCRIPT)`. The copy
|
|
98
|
+
had the rich options the shipped script lacked but, like the shipped script,
|
|
99
|
+
let `payload.data.url` override the tracked click URL. An app that pasted it,
|
|
100
|
+
or any other hand-made copy of the worker, should switch to writing the
|
|
101
|
+
export at build time; a copy does not change when the SDK is upgraded.
|
|
102
|
+
|
|
8
103
|
## [0.0.82] - 2026-10-12
|
|
9
104
|
|
|
10
105
|
### Fixed
|
package/README.md
CHANGED
|
@@ -497,7 +497,7 @@ The returned object is merged into the Stripe checkout session. You can return a
|
|
|
497
497
|
|
|
498
498
|
## 📈 Tracking & Tags
|
|
499
499
|
|
|
500
|
-
The tags you configure in the BuildBase console - under **Settings
|
|
500
|
+
The tags you configure in the BuildBase console - under **Settings > Tracking & tags**, attached to an auth client - installed in your app, with the conversions BuildBase already observes fired for you. No tag ids in your code: a marketing site and a product app carry different tags because they are different auth clients.
|
|
501
501
|
|
|
502
502
|
### Turn it on
|
|
503
503
|
|
|
@@ -840,52 +840,29 @@ function TrialInfo() {
|
|
|
840
840
|
|
|
841
841
|
## 🔔 Push Notifications
|
|
842
842
|
|
|
843
|
-
Browser push notifications
|
|
843
|
+
Browser push notifications - built into the SDK. Users can enable/disable from the **Notifications** tab in workspace settings. Billing events (payment failed, trial ending) auto-send push notifications.
|
|
844
844
|
|
|
845
|
-
**Only setup required:**
|
|
845
|
+
**Only setup required:** serve the SDK's service worker at `/push-sw.js`. It ships as the `PUSH_SERVICE_WORKER_SCRIPT` string, exported from both `@buildbase/sdk` and `@buildbase/sdk/react`. Write it into your public directory at build time, so it updates with the SDK:
|
|
846
846
|
|
|
847
847
|
```js
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
var payload = event.data.json();
|
|
852
|
-
var options = {
|
|
853
|
-
body: payload.body || '',
|
|
854
|
-
icon: payload.icon || undefined,
|
|
855
|
-
badge: payload.badge || payload.icon || undefined,
|
|
856
|
-
image: payload.image || undefined,
|
|
857
|
-
tag: payload.tag || undefined,
|
|
858
|
-
actions: payload.actions || undefined,
|
|
859
|
-
silent: payload.silent || false,
|
|
860
|
-
requireInteraction: payload.requireInteraction || false,
|
|
861
|
-
renotify: payload.renotify || false,
|
|
862
|
-
timestamp: payload.timestamp || undefined,
|
|
863
|
-
dir: payload.dir || 'auto',
|
|
864
|
-
data: { url: payload.url, ...(payload.data || {}) },
|
|
865
|
-
};
|
|
866
|
-
event.waitUntil(self.registration.showNotification(payload.title || 'Notification', options));
|
|
867
|
-
} catch (e) {
|
|
868
|
-
console.error('[PushSW]', e);
|
|
869
|
-
}
|
|
870
|
-
});
|
|
848
|
+
// scripts/write-push-sw.mjs
|
|
849
|
+
import fs from 'node:fs';
|
|
850
|
+
import { PUSH_SERVICE_WORKER_SCRIPT } from '@buildbase/sdk';
|
|
871
851
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
if (list[i].url === url && 'focus' in list[i]) return list[i].focus();
|
|
880
|
-
}
|
|
881
|
-
if (clients.openWindow) return clients.openWindow(url);
|
|
882
|
-
})
|
|
883
|
-
);
|
|
852
|
+
fs.writeFileSync('public/push-sw.js', PUSH_SERVICE_WORKER_SCRIPT);
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
```json
|
|
856
|
+
{
|
|
857
|
+
"scripts": {
|
|
858
|
+
"prebuild": "node scripts/write-push-sw.mjs"
|
|
884
859
|
}
|
|
885
|
-
}
|
|
860
|
+
}
|
|
886
861
|
```
|
|
887
862
|
|
|
888
|
-
|
|
863
|
+
The worker shows everything the server sends: title, body, icon, badge, image, action buttons, `requireInteraction`, `silent`, `renotify`, `timestamp` and `dir`. A notification with a `tag` replaces the earlier one with the same tag instead of stacking. A click, on the notification or on one of its buttons, opens the notification's tracked link, so clicks are counted. Do not hand-write a copy of the worker: a copy does not pick up fixes when you upgrade the SDK.
|
|
864
|
+
|
|
865
|
+
Everything else is built-in - permission handling, subscribe/unsubscribe, settings UI, billing auto-triggers, and browser-specific unblock instructions.
|
|
889
866
|
|
|
890
867
|
## 📬 Notifications
|
|
891
868
|
|
|
@@ -1401,6 +1378,83 @@ await bb.sessions.list(); // ISessionView[]
|
|
|
1401
1378
|
await bb.sessions.revoke(sessionId); // by the session's public handle
|
|
1402
1379
|
```
|
|
1403
1380
|
|
|
1381
|
+
## 🖼️ Uploads
|
|
1382
|
+
|
|
1383
|
+
Your users can upload an image - a workspace's, a profile picture - without
|
|
1384
|
+
it touching your server or ours. The SDK asks for an upload, the file goes
|
|
1385
|
+
straight to storage on a signed URL sized to that one file, and the server
|
|
1386
|
+
checks what landed before it becomes a file. Images only (PNG, JPEG, GIF,
|
|
1387
|
+
WebP, SVG), up to 5 MB, or less if you set less.
|
|
1388
|
+
|
|
1389
|
+
Every upload counts toward **your organization's storage**, like a file you
|
|
1390
|
+
upload in the console, and shows up in your assets with the user as its
|
|
1391
|
+
creator. You decide what your users may upload in the BuildBase console
|
|
1392
|
+
(Billing settings, Storage, **End-user uploads**): uploads on or off, the
|
|
1393
|
+
largest file (100 KB to 5 MB), and optionally a total per user. The SDK reads
|
|
1394
|
+
the first two from your organization's settings, so it offers an upload only
|
|
1395
|
+
when one can succeed and states your limit.
|
|
1396
|
+
|
|
1397
|
+
The built-in settings screens use this: the workspace screen's **Upload image**
|
|
1398
|
+
option, and the profile screen's **Upload picture**, which saves the picture
|
|
1399
|
+
on the user's profile.
|
|
1400
|
+
|
|
1401
|
+
```tsx
|
|
1402
|
+
import { UPLOAD_ACCEPT, useSaaSWorkspaces, useUpload } from '@buildbase/sdk/react';
|
|
1403
|
+
|
|
1404
|
+
function AvatarPicker() {
|
|
1405
|
+
const { upload, loading, error, enabled, maxLabel } = useUpload('profile-image');
|
|
1406
|
+
const { updateUserProfile } = useSaaSWorkspaces();
|
|
1407
|
+
if (!enabled) return null; // your organization turned uploads off
|
|
1408
|
+
return (
|
|
1409
|
+
<>
|
|
1410
|
+
<input
|
|
1411
|
+
type="file"
|
|
1412
|
+
accept={UPLOAD_ACCEPT}
|
|
1413
|
+
disabled={loading}
|
|
1414
|
+
onChange={async e => {
|
|
1415
|
+
const file = e.target.files?.[0];
|
|
1416
|
+
if (!file) return;
|
|
1417
|
+
// `error` says why, translated, when it rejects.
|
|
1418
|
+
const stored = await upload(file).catch(() => null);
|
|
1419
|
+
if (stored) await updateUserProfile({ image: stored.url });
|
|
1420
|
+
}}
|
|
1421
|
+
/>
|
|
1422
|
+
<p>Up to {maxLabel}.</p>
|
|
1423
|
+
{error && <p role="alert">{error}</p>}
|
|
1424
|
+
</>
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
```
|
|
1428
|
+
|
|
1429
|
+
`upload` resolves with `{ id, name, size, mimeType, url, width, height }`;
|
|
1430
|
+
`url` is public. A file over your limit, or any file while uploads are off, is
|
|
1431
|
+
refused before a request is sent. A refusal from the server rejects with its
|
|
1432
|
+
`status` and `code`: `413` `UPLOAD_TOO_LARGE`, `403` `UPLOADS_DISABLED` or
|
|
1433
|
+
`USER_UPLOAD_LIMIT` (the user's total is full), `429` for too many uploads at
|
|
1434
|
+
once, `503` `UPLOADS_UNAVAILABLE` when your organization cannot take files
|
|
1435
|
+
right now - its storage is full, say - and `501` when the server has no direct
|
|
1436
|
+
uploads configured. See `docs/ERROR_CODES.md`.
|
|
1437
|
+
|
|
1438
|
+
- **`useUpload(purpose)`** - `{ upload, cancel, loading, error, failure, enabled, maxBytes, maxLabel }`.
|
|
1439
|
+
`error` is translated into the active locale; `failure` is the reason
|
|
1440
|
+
(`tooLarge`, `invalid`, `disabled`, `userLimit`, `unavailable` or `failed`)
|
|
1441
|
+
for a screen that words it itself. One upload at a time: a new one, `cancel()`
|
|
1442
|
+
or unmounting aborts the one in flight, so a screen left mid-upload does not
|
|
1443
|
+
store a file nobody saves.
|
|
1444
|
+
- **`UploadsApi`** (+ `IUploadedFile`, `UploadPurpose`, `UploadOptions` with an
|
|
1445
|
+
optional `signal`) for direct calls, `useUploadsApi()` for a memoized one, and
|
|
1446
|
+
`client.forSession(sessionId).uploads` on the server.
|
|
1447
|
+
- **`uploadFailureOf(error)`**, **`uploadPolicyOf(settings)`**,
|
|
1448
|
+
**`preflightUpload(file, policy)`** and **`formatUploadLimit(bytes, locale)`**
|
|
1449
|
+
are the pieces `useUpload` is built from, for your own UI.
|
|
1450
|
+
- `UPLOAD_ACCEPT` is the `accept` list the server takes; `UPLOAD_MAX_BYTES` is
|
|
1451
|
+
the platform's 5 MB.
|
|
1452
|
+
- `purpose` is `'workspace-image'` or `'profile-image'`.
|
|
1453
|
+
|
|
1454
|
+
A workspace image sent as a `data:` URI larger than 16 KB is refused once your
|
|
1455
|
+
organization's storage limits are enforced; upload it instead and save its
|
|
1456
|
+
`url`.
|
|
1457
|
+
|
|
1404
1458
|
## 👤 User Management
|
|
1405
1459
|
|
|
1406
1460
|
### User Attributes
|
|
@@ -2064,6 +2118,47 @@ All endpoints (except `/token/exchange`) require the `x-session-id` header.
|
|
|
2064
2118
|
| `source` | `string` | No | Source identifier (e.g. `'backend'`, `'worker'`, `'cron'`) |
|
|
2065
2119
|
| `idempotencyKey` | `string` | No | Unique key for deduplication |
|
|
2066
2120
|
|
|
2121
|
+
> **`serverUrl` is the organisation's regional URL.** Today every organisation
|
|
2122
|
+
> is on the default cell, `https://api.console.buildbase.app`. When an
|
|
2123
|
+
> organisation lives in another region, its URL is the one central answers for
|
|
2124
|
+
> it (below), not one to guess. There is deliberately no auto-discovery
|
|
2125
|
+
> in the client bundle, because resolving in the browser would cost a round
|
|
2126
|
+
> trip before the first API call on every page load. If your app serves one
|
|
2127
|
+
> organisation, bake the URL in. If it serves many, resolve on the server:
|
|
2128
|
+
>
|
|
2129
|
+
> ```ts
|
|
2130
|
+
> import { resolveServerUrl } from '@buildbase/sdk';
|
|
2131
|
+
>
|
|
2132
|
+
> const { serverUrl } = await resolveServerUrl({
|
|
2133
|
+
> orgId,
|
|
2134
|
+
> centralUrl: process.env.BUILDBASE_CENTRAL_URL!,
|
|
2135
|
+
> apiKey: process.env.BUILDBASE_ORG_API_KEY!, // without it you get the default cell
|
|
2136
|
+
> });
|
|
2137
|
+
> const bb = BuildBase({ serverUrl, orgId });
|
|
2138
|
+
> ```
|
|
2139
|
+
>
|
|
2140
|
+
> It caches for as long as central says and throws rather than guessing: a
|
|
2141
|
+
> silent fallback to the default cell would send an EU organisation's request
|
|
2142
|
+
> to the US. A `421 misdirected_request` means the cached answer is stale
|
|
2143
|
+
> (usually a migration) - call `clearResolvedServerUrl` and retry once.
|
|
2144
|
+
>
|
|
2145
|
+
> `region` (`BuildBase({ serverUrl, orgId, region: 'eu' })`, read back as
|
|
2146
|
+
> `client.region`, and `SaaSOSProvider region="eu"`, read back from
|
|
2147
|
+
> `useSaaSOs().region`) is informational: it labels logs, metrics and badges.
|
|
2148
|
+
> `serverUrl` decides which cell answers; a wrong `region` cannot misroute a
|
|
2149
|
+
> request.
|
|
2150
|
+
|
|
2151
|
+
> **Pass `idempotencyKey` on anything that meters or spends.** It is optional in
|
|
2152
|
+
> the type, and you should treat it as required on `usage.record`,
|
|
2153
|
+
> `usage.recordBatch` and `credits.consume`. The SDK retries only idempotent
|
|
2154
|
+
> requests, so a `POST` that fails in flight is not retried for you: your code
|
|
2155
|
+
> retries, and without a key the second attempt bills the customer twice. Derive
|
|
2156
|
+
> the key from the unit of work (`gen-<requestId>`, `email-<messageId>`), never a
|
|
2157
|
+
> fresh UUID per attempt, or every retry looks like new usage. This matters more
|
|
2158
|
+
> once the API runs several replicas behind a load balancer, because a retry
|
|
2159
|
+
> landing on a different replica than the first attempt is then the normal case
|
|
2160
|
+
> rather than the exception.
|
|
2161
|
+
|
|
2067
2162
|
**Record usage response:**
|
|
2068
2163
|
|
|
2069
2164
|
| Field | Type | Description |
|
|
@@ -2578,6 +2673,7 @@ Prefer these SDK hooks for state and operations instead of `useAppSelector`:
|
|
|
2578
2673
|
| `useSaaSWorkspaces()` | Workspaces, currentWorkspace, loading, switching/switchingToId, CRUD and switch actions |
|
|
2579
2674
|
| `useSaaSOs()` | OS config (serverUrl, version, orgId, auth, settings) when you need the full config object |
|
|
2580
2675
|
| `useSaaSSettings()` | Organization settings — `{ settings, loading, error, refetch }` (prefer this when you only need settings) |
|
|
2676
|
+
| `useUpload(purpose)` | Upload an image within the organization's upload settings — `{ upload, cancel, loading, error, failure, enabled, maxBytes, maxLabel }` (see Uploads) |
|
|
2581
2677
|
| `useUserAttributes()` | User attributes — `{ attributes, loading, error, refetch, updateAttribute, updateAttributes }` |
|
|
2582
2678
|
| `useUserFeatures()` | User feature flags — `{ features, isFeatureEnabled, loading, error, refetch }` |
|
|
2583
2679
|
| `useConnectedAgents()` | Authorized AI agents — `{ agents, loading, error, revoking, refresh, revoke }` |
|
|
@@ -2619,6 +2715,7 @@ All TypeScript types are exported for type safety. See the [TypeScript definitio
|
|
|
2619
2715
|
| `orgId` | `string` | ✅ | Organization ID (must be valid MongoDB ObjectId - 24 hex characters) |
|
|
2620
2716
|
| `auth` | `IAuthConfig` | ❌ | Authentication configuration |
|
|
2621
2717
|
| `locale` | `SDKLocale` | ❌ | SDK UI language (`'en'`, `'es'`, `'fr'`, `'de'`, `'ja'`, `'zh'`, `'hi'`, `'ar'`) |
|
|
2718
|
+
| `region` | `Region` | ❌ | Informational: the organization's region, for logs and badges (`useSaaSOs().region`) |
|
|
2622
2719
|
| `defaultPermissions` | `Record<string, string[]>` | ❌ | Default app permissions per role |
|
|
2623
2720
|
| `getCheckoutStripeParams` | `GetCheckoutStripeParams` | ❌ | Async callback called before every Stripe checkout to return metadata, referral IDs, etc. |
|
|
2624
2721
|
| `children` | `ReactNode` | ✅ | React children |
|
|
@@ -3426,7 +3523,7 @@ app.use(async (req, res, next) => {
|
|
|
3426
3523
|
|
|
3427
3524
|
(`resolveWellKnown` remains available for `.well-known`-only wiring; `resolveAgentPath` is a superset.)
|
|
3428
3525
|
|
|
3429
|
-
`fetchAgentReadiness` is **fail-soft** — any network/HTTP/parse error resolves to `{ enabled: false }`, so a discovery route never 500s and never leaks that the platform is down; agents simply see the app as not (yet) agent-ready. Results are cached
|
|
3526
|
+
`fetchAgentReadiness` is **fail-soft** — any network/HTTP/parse error resolves to `{ enabled: false }`, so a discovery route never 500s and never leaks that the platform is down; agents simply see the app as not (yet) agent-ready. Results are cached for `cacheTtlSeconds` (default 300s), in process memory unless you pass `cache`: a `DiscoveryCacheStore` (`get`, `set`, optional `delete`, string values) backed by Redis, Cloudflare KV or Upstash, so every replica shares one copy. A store that throws, or takes longer than `DISCOVERY_CACHE_TIMEOUT_MS` (500 ms), counts as a miss, never as a failed or hung response. Call `clearAgentReadinessCache(store?)` to reset (mainly for tests); with a store it deletes the keys this process wrote.
|
|
3430
3527
|
|
|
3431
3528
|
### robots.txt, Content Signals & AI bots
|
|
3432
3529
|
|