spaps-sdk 1.1.52 → 1.2.1

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
@@ -189,14 +189,21 @@ const user = await spaps.getUser();
189
189
 
190
190
  ### 💳 Stripe Integration
191
191
  ```javascript
192
- // Create checkout session
193
- const session = await spaps.createCheckoutSession(priceId, successUrl);
194
- window.location.href = session.data.url;
192
+ // Create checkout session with consent control
193
+ const session = await spaps.payments.createPaymentCheckout({
194
+ price_id: 'price_123',
195
+ success_url: 'https://app.example.com/success',
196
+ cancel_url: 'https://app.example.com/cancel',
197
+ require_legal_consent: true,
198
+ legal_consent_text: 'I agree I am 18+ and accept the HTMA Terms & Privacy.'
199
+ });
200
+ window.location.href = session.url;
195
201
 
196
202
  // Manage subscription
197
203
  const subscription = await spaps.getSubscription();
198
204
  await spaps.cancelSubscription();
199
205
  ```
206
+ > `require_legal_consent` forces Stripe’s Terms/Privacy checkbox, and `legal_consent_text` (≤120 chars) customizes the copy. Text is sanitized and defaults to “I agree to the Terms of Service and Privacy Policy.” when omitted.
200
207
 
201
208
  ### 📊 Usage Tracking
202
209
  ```javascript
package/dist/index.d.mts CHANGED
@@ -85,12 +85,53 @@ declare class PermissionChecker {
85
85
  declare function createPermissionChecker(customAdmins?: (string | AdminConfig)[]): PermissionChecker;
86
86
  declare const defaultPermissionChecker: PermissionChecker;
87
87
 
88
+ type ApiKeyType = 'publishable' | 'secret';
88
89
  interface SPAPSConfig {
89
90
  apiUrl?: string;
91
+ /** @deprecated Use publishableKey or secretKey instead */
90
92
  apiKey?: string;
93
+ /** Browser-safe key for client-side usage (spaps_pub_xxx) */
94
+ publishableKey?: string;
95
+ /** Server-only key for full access (spaps_sec_xxx) */
96
+ secretKey?: string;
91
97
  autoDetect?: boolean;
92
98
  timeout?: number;
93
99
  }
100
+ interface CheckoutLineItemPriceData {
101
+ currency: string;
102
+ unit_amount: number;
103
+ product_data: {
104
+ name: string;
105
+ description?: string;
106
+ metadata?: Record<string, string>;
107
+ };
108
+ }
109
+ interface CheckoutLineItem {
110
+ price_id?: string;
111
+ product_id?: string;
112
+ quantity: number;
113
+ price_data?: CheckoutLineItemPriceData;
114
+ }
115
+ interface CreateCheckoutSessionPayload {
116
+ mode: 'payment' | 'subscription';
117
+ line_items: CheckoutLineItem[];
118
+ success_url: string;
119
+ cancel_url: string;
120
+ metadata?: Record<string, string>;
121
+ customer_email?: string;
122
+ client_reference_id?: string;
123
+ payment_intent_data?: {
124
+ metadata?: Record<string, string>;
125
+ };
126
+ subscription_data?: {
127
+ metadata?: Record<string, string>;
128
+ trial_period_days?: number;
129
+ };
130
+ allow_promotion_codes?: boolean;
131
+ locale?: string;
132
+ require_legal_consent?: boolean;
133
+ legal_consent_text?: string;
134
+ }
94
135
 
95
136
  declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Record<string, any>> {
96
137
  private client;
@@ -203,14 +244,13 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
203
244
  password: string;
204
245
  }) => Promise<any>;
205
246
  /**
206
- * Verify a magic link token. Does not set access/refresh tokens.
207
- * Consumers should redirect or prompt login based on the returned status.
247
+ * Verify a magic link token and authenticate the user.
248
+ * Returns full auth response with tokens and auto-stores them.
208
249
  */
209
250
  verifyMagicLink: (payload: {
210
251
  token: string;
211
- }) => Promise<{
212
- success: boolean;
213
- }>;
252
+ type?: string;
253
+ }) => Promise<AuthResponse>;
214
254
  solana: {
215
255
  linkWallet: (payload: {
216
256
  wallet_address: string;
@@ -269,18 +309,22 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
269
309
  cursor?: Record<string, unknown>;
270
310
  }>;
271
311
  };
272
- createCheckoutSession: (payload: any) => Promise<CheckoutSession>;
312
+ createCheckoutSession: (payload: CreateCheckoutSessionPayload) => Promise<CheckoutSession>;
273
313
  createPaymentCheckout: (params: {
274
314
  price_id: string;
275
315
  quantity?: number;
276
316
  success_url: string;
277
317
  cancel_url: string;
318
+ require_legal_consent?: boolean;
319
+ legal_consent_text?: string;
278
320
  }) => Promise<CheckoutSession>;
279
321
  createSubscriptionCheckout: (params: {
280
322
  price_id: string;
281
323
  success_url: string;
282
324
  cancel_url: string;
283
325
  trial_period_days?: number;
326
+ require_legal_consent?: boolean;
327
+ legal_consent_text?: string;
284
328
  }) => Promise<CheckoutSession>;
285
329
  getCheckoutSession: (sessionId: string) => Promise<CheckoutSession>;
286
330
  listCheckoutSessions: (query?: {
@@ -357,6 +401,51 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
357
401
  revokeAll: () => Promise<any>;
358
402
  touch: () => Promise<any>;
359
403
  };
404
+ /**
405
+ * CFO (QuickBooks) namespace for managing multi-account connections
406
+ */
407
+ cfo: {
408
+ /**
409
+ * Get all QuickBooks connections for the current user
410
+ */
411
+ getConnections: () => Promise<{
412
+ connections: Array<{
413
+ id: string;
414
+ status: string;
415
+ companyName?: string;
416
+ realmId?: string;
417
+ connectedAt?: string;
418
+ }>;
419
+ }>;
420
+ /**
421
+ * Get connection status (returns primary connection for backward compatibility)
422
+ */
423
+ getStatus: () => Promise<{
424
+ connection: {
425
+ id: string;
426
+ status: string;
427
+ companyName?: string;
428
+ realmId?: string;
429
+ } | null;
430
+ totalConnections: number;
431
+ }>;
432
+ /**
433
+ * Start adding a new QuickBooks connection
434
+ * Returns auth URL to redirect user to QuickBooks OAuth
435
+ */
436
+ addConnection: () => Promise<{
437
+ authUrl: string;
438
+ connectionId: string;
439
+ existingConnections: number;
440
+ }>;
441
+ /**
442
+ * Disconnect a specific QuickBooks account
443
+ */
444
+ disconnect: (connectionId: string) => Promise<{
445
+ success: boolean;
446
+ message: string;
447
+ }>;
448
+ };
360
449
  createCheckoutSession(priceId: string, successUrl: string, cancelUrl?: string): Promise<{
361
450
  data: CheckoutSession;
362
451
  }>;
@@ -417,6 +506,12 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
417
506
  isAuthenticated(): boolean;
418
507
  getAccessToken(): string | undefined;
419
508
  setAccessToken(token: string): void;
509
+ setRefreshToken(token: string): void;
510
+ /**
511
+ * Restore tokens (for SSO/OAuth flows)
512
+ * Sets both access and refresh tokens
513
+ */
514
+ restoreTokens(accessToken: string, refreshToken?: string): void;
420
515
  isLocalMode(): boolean;
421
516
  /**
422
517
  * Check if current user has admin privileges
@@ -433,6 +528,10 @@ declare class TokenManager {
433
528
  private static readonly ACCESS_TOKEN_KEY;
434
529
  private static readonly REFRESH_TOKEN_KEY;
435
530
  private static readonly USER_KEY;
531
+ /**
532
+ * Cross-platform base64 decode (works in both Node.js and browser)
533
+ */
534
+ private static base64Decode;
436
535
  private static getStorage;
437
536
  static storeTokens(tokens: AuthResponse): void;
438
537
  static getAccessToken(): string | null;
@@ -441,10 +540,62 @@ declare class TokenManager {
441
540
  static clearTokens(): void;
442
541
  static isTokenExpired(token: string): boolean;
443
542
  static autoRefreshToken(sdk: SPAPSClient): Promise<boolean>;
543
+ /**
544
+ * Parse auth tokens from URL fragment (for OAuth callbacks)
545
+ * URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
546
+ */
547
+ static parseTokensFromUrlFragment(url?: string): {
548
+ access_token?: string;
549
+ refresh_token?: string;
550
+ user_id?: string;
551
+ } | null;
552
+ /**
553
+ * Handle OAuth callback - parse tokens from URL and store them
554
+ * Returns user info decoded from JWT, or null if no tokens found
555
+ */
556
+ static handleOAuthCallback(sdk: SPAPSClient): {
557
+ id: string;
558
+ email?: string;
559
+ role?: string;
560
+ } | null;
444
561
  }
445
562
  declare class WalletUtils {
446
563
  static detectChainType(address: string): 'solana' | 'ethereum' | 'bitcoin' | null;
447
564
  static isValidAddress(address: string, chainType?: 'solana' | 'ethereum' | 'bitcoin' | 'base'): boolean;
448
565
  }
566
+ /**
567
+ * Create a SPAPS client for browser/client-side usage
568
+ * Uses publishable key which is safe to expose in client bundles
569
+ *
570
+ * @example
571
+ * ```typescript
572
+ * // In your frontend code
573
+ * const spaps = createBrowserClient('spaps_pub_xxx');
574
+ *
575
+ * // Use for authentication and checkout
576
+ * const { user } = await spaps.auth.signInWithPassword({ email, password });
577
+ * const checkout = await spaps.payments.createCheckoutSession({...});
578
+ * ```
579
+ */
580
+ declare function createBrowserClient(publishableKey: string, options?: Omit<SPAPSConfig, 'publishableKey' | 'secretKey' | 'apiKey'>): SPAPSClient;
581
+ /**
582
+ * Create a SPAPS client for server-side usage
583
+ * Uses secret key which provides full access to all endpoints
584
+ *
585
+ * @example
586
+ * ```typescript
587
+ * // In your API routes (Next.js, Express, etc.)
588
+ * const spaps = createServerClient('spaps_sec_xxx');
589
+ *
590
+ * // Full access to admin operations
591
+ * await spaps.admin.createProduct({...});
592
+ * await spaps.payments.crypto.reconcile();
593
+ * ```
594
+ */
595
+ declare function createServerClient(secretKey: string, options?: Omit<SPAPSConfig, 'publishableKey' | 'secretKey' | 'apiKey'>): SPAPSClient;
596
+ /**
597
+ * Detect key type from key prefix
598
+ */
599
+ declare function detectKeyType(key: string): ApiKeyType | null;
449
600
 
450
- export { type AdminConfig, DEFAULT_ADMIN_ACCOUNTS, type PermissionCheckResult, PermissionChecker, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, TokenManager, WalletUtils, canAccessAdmin, createPermissionChecker, SPAPSClient as default, defaultPermissionChecker, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, verifyCryptoWebhookSignature };
601
+ export { type AdminConfig, type ApiKeyType, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, DEFAULT_ADMIN_ACCOUNTS, type PermissionCheckResult, PermissionChecker, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, TokenManager, WalletUtils, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, verifyCryptoWebhookSignature };
package/dist/index.d.ts CHANGED
@@ -85,12 +85,53 @@ declare class PermissionChecker {
85
85
  declare function createPermissionChecker(customAdmins?: (string | AdminConfig)[]): PermissionChecker;
86
86
  declare const defaultPermissionChecker: PermissionChecker;
87
87
 
88
+ type ApiKeyType = 'publishable' | 'secret';
88
89
  interface SPAPSConfig {
89
90
  apiUrl?: string;
91
+ /** @deprecated Use publishableKey or secretKey instead */
90
92
  apiKey?: string;
93
+ /** Browser-safe key for client-side usage (spaps_pub_xxx) */
94
+ publishableKey?: string;
95
+ /** Server-only key for full access (spaps_sec_xxx) */
96
+ secretKey?: string;
91
97
  autoDetect?: boolean;
92
98
  timeout?: number;
93
99
  }
100
+ interface CheckoutLineItemPriceData {
101
+ currency: string;
102
+ unit_amount: number;
103
+ product_data: {
104
+ name: string;
105
+ description?: string;
106
+ metadata?: Record<string, string>;
107
+ };
108
+ }
109
+ interface CheckoutLineItem {
110
+ price_id?: string;
111
+ product_id?: string;
112
+ quantity: number;
113
+ price_data?: CheckoutLineItemPriceData;
114
+ }
115
+ interface CreateCheckoutSessionPayload {
116
+ mode: 'payment' | 'subscription';
117
+ line_items: CheckoutLineItem[];
118
+ success_url: string;
119
+ cancel_url: string;
120
+ metadata?: Record<string, string>;
121
+ customer_email?: string;
122
+ client_reference_id?: string;
123
+ payment_intent_data?: {
124
+ metadata?: Record<string, string>;
125
+ };
126
+ subscription_data?: {
127
+ metadata?: Record<string, string>;
128
+ trial_period_days?: number;
129
+ };
130
+ allow_promotion_codes?: boolean;
131
+ locale?: string;
132
+ require_legal_consent?: boolean;
133
+ legal_consent_text?: string;
134
+ }
94
135
 
95
136
  declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Record<string, any>> {
96
137
  private client;
@@ -203,14 +244,13 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
203
244
  password: string;
204
245
  }) => Promise<any>;
205
246
  /**
206
- * Verify a magic link token. Does not set access/refresh tokens.
207
- * Consumers should redirect or prompt login based on the returned status.
247
+ * Verify a magic link token and authenticate the user.
248
+ * Returns full auth response with tokens and auto-stores them.
208
249
  */
209
250
  verifyMagicLink: (payload: {
210
251
  token: string;
211
- }) => Promise<{
212
- success: boolean;
213
- }>;
252
+ type?: string;
253
+ }) => Promise<AuthResponse>;
214
254
  solana: {
215
255
  linkWallet: (payload: {
216
256
  wallet_address: string;
@@ -269,18 +309,22 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
269
309
  cursor?: Record<string, unknown>;
270
310
  }>;
271
311
  };
272
- createCheckoutSession: (payload: any) => Promise<CheckoutSession>;
312
+ createCheckoutSession: (payload: CreateCheckoutSessionPayload) => Promise<CheckoutSession>;
273
313
  createPaymentCheckout: (params: {
274
314
  price_id: string;
275
315
  quantity?: number;
276
316
  success_url: string;
277
317
  cancel_url: string;
318
+ require_legal_consent?: boolean;
319
+ legal_consent_text?: string;
278
320
  }) => Promise<CheckoutSession>;
279
321
  createSubscriptionCheckout: (params: {
280
322
  price_id: string;
281
323
  success_url: string;
282
324
  cancel_url: string;
283
325
  trial_period_days?: number;
326
+ require_legal_consent?: boolean;
327
+ legal_consent_text?: string;
284
328
  }) => Promise<CheckoutSession>;
285
329
  getCheckoutSession: (sessionId: string) => Promise<CheckoutSession>;
286
330
  listCheckoutSessions: (query?: {
@@ -357,6 +401,51 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
357
401
  revokeAll: () => Promise<any>;
358
402
  touch: () => Promise<any>;
359
403
  };
404
+ /**
405
+ * CFO (QuickBooks) namespace for managing multi-account connections
406
+ */
407
+ cfo: {
408
+ /**
409
+ * Get all QuickBooks connections for the current user
410
+ */
411
+ getConnections: () => Promise<{
412
+ connections: Array<{
413
+ id: string;
414
+ status: string;
415
+ companyName?: string;
416
+ realmId?: string;
417
+ connectedAt?: string;
418
+ }>;
419
+ }>;
420
+ /**
421
+ * Get connection status (returns primary connection for backward compatibility)
422
+ */
423
+ getStatus: () => Promise<{
424
+ connection: {
425
+ id: string;
426
+ status: string;
427
+ companyName?: string;
428
+ realmId?: string;
429
+ } | null;
430
+ totalConnections: number;
431
+ }>;
432
+ /**
433
+ * Start adding a new QuickBooks connection
434
+ * Returns auth URL to redirect user to QuickBooks OAuth
435
+ */
436
+ addConnection: () => Promise<{
437
+ authUrl: string;
438
+ connectionId: string;
439
+ existingConnections: number;
440
+ }>;
441
+ /**
442
+ * Disconnect a specific QuickBooks account
443
+ */
444
+ disconnect: (connectionId: string) => Promise<{
445
+ success: boolean;
446
+ message: string;
447
+ }>;
448
+ };
360
449
  createCheckoutSession(priceId: string, successUrl: string, cancelUrl?: string): Promise<{
361
450
  data: CheckoutSession;
362
451
  }>;
@@ -417,6 +506,12 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
417
506
  isAuthenticated(): boolean;
418
507
  getAccessToken(): string | undefined;
419
508
  setAccessToken(token: string): void;
509
+ setRefreshToken(token: string): void;
510
+ /**
511
+ * Restore tokens (for SSO/OAuth flows)
512
+ * Sets both access and refresh tokens
513
+ */
514
+ restoreTokens(accessToken: string, refreshToken?: string): void;
420
515
  isLocalMode(): boolean;
421
516
  /**
422
517
  * Check if current user has admin privileges
@@ -433,6 +528,10 @@ declare class TokenManager {
433
528
  private static readonly ACCESS_TOKEN_KEY;
434
529
  private static readonly REFRESH_TOKEN_KEY;
435
530
  private static readonly USER_KEY;
531
+ /**
532
+ * Cross-platform base64 decode (works in both Node.js and browser)
533
+ */
534
+ private static base64Decode;
436
535
  private static getStorage;
437
536
  static storeTokens(tokens: AuthResponse): void;
438
537
  static getAccessToken(): string | null;
@@ -441,10 +540,62 @@ declare class TokenManager {
441
540
  static clearTokens(): void;
442
541
  static isTokenExpired(token: string): boolean;
443
542
  static autoRefreshToken(sdk: SPAPSClient): Promise<boolean>;
543
+ /**
544
+ * Parse auth tokens from URL fragment (for OAuth callbacks)
545
+ * URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
546
+ */
547
+ static parseTokensFromUrlFragment(url?: string): {
548
+ access_token?: string;
549
+ refresh_token?: string;
550
+ user_id?: string;
551
+ } | null;
552
+ /**
553
+ * Handle OAuth callback - parse tokens from URL and store them
554
+ * Returns user info decoded from JWT, or null if no tokens found
555
+ */
556
+ static handleOAuthCallback(sdk: SPAPSClient): {
557
+ id: string;
558
+ email?: string;
559
+ role?: string;
560
+ } | null;
444
561
  }
445
562
  declare class WalletUtils {
446
563
  static detectChainType(address: string): 'solana' | 'ethereum' | 'bitcoin' | null;
447
564
  static isValidAddress(address: string, chainType?: 'solana' | 'ethereum' | 'bitcoin' | 'base'): boolean;
448
565
  }
566
+ /**
567
+ * Create a SPAPS client for browser/client-side usage
568
+ * Uses publishable key which is safe to expose in client bundles
569
+ *
570
+ * @example
571
+ * ```typescript
572
+ * // In your frontend code
573
+ * const spaps = createBrowserClient('spaps_pub_xxx');
574
+ *
575
+ * // Use for authentication and checkout
576
+ * const { user } = await spaps.auth.signInWithPassword({ email, password });
577
+ * const checkout = await spaps.payments.createCheckoutSession({...});
578
+ * ```
579
+ */
580
+ declare function createBrowserClient(publishableKey: string, options?: Omit<SPAPSConfig, 'publishableKey' | 'secretKey' | 'apiKey'>): SPAPSClient;
581
+ /**
582
+ * Create a SPAPS client for server-side usage
583
+ * Uses secret key which provides full access to all endpoints
584
+ *
585
+ * @example
586
+ * ```typescript
587
+ * // In your API routes (Next.js, Express, etc.)
588
+ * const spaps = createServerClient('spaps_sec_xxx');
589
+ *
590
+ * // Full access to admin operations
591
+ * await spaps.admin.createProduct({...});
592
+ * await spaps.payments.crypto.reconcile();
593
+ * ```
594
+ */
595
+ declare function createServerClient(secretKey: string, options?: Omit<SPAPSConfig, 'publishableKey' | 'secretKey' | 'apiKey'>): SPAPSClient;
596
+ /**
597
+ * Detect key type from key prefix
598
+ */
599
+ declare function detectKeyType(key: string): ApiKeyType | null;
449
600
 
450
- export { type AdminConfig, DEFAULT_ADMIN_ACCOUNTS, type PermissionCheckResult, PermissionChecker, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, TokenManager, WalletUtils, canAccessAdmin, createPermissionChecker, SPAPSClient as default, defaultPermissionChecker, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, verifyCryptoWebhookSignature };
601
+ export { type AdminConfig, type ApiKeyType, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, DEFAULT_ADMIN_ACCOUNTS, type PermissionCheckResult, PermissionChecker, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, TokenManager, WalletUtils, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, verifyCryptoWebhookSignature };
package/dist/index.js CHANGED
@@ -201,10 +201,13 @@ __export(index_exports, {
201
201
  TokenManager: () => TokenManager,
202
202
  WalletUtils: () => WalletUtils,
203
203
  canAccessAdmin: () => canAccessAdmin,
204
+ createBrowserClient: () => createBrowserClient,
204
205
  createPermissionChecker: () => createPermissionChecker,
205
206
  createSecureMessageRequestSchema: () => import_spaps_types.createSecureMessageRequestSchema,
207
+ createServerClient: () => createServerClient,
206
208
  default: () => index_default,
207
209
  defaultPermissionChecker: () => defaultPermissionChecker,
210
+ detectKeyType: () => detectKeyType,
208
211
  getRoleAwareErrorMessage: () => getRoleAwareErrorMessage,
209
212
  getUserDisplay: () => getUserDisplay,
210
213
  getUserRole: () => getUserRole,
@@ -279,6 +282,20 @@ var SPAPSClient = class {
279
282
  };
280
283
  constructor(config = {}) {
281
284
  const apiUrl = config.apiUrl || process.env.SPAPS_API_URL || process.env.NEXT_PUBLIC_SPAPS_API_URL;
285
+ const isBrowser = typeof window !== "undefined";
286
+ let effectiveApiKey;
287
+ if (config.publishableKey) {
288
+ effectiveApiKey = config.publishableKey;
289
+ } else if (config.secretKey) {
290
+ effectiveApiKey = config.secretKey;
291
+ if (isBrowser) {
292
+ console.warn("\u26A0\uFE0F SPAPS: Using secretKey in browser is not recommended. Use publishableKey instead.");
293
+ }
294
+ } else if (config.apiKey) {
295
+ effectiveApiKey = config.apiKey;
296
+ } else {
297
+ effectiveApiKey = process.env.SPAPS_API_KEY || process.env.NEXT_PUBLIC_SPAPS_API_KEY;
298
+ }
282
299
  if (!apiUrl || apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1")) {
283
300
  this._isLocalMode = true;
284
301
  this.client = import_axios.default.create({
@@ -289,10 +306,10 @@ var SPAPSClient = class {
289
306
  }
290
307
  });
291
308
  } else {
292
- if (!config.apiKey && !process.env.SPAPS_API_KEY) {
309
+ if (!effectiveApiKey) {
293
310
  console.warn("\u26A0\uFE0F SPAPS: No API key provided. Some features may not work.");
294
311
  }
295
- this.apiKey = config.apiKey || process.env.SPAPS_API_KEY;
312
+ this.apiKey = effectiveApiKey;
296
313
  this.client = import_axios.default.create({
297
314
  baseURL: apiUrl,
298
315
  timeout: config.timeout || 1e4,
@@ -453,12 +470,24 @@ var SPAPSClient = class {
453
470
  return data;
454
471
  },
455
472
  /**
456
- * Verify a magic link token. Does not set access/refresh tokens.
457
- * Consumers should redirect or prompt login based on the returned status.
473
+ * Verify a magic link token and authenticate the user.
474
+ * Returns full auth response with tokens and auto-stores them.
458
475
  */
459
476
  verifyMagicLink: async (payload) => {
460
- const res = await this.client.post("/api/auth/verify-magic-link", payload);
461
- return this.unwrapApiResponse(res, "Failed to verify magic link");
477
+ const res = await this.client.post("/api/auth/verify-magic-link", {
478
+ token: payload.token,
479
+ type: payload.type || "magiclink"
480
+ });
481
+ const body = res.data;
482
+ if (body?.success === false) throw new Error(body?.error?.message || "Magic link verification failed");
483
+ const data = {
484
+ access_token: body.tokens?.access_token || body.access_token,
485
+ refresh_token: body.tokens?.refresh_token || body.refresh_token,
486
+ user: body.user
487
+ };
488
+ this.accessToken = data.access_token;
489
+ this.refreshToken = data.refresh_token;
490
+ return data;
462
491
  },
463
492
  solana: {
464
493
  linkWallet: async (payload) => {
@@ -600,11 +629,25 @@ var SPAPSClient = class {
600
629
  return this.unwrapApiResponse(res, "Failed to create checkout session");
601
630
  },
602
631
  createPaymentCheckout: async (params) => {
603
- const payload = { mode: "payment", line_items: [{ price_id: params.price_id, quantity: params.quantity ?? 1 }], success_url: params.success_url, cancel_url: params.cancel_url };
632
+ const payload = {
633
+ mode: "payment",
634
+ line_items: [{ price_id: params.price_id, quantity: params.quantity ?? 1 }],
635
+ success_url: params.success_url,
636
+ cancel_url: params.cancel_url,
637
+ require_legal_consent: params.require_legal_consent,
638
+ legal_consent_text: params.legal_consent_text
639
+ };
604
640
  return this.payments.createCheckoutSession(payload);
605
641
  },
606
642
  createSubscriptionCheckout: async (params) => {
607
- const payload = { mode: "subscription", line_items: [{ price_id: params.price_id, quantity: 1 }], success_url: params.success_url, cancel_url: params.cancel_url };
643
+ const payload = {
644
+ mode: "subscription",
645
+ line_items: [{ price_id: params.price_id, quantity: 1 }],
646
+ success_url: params.success_url,
647
+ cancel_url: params.cancel_url,
648
+ require_legal_consent: params.require_legal_consent,
649
+ legal_consent_text: params.legal_consent_text
650
+ };
608
651
  if (params.trial_period_days) payload.subscription_data = { trial_period_days: params.trial_period_days };
609
652
  return this.payments.createCheckoutSession(payload);
610
653
  },
@@ -760,6 +803,40 @@ var SPAPSClient = class {
760
803
  return this.unwrapApiResponse(res, "Failed to touch session");
761
804
  }
762
805
  };
806
+ /**
807
+ * CFO (QuickBooks) namespace for managing multi-account connections
808
+ */
809
+ cfo = {
810
+ /**
811
+ * Get all QuickBooks connections for the current user
812
+ */
813
+ getConnections: async () => {
814
+ const res = await this.client.get("/api/cfo/user-connections", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
815
+ return this.unwrapApiResponse(res, "Failed to get connections");
816
+ },
817
+ /**
818
+ * Get connection status (returns primary connection for backward compatibility)
819
+ */
820
+ getStatus: async () => {
821
+ const res = await this.client.get("/api/cfo/user-status", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
822
+ return this.unwrapApiResponse(res, "Failed to get status");
823
+ },
824
+ /**
825
+ * Start adding a new QuickBooks connection
826
+ * Returns auth URL to redirect user to QuickBooks OAuth
827
+ */
828
+ addConnection: async () => {
829
+ const res = await this.client.post("/api/cfo/user-add-connection", {}, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
830
+ return this.unwrapApiResponse(res, "Failed to start connection");
831
+ },
832
+ /**
833
+ * Disconnect a specific QuickBooks account
834
+ */
835
+ disconnect: async (connectionId) => {
836
+ const res = await this.client.delete(`/api/cfo/user-connections/${connectionId}`, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
837
+ return this.unwrapApiResponse(res, "Failed to disconnect");
838
+ }
839
+ };
763
840
  // Stripe Methods
764
841
  async createCheckoutSession(priceId, successUrl, cancelUrl) {
765
842
  return this.client.post("/api/stripe/create-checkout-session", {
@@ -883,6 +960,19 @@ var SPAPSClient = class {
883
960
  setAccessToken(token) {
884
961
  this.accessToken = token;
885
962
  }
963
+ setRefreshToken(token) {
964
+ this.refreshToken = token;
965
+ }
966
+ /**
967
+ * Restore tokens (for SSO/OAuth flows)
968
+ * Sets both access and refresh tokens
969
+ */
970
+ restoreTokens(accessToken, refreshToken) {
971
+ this.accessToken = accessToken;
972
+ if (refreshToken) {
973
+ this.refreshToken = refreshToken;
974
+ }
975
+ }
886
976
  isLocalMode() {
887
977
  return this._isLocalMode;
888
978
  }
@@ -937,6 +1027,17 @@ var TokenManager = class _TokenManager {
937
1027
  static ACCESS_TOKEN_KEY = "sweet_potato_access_token";
938
1028
  static REFRESH_TOKEN_KEY = "sweet_potato_refresh_token";
939
1029
  static USER_KEY = "sweet_potato_user";
1030
+ /**
1031
+ * Cross-platform base64 decode (works in both Node.js and browser)
1032
+ */
1033
+ static base64Decode(str) {
1034
+ if (typeof Buffer !== "undefined") {
1035
+ return Buffer.from(str, "base64").toString("utf8");
1036
+ } else if (typeof atob !== "undefined") {
1037
+ return atob(str);
1038
+ }
1039
+ throw new Error("No base64 decode method available");
1040
+ }
940
1041
  static getStorage() {
941
1042
  if (typeof globalThis !== "undefined" && globalThis.localStorage) return globalThis.localStorage;
942
1043
  if (typeof globalThis !== "undefined" && globalThis.window?.localStorage) return globalThis.window.localStorage;
@@ -977,7 +1078,7 @@ var TokenManager = class _TokenManager {
977
1078
  try {
978
1079
  const parts = token.split(".");
979
1080
  if (parts.length !== 3 || !parts[1]) return true;
980
- const payload = JSON.parse(atob(parts[1]));
1081
+ const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
981
1082
  const now = Math.floor(Date.now() / 1e3);
982
1083
  return payload.exp < now;
983
1084
  } catch {
@@ -1001,6 +1102,74 @@ var TokenManager = class _TokenManager {
1001
1102
  return false;
1002
1103
  }
1003
1104
  }
1105
+ /**
1106
+ * Parse auth tokens from URL fragment (for OAuth callbacks)
1107
+ * URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
1108
+ */
1109
+ static parseTokensFromUrlFragment(url) {
1110
+ let hash;
1111
+ if (url) {
1112
+ try {
1113
+ hash = new URL(url).hash;
1114
+ } catch {
1115
+ return null;
1116
+ }
1117
+ } else if (typeof window !== "undefined") {
1118
+ hash = window.location.hash;
1119
+ } else {
1120
+ return null;
1121
+ }
1122
+ if (!hash || hash.length < 2) return null;
1123
+ const params = new URLSearchParams(hash.substring(1));
1124
+ const access_token = params.get("access_token");
1125
+ const refresh_token = params.get("refresh_token");
1126
+ if (!access_token) return null;
1127
+ return {
1128
+ access_token,
1129
+ refresh_token: refresh_token || void 0,
1130
+ user_id: params.get("user_id") || void 0
1131
+ };
1132
+ }
1133
+ /**
1134
+ * Handle OAuth callback - parse tokens from URL and store them
1135
+ * Returns user info decoded from JWT, or null if no tokens found
1136
+ */
1137
+ static handleOAuthCallback(sdk) {
1138
+ const tokens = _TokenManager.parseTokensFromUrlFragment();
1139
+ if (!tokens?.access_token) {
1140
+ console.debug("[SPAPS] handleOAuthCallback: No access token found in URL fragment");
1141
+ return null;
1142
+ }
1143
+ if (typeof window !== "undefined") {
1144
+ window.history.replaceState(null, "", window.location.pathname + window.location.search);
1145
+ }
1146
+ try {
1147
+ const parts = tokens.access_token.split(".");
1148
+ if (parts.length !== 3 || !parts[1]) {
1149
+ console.warn("[SPAPS] handleOAuthCallback: Invalid JWT format - expected 3 parts");
1150
+ return null;
1151
+ }
1152
+ const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
1153
+ const user = {
1154
+ id: payload.user_id || payload.sub,
1155
+ email: payload.email,
1156
+ role: payload.role || "user"
1157
+ };
1158
+ _TokenManager.storeTokens({
1159
+ access_token: tokens.access_token,
1160
+ refresh_token: tokens.refresh_token || "",
1161
+ user
1162
+ });
1163
+ sdk.setAccessToken(tokens.access_token);
1164
+ if (tokens.refresh_token) {
1165
+ sdk.setRefreshToken(tokens.refresh_token);
1166
+ }
1167
+ return user;
1168
+ } catch (err) {
1169
+ console.warn("[SPAPS] handleOAuthCallback: Failed to decode JWT", err);
1170
+ return null;
1171
+ }
1172
+ }
1004
1173
  };
1005
1174
  var WalletUtils = class _WalletUtils {
1006
1175
  static detectChainType(address) {
@@ -1026,6 +1195,29 @@ var WalletUtils = class _WalletUtils {
1026
1195
  }
1027
1196
  }
1028
1197
  };
1198
+ function createBrowserClient(publishableKey, options) {
1199
+ if (!publishableKey.startsWith("spaps_pub_")) {
1200
+ console.warn("\u26A0\uFE0F SPAPS: Expected a publishable key (spaps_pub_xxx). Using a secret key in browser is not recommended.");
1201
+ }
1202
+ return new SPAPSClient({
1203
+ ...options,
1204
+ publishableKey
1205
+ });
1206
+ }
1207
+ function createServerClient(secretKey, options) {
1208
+ if (typeof window !== "undefined") {
1209
+ console.warn("\u26A0\uFE0F SPAPS: createServerClient should only be used in server environments. Use createBrowserClient for browser usage.");
1210
+ }
1211
+ return new SPAPSClient({
1212
+ ...options,
1213
+ secretKey
1214
+ });
1215
+ }
1216
+ function detectKeyType(key) {
1217
+ if (key.startsWith("spaps_pub_")) return "publishable";
1218
+ if (key.startsWith("spaps_sec_") || key.startsWith("spaps_")) return "secret";
1219
+ return null;
1220
+ }
1029
1221
  // Annotate the CommonJS export names for ESM import in node:
1030
1222
  0 && (module.exports = {
1031
1223
  DEFAULT_ADMIN_ACCOUNTS,
@@ -1035,9 +1227,12 @@ var WalletUtils = class _WalletUtils {
1035
1227
  TokenManager,
1036
1228
  WalletUtils,
1037
1229
  canAccessAdmin,
1230
+ createBrowserClient,
1038
1231
  createPermissionChecker,
1039
1232
  createSecureMessageRequestSchema,
1233
+ createServerClient,
1040
1234
  defaultPermissionChecker,
1235
+ detectKeyType,
1041
1236
  getRoleAwareErrorMessage,
1042
1237
  getUserDisplay,
1043
1238
  getUserRole,
package/dist/index.mjs CHANGED
@@ -255,6 +255,20 @@ var SPAPSClient = class {
255
255
  };
256
256
  constructor(config = {}) {
257
257
  const apiUrl = config.apiUrl || process.env.SPAPS_API_URL || process.env.NEXT_PUBLIC_SPAPS_API_URL;
258
+ const isBrowser = typeof window !== "undefined";
259
+ let effectiveApiKey;
260
+ if (config.publishableKey) {
261
+ effectiveApiKey = config.publishableKey;
262
+ } else if (config.secretKey) {
263
+ effectiveApiKey = config.secretKey;
264
+ if (isBrowser) {
265
+ console.warn("\u26A0\uFE0F SPAPS: Using secretKey in browser is not recommended. Use publishableKey instead.");
266
+ }
267
+ } else if (config.apiKey) {
268
+ effectiveApiKey = config.apiKey;
269
+ } else {
270
+ effectiveApiKey = process.env.SPAPS_API_KEY || process.env.NEXT_PUBLIC_SPAPS_API_KEY;
271
+ }
258
272
  if (!apiUrl || apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1")) {
259
273
  this._isLocalMode = true;
260
274
  this.client = axios.create({
@@ -265,10 +279,10 @@ var SPAPSClient = class {
265
279
  }
266
280
  });
267
281
  } else {
268
- if (!config.apiKey && !process.env.SPAPS_API_KEY) {
282
+ if (!effectiveApiKey) {
269
283
  console.warn("\u26A0\uFE0F SPAPS: No API key provided. Some features may not work.");
270
284
  }
271
- this.apiKey = config.apiKey || process.env.SPAPS_API_KEY;
285
+ this.apiKey = effectiveApiKey;
272
286
  this.client = axios.create({
273
287
  baseURL: apiUrl,
274
288
  timeout: config.timeout || 1e4,
@@ -429,12 +443,24 @@ var SPAPSClient = class {
429
443
  return data;
430
444
  },
431
445
  /**
432
- * Verify a magic link token. Does not set access/refresh tokens.
433
- * Consumers should redirect or prompt login based on the returned status.
446
+ * Verify a magic link token and authenticate the user.
447
+ * Returns full auth response with tokens and auto-stores them.
434
448
  */
435
449
  verifyMagicLink: async (payload) => {
436
- const res = await this.client.post("/api/auth/verify-magic-link", payload);
437
- return this.unwrapApiResponse(res, "Failed to verify magic link");
450
+ const res = await this.client.post("/api/auth/verify-magic-link", {
451
+ token: payload.token,
452
+ type: payload.type || "magiclink"
453
+ });
454
+ const body = res.data;
455
+ if (body?.success === false) throw new Error(body?.error?.message || "Magic link verification failed");
456
+ const data = {
457
+ access_token: body.tokens?.access_token || body.access_token,
458
+ refresh_token: body.tokens?.refresh_token || body.refresh_token,
459
+ user: body.user
460
+ };
461
+ this.accessToken = data.access_token;
462
+ this.refreshToken = data.refresh_token;
463
+ return data;
438
464
  },
439
465
  solana: {
440
466
  linkWallet: async (payload) => {
@@ -576,11 +602,25 @@ var SPAPSClient = class {
576
602
  return this.unwrapApiResponse(res, "Failed to create checkout session");
577
603
  },
578
604
  createPaymentCheckout: async (params) => {
579
- const payload = { mode: "payment", line_items: [{ price_id: params.price_id, quantity: params.quantity ?? 1 }], success_url: params.success_url, cancel_url: params.cancel_url };
605
+ const payload = {
606
+ mode: "payment",
607
+ line_items: [{ price_id: params.price_id, quantity: params.quantity ?? 1 }],
608
+ success_url: params.success_url,
609
+ cancel_url: params.cancel_url,
610
+ require_legal_consent: params.require_legal_consent,
611
+ legal_consent_text: params.legal_consent_text
612
+ };
580
613
  return this.payments.createCheckoutSession(payload);
581
614
  },
582
615
  createSubscriptionCheckout: async (params) => {
583
- const payload = { mode: "subscription", line_items: [{ price_id: params.price_id, quantity: 1 }], success_url: params.success_url, cancel_url: params.cancel_url };
616
+ const payload = {
617
+ mode: "subscription",
618
+ line_items: [{ price_id: params.price_id, quantity: 1 }],
619
+ success_url: params.success_url,
620
+ cancel_url: params.cancel_url,
621
+ require_legal_consent: params.require_legal_consent,
622
+ legal_consent_text: params.legal_consent_text
623
+ };
584
624
  if (params.trial_period_days) payload.subscription_data = { trial_period_days: params.trial_period_days };
585
625
  return this.payments.createCheckoutSession(payload);
586
626
  },
@@ -736,6 +776,40 @@ var SPAPSClient = class {
736
776
  return this.unwrapApiResponse(res, "Failed to touch session");
737
777
  }
738
778
  };
779
+ /**
780
+ * CFO (QuickBooks) namespace for managing multi-account connections
781
+ */
782
+ cfo = {
783
+ /**
784
+ * Get all QuickBooks connections for the current user
785
+ */
786
+ getConnections: async () => {
787
+ const res = await this.client.get("/api/cfo/user-connections", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
788
+ return this.unwrapApiResponse(res, "Failed to get connections");
789
+ },
790
+ /**
791
+ * Get connection status (returns primary connection for backward compatibility)
792
+ */
793
+ getStatus: async () => {
794
+ const res = await this.client.get("/api/cfo/user-status", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
795
+ return this.unwrapApiResponse(res, "Failed to get status");
796
+ },
797
+ /**
798
+ * Start adding a new QuickBooks connection
799
+ * Returns auth URL to redirect user to QuickBooks OAuth
800
+ */
801
+ addConnection: async () => {
802
+ const res = await this.client.post("/api/cfo/user-add-connection", {}, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
803
+ return this.unwrapApiResponse(res, "Failed to start connection");
804
+ },
805
+ /**
806
+ * Disconnect a specific QuickBooks account
807
+ */
808
+ disconnect: async (connectionId) => {
809
+ const res = await this.client.delete(`/api/cfo/user-connections/${connectionId}`, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
810
+ return this.unwrapApiResponse(res, "Failed to disconnect");
811
+ }
812
+ };
739
813
  // Stripe Methods
740
814
  async createCheckoutSession(priceId, successUrl, cancelUrl) {
741
815
  return this.client.post("/api/stripe/create-checkout-session", {
@@ -859,6 +933,19 @@ var SPAPSClient = class {
859
933
  setAccessToken(token) {
860
934
  this.accessToken = token;
861
935
  }
936
+ setRefreshToken(token) {
937
+ this.refreshToken = token;
938
+ }
939
+ /**
940
+ * Restore tokens (for SSO/OAuth flows)
941
+ * Sets both access and refresh tokens
942
+ */
943
+ restoreTokens(accessToken, refreshToken) {
944
+ this.accessToken = accessToken;
945
+ if (refreshToken) {
946
+ this.refreshToken = refreshToken;
947
+ }
948
+ }
862
949
  isLocalMode() {
863
950
  return this._isLocalMode;
864
951
  }
@@ -913,6 +1000,17 @@ var TokenManager = class _TokenManager {
913
1000
  static ACCESS_TOKEN_KEY = "sweet_potato_access_token";
914
1001
  static REFRESH_TOKEN_KEY = "sweet_potato_refresh_token";
915
1002
  static USER_KEY = "sweet_potato_user";
1003
+ /**
1004
+ * Cross-platform base64 decode (works in both Node.js and browser)
1005
+ */
1006
+ static base64Decode(str) {
1007
+ if (typeof Buffer !== "undefined") {
1008
+ return Buffer.from(str, "base64").toString("utf8");
1009
+ } else if (typeof atob !== "undefined") {
1010
+ return atob(str);
1011
+ }
1012
+ throw new Error("No base64 decode method available");
1013
+ }
916
1014
  static getStorage() {
917
1015
  if (typeof globalThis !== "undefined" && globalThis.localStorage) return globalThis.localStorage;
918
1016
  if (typeof globalThis !== "undefined" && globalThis.window?.localStorage) return globalThis.window.localStorage;
@@ -953,7 +1051,7 @@ var TokenManager = class _TokenManager {
953
1051
  try {
954
1052
  const parts = token.split(".");
955
1053
  if (parts.length !== 3 || !parts[1]) return true;
956
- const payload = JSON.parse(atob(parts[1]));
1054
+ const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
957
1055
  const now = Math.floor(Date.now() / 1e3);
958
1056
  return payload.exp < now;
959
1057
  } catch {
@@ -977,6 +1075,74 @@ var TokenManager = class _TokenManager {
977
1075
  return false;
978
1076
  }
979
1077
  }
1078
+ /**
1079
+ * Parse auth tokens from URL fragment (for OAuth callbacks)
1080
+ * URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
1081
+ */
1082
+ static parseTokensFromUrlFragment(url) {
1083
+ let hash;
1084
+ if (url) {
1085
+ try {
1086
+ hash = new URL(url).hash;
1087
+ } catch {
1088
+ return null;
1089
+ }
1090
+ } else if (typeof window !== "undefined") {
1091
+ hash = window.location.hash;
1092
+ } else {
1093
+ return null;
1094
+ }
1095
+ if (!hash || hash.length < 2) return null;
1096
+ const params = new URLSearchParams(hash.substring(1));
1097
+ const access_token = params.get("access_token");
1098
+ const refresh_token = params.get("refresh_token");
1099
+ if (!access_token) return null;
1100
+ return {
1101
+ access_token,
1102
+ refresh_token: refresh_token || void 0,
1103
+ user_id: params.get("user_id") || void 0
1104
+ };
1105
+ }
1106
+ /**
1107
+ * Handle OAuth callback - parse tokens from URL and store them
1108
+ * Returns user info decoded from JWT, or null if no tokens found
1109
+ */
1110
+ static handleOAuthCallback(sdk) {
1111
+ const tokens = _TokenManager.parseTokensFromUrlFragment();
1112
+ if (!tokens?.access_token) {
1113
+ console.debug("[SPAPS] handleOAuthCallback: No access token found in URL fragment");
1114
+ return null;
1115
+ }
1116
+ if (typeof window !== "undefined") {
1117
+ window.history.replaceState(null, "", window.location.pathname + window.location.search);
1118
+ }
1119
+ try {
1120
+ const parts = tokens.access_token.split(".");
1121
+ if (parts.length !== 3 || !parts[1]) {
1122
+ console.warn("[SPAPS] handleOAuthCallback: Invalid JWT format - expected 3 parts");
1123
+ return null;
1124
+ }
1125
+ const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
1126
+ const user = {
1127
+ id: payload.user_id || payload.sub,
1128
+ email: payload.email,
1129
+ role: payload.role || "user"
1130
+ };
1131
+ _TokenManager.storeTokens({
1132
+ access_token: tokens.access_token,
1133
+ refresh_token: tokens.refresh_token || "",
1134
+ user
1135
+ });
1136
+ sdk.setAccessToken(tokens.access_token);
1137
+ if (tokens.refresh_token) {
1138
+ sdk.setRefreshToken(tokens.refresh_token);
1139
+ }
1140
+ return user;
1141
+ } catch (err) {
1142
+ console.warn("[SPAPS] handleOAuthCallback: Failed to decode JWT", err);
1143
+ return null;
1144
+ }
1145
+ }
980
1146
  };
981
1147
  var WalletUtils = class _WalletUtils {
982
1148
  static detectChainType(address) {
@@ -1002,6 +1168,29 @@ var WalletUtils = class _WalletUtils {
1002
1168
  }
1003
1169
  }
1004
1170
  };
1171
+ function createBrowserClient(publishableKey, options) {
1172
+ if (!publishableKey.startsWith("spaps_pub_")) {
1173
+ console.warn("\u26A0\uFE0F SPAPS: Expected a publishable key (spaps_pub_xxx). Using a secret key in browser is not recommended.");
1174
+ }
1175
+ return new SPAPSClient({
1176
+ ...options,
1177
+ publishableKey
1178
+ });
1179
+ }
1180
+ function createServerClient(secretKey, options) {
1181
+ if (typeof window !== "undefined") {
1182
+ console.warn("\u26A0\uFE0F SPAPS: createServerClient should only be used in server environments. Use createBrowserClient for browser usage.");
1183
+ }
1184
+ return new SPAPSClient({
1185
+ ...options,
1186
+ secretKey
1187
+ });
1188
+ }
1189
+ function detectKeyType(key) {
1190
+ if (key.startsWith("spaps_pub_")) return "publishable";
1191
+ if (key.startsWith("spaps_sec_") || key.startsWith("spaps_")) return "secret";
1192
+ return null;
1193
+ }
1005
1194
  export {
1006
1195
  DEFAULT_ADMIN_ACCOUNTS,
1007
1196
  PermissionChecker,
@@ -1010,10 +1199,13 @@ export {
1010
1199
  TokenManager,
1011
1200
  WalletUtils,
1012
1201
  canAccessAdmin,
1202
+ createBrowserClient,
1013
1203
  createPermissionChecker,
1014
1204
  createSecureMessageRequestSchema,
1205
+ createServerClient,
1015
1206
  index_default as default,
1016
1207
  defaultPermissionChecker,
1208
+ detectKeyType,
1017
1209
  getRoleAwareErrorMessage,
1018
1210
  getUserDisplay,
1019
1211
  getUserRole,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spaps-sdk",
3
- "version": "1.1.52",
3
+ "version": "1.2.1",
4
4
  "description": "Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking and role-based access control",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -44,7 +44,7 @@
44
44
  "email": "buildooor@gmail.com"
45
45
  },
46
46
  "dependencies": {
47
- "spaps-types": "^1.0.57",
47
+ "spaps-types": "^1.0.59",
48
48
  "axios": "^1.6.0",
49
49
  "cross-fetch": "^4.0.0"
50
50
  },