@dotbots-boutique/auth-sdk 1.0.8 → 1.0.10

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/README.md CHANGED
@@ -115,7 +115,7 @@ App (frontend) → Local proxy (per server) → api.dotbots.ai
115
115
 
116
116
  During `initialize()`, the SDK fetches the proxy config from `GET {apiUrl}/api/proxy/config`. After that, all `auth.fetch()` calls are routed through the proxy automatically. If the proxy config cannot be fetched, the SDK falls back to direct communication with `apiUrl`.
117
117
 
118
- Auth endpoints (`/auth/*`) and the proxy config endpoint always go directly to `apiUrl` never through the proxy.
118
+ Only `GET /api/proxy/config` always goes directly to `apiUrl`. All other calls — auth token exchange, refresh, revoke, user info, payments, and `auth.fetch()` — go to `proxyUrl` when available, falling back to `apiUrl` when proxy is unavailable.
119
119
 
120
120
  ---
121
121
 
@@ -150,7 +150,43 @@ const response = await auth.fetch('/api/customers', {
150
150
  });
151
151
  ```
152
152
 
153
- This is the **only** method that routes via `proxyUrl`. All auth calls and the proxy config call always go directly to `apiUrl`.
153
+ All SDK calls route via `proxyUrl` when available. Only `GET /api/proxy/config` always goes to `apiUrl`.
154
+
155
+ ---
156
+
157
+ ## Payments
158
+
159
+ Use `auth.charge()` to charge a feature usage to the organisation or app budget. The app never handles token amounts, balances or budgets directly — the platform manages all of that.
160
+
161
+ ```typescript
162
+ const { transactionId } = await auth.charge('ai.generate', 'org');
163
+ ```
164
+
165
+ With a custom quantity:
166
+
167
+ ```typescript
168
+ const { transactionId } = await auth.charge('ai.generate', 'org', 5);
169
+ ```
170
+
171
+ ### Error handling
172
+
173
+ ```typescript
174
+ import { DotBotsAuthError } from '@dotbots-boutique/auth-sdk';
175
+
176
+ try {
177
+ const { transactionId } = await auth.charge('ai.generate', 'org', 1);
178
+ } catch (error) {
179
+ if (error instanceof DotBotsAuthError && error.code === 'PAYMENT_FAILED') {
180
+ if (error.message === 'INSUFFICIENT_BALANCE') {
181
+ // show top-up prompt
182
+ } else if (error.message === 'BUDGET_EXCEEDED') {
183
+ // show budget limit message
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ Possible `PAYMENT_FAILED` messages: `INSUFFICIENT_BALANCE`, `BUDGET_EXCEEDED`, `FEATURE_NOT_FOUND`, `PAYMENT_REJECTED`.
154
190
 
155
191
  ---
156
192
 
@@ -174,6 +210,7 @@ auth.on('sessionExpired', () => {
174
210
  });
175
211
  auth.on('loggedOut', () => { });
176
212
  auth.on('userLoaded', () => { });
213
+ auth.on('charged', () => { });
177
214
  ```
178
215
 
179
216
  | Event | Description |
@@ -182,6 +219,7 @@ auth.on('userLoaded', () => { });
182
219
  | `loggedOut` | User logged out |
183
220
  | `sessionExpired` | Refresh token expired, user must re-authenticate |
184
221
  | `userLoaded` | User data was fetched |
222
+ | `charged` | Successful charge — also sent as `DOTBOTS_CHARGE` postMessage to parent |
185
223
 
186
224
  ---
187
225
 
@@ -223,6 +261,7 @@ try {
223
261
  | `NETWORK_ERROR` | API unreachable | Yes |
224
262
  | `NOT_INITIALIZED` | `initialize()` has not been called yet | Yes |
225
263
  | `PROXY_UNAVAILABLE` | Proxy config could not be fetched | No — falls back to direct API |
264
+ | `PAYMENT_FAILED` | Payment was rejected (402) — see `message` for reason | No |
226
265
 
227
266
  ---
228
267
 
@@ -246,19 +285,17 @@ interface DotBotsConfig {
246
285
  ```typescript
247
286
  interface DotBotsUser {
248
287
  id: string;
249
- name: string | null; // null if 'profile' scope not granted
250
- email: string | null; // null if 'email' scope not granted
288
+ name: string;
289
+ email: string;
251
290
  orgId: string;
252
- orgName: string | null; // null if 'org' scope not granted
291
+ orgName: string;
253
292
  teams: { teamId: string; teamName: string }[];
254
293
  roles: string[];
255
294
  permissions: string[];
256
- avatarUrl: string | null; // null if 'avatar' scope not granted
295
+ avatarUrl?: string;
257
296
  }
258
297
  ```
259
298
 
260
- Fields marked as `null` are not provided by the platform because the user has not granted the corresponding scope.
261
-
262
299
  ---
263
300
 
264
301
  ## API Reference
@@ -301,6 +338,10 @@ Check if the user has a specific role.
301
338
 
302
339
  Authenticated fetch wrapper. Routes through the proxy when available, falls back to `apiUrl` when not. Retries once on 401 after refreshing the token.
303
340
 
341
+ #### `charge(featureCode: string, paidBy: 'org' | 'app', quantity?: number): Promise<{ transactionId: string }>`
342
+
343
+ Charges a feature usage. Calls `POST {proxyUrl}/payments/charge`. On success, sends a `DOTBOTS_CHARGE` postMessage to the parent window (iframe only) and emits the `charged` event. Throws `PAYMENT_FAILED` on 402 with a message indicating the reason (`INSUFFICIENT_BALANCE`, `BUDGET_EXCEEDED`, `FEATURE_NOT_FOUND`, `PAYMENT_REJECTED`).
344
+
304
345
  #### `logout(): Promise<void>`
305
346
 
306
347
  Logs out the user. Revokes tokens on `apiUrl`, clears state, and redirects (standalone) or notifies the parent (iframe).
@@ -325,6 +366,7 @@ const {
325
366
  canAny, // (permissions: string[]) => boolean
326
367
  hasRole, // (role: string) => boolean
327
368
  fetch, // auth.fetch proxied
369
+ charge, // auth.charge proxied
328
370
  logout, // auth.logout proxied
329
371
  } = useDotBotsAuth();
330
372
  ```
package/dist/cjs/index.js CHANGED
@@ -193,6 +193,14 @@ class PostMessageHandler {
193
193
  window.parent.postMessage({ type: 'DOTBOTS_LOGOUT' }, this.marketplaceOrigin);
194
194
  }
195
195
  }
196
+ /**
197
+ * Notify the parent of a successful charge (for real-time payment indicator).
198
+ */
199
+ sendCharge(appId, featureCode, amount, transactionId) {
200
+ if (this.isInIframe()) {
201
+ window.parent.postMessage({ type: 'DOTBOTS_CHARGE', appId, featureCode, amount, transactionId }, this.marketplaceOrigin);
202
+ }
203
+ }
196
204
  }
197
205
 
198
206
  class ProxyConfigManager {
@@ -325,6 +333,26 @@ class DotBotsAuth {
325
333
  }
326
334
  return response;
327
335
  }
336
+ async charge(featureCode, paidBy, quantity) {
337
+ this.assertInitialized();
338
+ const baseUrl = this.proxyConfigManager.getBaseUrl();
339
+ const response = await this.buildRequest(`${baseUrl}/payments/charge`, {
340
+ method: 'POST',
341
+ headers: { 'Content-Type': 'application/json' },
342
+ body: JSON.stringify({ featureCode, paidBy, quantity: quantity ?? 1 }),
343
+ });
344
+ if (response.status === 402) {
345
+ const body = await response.json();
346
+ throw new DotBotsAuthError('PAYMENT_FAILED', body.error, body.reason);
347
+ }
348
+ if (!response.ok) {
349
+ throw new DotBotsAuthError('NETWORK_ERROR', 'Payment request failed');
350
+ }
351
+ const result = await response.json();
352
+ this.postMessageHandler.sendCharge(this.config.appId, featureCode, result.amount, result.transactionId);
353
+ this.emit('charged');
354
+ return result;
355
+ }
328
356
  async logout() {
329
357
  this.assertInitialized();
330
358
  await this.tokenManager.revoke();
@@ -335,7 +363,7 @@ class DotBotsAuth {
335
363
  }
336
364
  else {
337
365
  const redirectUri = encodeURIComponent(window.location.origin);
338
- window.location.href = `${this.config.apiUrl}/auth/logout?redirectUri=${redirectUri}`;
366
+ window.location.href = `${this.config.apiUrl}/api/auth/logout?redirectUri=${redirectUri}`;
339
367
  }
340
368
  }
341
369
  on(event, handler) {
@@ -383,7 +411,7 @@ class DotBotsAuth {
383
411
  else if (!this.tokenManager.isAuthenticated()) {
384
412
  // Redirect to auth
385
413
  const redirectUri = encodeURIComponent(window.location.href);
386
- window.location.href = `${this.config.apiUrl}/auth/authorize?appId=${this.config.appId}&redirectUri=${redirectUri}`;
414
+ window.location.href = `${this.config.apiUrl}/api/auth/authorize?appId=${this.config.appId}&redirectUri=${redirectUri}`;
387
415
  }
388
416
  }
389
417
  async buildRequest(url, options) {
@@ -415,7 +443,7 @@ class DotBotsAuth {
415
443
  }
416
444
  }
417
445
  }
418
- DotBotsAuth.SDK_VERSION = '1.0.8';
446
+ DotBotsAuth.SDK_VERSION = '1.0.10';
419
447
 
420
448
  exports.DotBotsAuth = DotBotsAuth;
421
449
  exports.DotBotsAuthError = DotBotsAuthError;
@@ -49,6 +49,7 @@ function DotBotsAuthProvider({ auth, children, loadingComponent, errorComponent,
49
49
  canAny: (permissions) => auth.canAny(permissions),
50
50
  hasRole: (role) => auth.hasRole(role),
51
51
  fetch: (url, options) => auth.fetch(url, options),
52
+ charge: (featureCode, paidBy, quantity) => auth.charge(featureCode, paidBy, quantity),
52
53
  logout: () => auth.logout(),
53
54
  };
54
55
  return (jsxRuntime.jsx(DotBotsAuthContext.Provider, { value: value, children: children }));
package/dist/esm/index.js CHANGED
@@ -191,6 +191,14 @@ class PostMessageHandler {
191
191
  window.parent.postMessage({ type: 'DOTBOTS_LOGOUT' }, this.marketplaceOrigin);
192
192
  }
193
193
  }
194
+ /**
195
+ * Notify the parent of a successful charge (for real-time payment indicator).
196
+ */
197
+ sendCharge(appId, featureCode, amount, transactionId) {
198
+ if (this.isInIframe()) {
199
+ window.parent.postMessage({ type: 'DOTBOTS_CHARGE', appId, featureCode, amount, transactionId }, this.marketplaceOrigin);
200
+ }
201
+ }
194
202
  }
195
203
 
196
204
  class ProxyConfigManager {
@@ -323,6 +331,26 @@ class DotBotsAuth {
323
331
  }
324
332
  return response;
325
333
  }
334
+ async charge(featureCode, paidBy, quantity) {
335
+ this.assertInitialized();
336
+ const baseUrl = this.proxyConfigManager.getBaseUrl();
337
+ const response = await this.buildRequest(`${baseUrl}/payments/charge`, {
338
+ method: 'POST',
339
+ headers: { 'Content-Type': 'application/json' },
340
+ body: JSON.stringify({ featureCode, paidBy, quantity: quantity ?? 1 }),
341
+ });
342
+ if (response.status === 402) {
343
+ const body = await response.json();
344
+ throw new DotBotsAuthError('PAYMENT_FAILED', body.error, body.reason);
345
+ }
346
+ if (!response.ok) {
347
+ throw new DotBotsAuthError('NETWORK_ERROR', 'Payment request failed');
348
+ }
349
+ const result = await response.json();
350
+ this.postMessageHandler.sendCharge(this.config.appId, featureCode, result.amount, result.transactionId);
351
+ this.emit('charged');
352
+ return result;
353
+ }
326
354
  async logout() {
327
355
  this.assertInitialized();
328
356
  await this.tokenManager.revoke();
@@ -333,7 +361,7 @@ class DotBotsAuth {
333
361
  }
334
362
  else {
335
363
  const redirectUri = encodeURIComponent(window.location.origin);
336
- window.location.href = `${this.config.apiUrl}/auth/logout?redirectUri=${redirectUri}`;
364
+ window.location.href = `${this.config.apiUrl}/api/auth/logout?redirectUri=${redirectUri}`;
337
365
  }
338
366
  }
339
367
  on(event, handler) {
@@ -381,7 +409,7 @@ class DotBotsAuth {
381
409
  else if (!this.tokenManager.isAuthenticated()) {
382
410
  // Redirect to auth
383
411
  const redirectUri = encodeURIComponent(window.location.href);
384
- window.location.href = `${this.config.apiUrl}/auth/authorize?appId=${this.config.appId}&redirectUri=${redirectUri}`;
412
+ window.location.href = `${this.config.apiUrl}/api/auth/authorize?appId=${this.config.appId}&redirectUri=${redirectUri}`;
385
413
  }
386
414
  }
387
415
  async buildRequest(url, options) {
@@ -413,6 +441,6 @@ class DotBotsAuth {
413
441
  }
414
442
  }
415
443
  }
416
- DotBotsAuth.SDK_VERSION = '1.0.8';
444
+ DotBotsAuth.SDK_VERSION = '1.0.10';
417
445
 
418
446
  export { DotBotsAuth, DotBotsAuthError };
@@ -47,6 +47,7 @@ function DotBotsAuthProvider({ auth, children, loadingComponent, errorComponent,
47
47
  canAny: (permissions) => auth.canAny(permissions),
48
48
  hasRole: (role) => auth.hasRole(role),
49
49
  fetch: (url, options) => auth.fetch(url, options),
50
+ charge: (featureCode, paidBy, quantity) => auth.charge(featureCode, paidBy, quantity),
50
51
  logout: () => auth.logout(),
51
52
  };
52
53
  return (jsx(DotBotsAuthContext.Provider, { value: value, children: children }));
@@ -8,7 +8,7 @@ export declare class DotBotsAuth {
8
8
  private readonly listeners;
9
9
  private cachedUser;
10
10
  private initialized;
11
- static readonly SDK_VERSION = "1.0.8";
11
+ static readonly SDK_VERSION = "1.0.10";
12
12
  constructor(config: DotBotsConfig);
13
13
  initialize(): Promise<void>;
14
14
  getUser(): Promise<DotBotsUser>;
@@ -17,6 +17,9 @@ export declare class DotBotsAuth {
17
17
  canAny(permissions: string[]): boolean;
18
18
  hasRole(role: string): boolean;
19
19
  fetch(url: string, options?: RequestInit): Promise<Response>;
20
+ charge(featureCode: string, paidBy: 'org' | 'app', quantity?: number): Promise<{
21
+ transactionId: string;
22
+ }>;
20
23
  logout(): Promise<void>;
21
24
  on(event: DotBotsAuthEvent, handler: Function): void;
22
25
  off(event: DotBotsAuthEvent, handler: Function): void;
@@ -15,4 +15,8 @@ export declare class PostMessageHandler {
15
15
  * Notify the parent that the user has logged out.
16
16
  */
17
17
  sendLogout(): void;
18
+ /**
19
+ * Notify the parent of a successful charge (for real-time payment indicator).
20
+ */
21
+ sendCharge(appId: string, featureCode: string, amount: number, transactionId: string): void;
18
22
  }
@@ -191,6 +191,14 @@ class PostMessageHandler {
191
191
  window.parent.postMessage({ type: 'DOTBOTS_LOGOUT' }, this.marketplaceOrigin);
192
192
  }
193
193
  }
194
+ /**
195
+ * Notify the parent of a successful charge (for real-time payment indicator).
196
+ */
197
+ sendCharge(appId, featureCode, amount, transactionId) {
198
+ if (this.isInIframe()) {
199
+ window.parent.postMessage({ type: 'DOTBOTS_CHARGE', appId, featureCode, amount, transactionId }, this.marketplaceOrigin);
200
+ }
201
+ }
194
202
  }
195
203
 
196
204
  class ProxyConfigManager {
@@ -323,6 +331,26 @@ class DotBotsAuth {
323
331
  }
324
332
  return response;
325
333
  }
334
+ async charge(featureCode, paidBy, quantity) {
335
+ this.assertInitialized();
336
+ const baseUrl = this.proxyConfigManager.getBaseUrl();
337
+ const response = await this.buildRequest(`${baseUrl}/payments/charge`, {
338
+ method: 'POST',
339
+ headers: { 'Content-Type': 'application/json' },
340
+ body: JSON.stringify({ featureCode, paidBy, quantity: quantity ?? 1 }),
341
+ });
342
+ if (response.status === 402) {
343
+ const body = await response.json();
344
+ throw new DotBotsAuthError('PAYMENT_FAILED', body.error, body.reason);
345
+ }
346
+ if (!response.ok) {
347
+ throw new DotBotsAuthError('NETWORK_ERROR', 'Payment request failed');
348
+ }
349
+ const result = await response.json();
350
+ this.postMessageHandler.sendCharge(this.config.appId, featureCode, result.amount, result.transactionId);
351
+ this.emit('charged');
352
+ return result;
353
+ }
326
354
  async logout() {
327
355
  this.assertInitialized();
328
356
  await this.tokenManager.revoke();
@@ -333,7 +361,7 @@ class DotBotsAuth {
333
361
  }
334
362
  else {
335
363
  const redirectUri = encodeURIComponent(window.location.origin);
336
- window.location.href = `${this.config.apiUrl}/auth/logout?redirectUri=${redirectUri}`;
364
+ window.location.href = `${this.config.apiUrl}/api/auth/logout?redirectUri=${redirectUri}`;
337
365
  }
338
366
  }
339
367
  on(event, handler) {
@@ -381,7 +409,7 @@ class DotBotsAuth {
381
409
  else if (!this.tokenManager.isAuthenticated()) {
382
410
  // Redirect to auth
383
411
  const redirectUri = encodeURIComponent(window.location.href);
384
- window.location.href = `${this.config.apiUrl}/auth/authorize?appId=${this.config.appId}&redirectUri=${redirectUri}`;
412
+ window.location.href = `${this.config.apiUrl}/api/auth/authorize?appId=${this.config.appId}&redirectUri=${redirectUri}`;
385
413
  }
386
414
  }
387
415
  async buildRequest(url, options) {
@@ -413,6 +441,6 @@ class DotBotsAuth {
413
441
  }
414
442
  }
415
443
  }
416
- DotBotsAuth.SDK_VERSION = '1.0.8';
444
+ DotBotsAuth.SDK_VERSION = '1.0.10';
417
445
 
418
446
  export { DotBotsAuth, DotBotsAuthError };
@@ -11,6 +11,9 @@ export interface DotBotsAuthContextValue {
11
11
  canAny: (permissions: string[]) => boolean;
12
12
  hasRole: (role: string) => boolean;
13
13
  fetch: (url: string, options?: RequestInit) => Promise<Response>;
14
+ charge: (featureCode: string, paidBy: 'org' | 'app', quantity?: number) => Promise<{
15
+ transactionId: string;
16
+ }>;
14
17
  logout: () => Promise<void>;
15
18
  }
16
19
  export declare const DotBotsAuthContext: import("react").Context<DotBotsAuthContextValue | null>;
@@ -47,6 +47,7 @@ function DotBotsAuthProvider({ auth, children, loadingComponent, errorComponent,
47
47
  canAny: (permissions) => auth.canAny(permissions),
48
48
  hasRole: (role) => auth.hasRole(role),
49
49
  fetch: (url, options) => auth.fetch(url, options),
50
+ charge: (featureCode, paidBy, quantity) => auth.charge(featureCode, paidBy, quantity),
50
51
  logout: () => auth.logout(),
51
52
  };
52
53
  return (jsx(DotBotsAuthContext.Provider, { value: value, children: children }));
@@ -19,17 +19,17 @@ export interface DotBotsConfig {
19
19
  }
20
20
  export interface DotBotsUser {
21
21
  id: string;
22
- name: string | null;
23
- email: string | null;
22
+ name: string;
23
+ email: string;
24
24
  orgId: string;
25
- orgName: string | null;
25
+ orgName: string;
26
26
  teams: {
27
27
  teamId: string;
28
28
  teamName: string;
29
29
  }[];
30
30
  roles: string[];
31
31
  permissions: string[];
32
- avatarUrl: string | null;
32
+ avatarUrl?: string;
33
33
  }
34
34
  export interface DotBotsProxyConfig {
35
35
  /** URL of the local proxy, e.g. 'https://proxy.test-apps.dotbots.boutique' */
@@ -40,8 +40,8 @@ export interface DotBotsProxyConfig {
40
40
  cacheTtl: number;
41
41
  }
42
42
  export type ProxyFeature = 'cache' | 'localdb' | 'webhooks' | 'ratelimit';
43
- export type DotBotsAuthEvent = 'tokenRefreshed' | 'loggedOut' | 'sessionExpired' | 'userLoaded';
44
- export type DotBotsAuthErrorCode = 'IFRAME_TIMEOUT' | 'CODE_EXPIRED' | 'UNAUTHORIZED' | 'REFRESH_FAILED' | 'NETWORK_ERROR' | 'NOT_INITIALIZED' | 'PROXY_UNAVAILABLE';
43
+ export type DotBotsAuthEvent = 'tokenRefreshed' | 'loggedOut' | 'sessionExpired' | 'userLoaded' | 'charged';
44
+ export type DotBotsAuthErrorCode = 'IFRAME_TIMEOUT' | 'CODE_EXPIRED' | 'UNAUTHORIZED' | 'REFRESH_FAILED' | 'NETWORK_ERROR' | 'NOT_INITIALIZED' | 'PROXY_UNAVAILABLE' | 'PAYMENT_FAILED';
45
45
  export interface TokenPair {
46
46
  accessToken: string;
47
47
  refreshToken: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotbots-boutique/auth-sdk",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Authentication SDK for DotBots marketplace apps",
5
5
  "license": "MIT",
6
6
  "type": "module",