@basictech/react 0.7.0 → 0.8.0-beta.2

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.
@@ -59,23 +59,26 @@ export class RemoteCollection<T extends { id: string } = Record<string, any> & {
59
59
 
60
60
  this.log(`${method} ${url}`, body ? JSON.stringify(body) : '')
61
61
 
62
+ const headers: Record<string, string> = {
63
+ 'Authorization': `Bearer ${token}`
64
+ }
65
+ if (body) {
66
+ headers['Content-Type'] = 'application/json'
67
+ }
68
+
62
69
  const response = await fetch(url, {
63
70
  method,
64
- headers: {
65
- 'Content-Type': 'application/json',
66
- 'Authorization': `Bearer ${token}`
67
- },
71
+ headers,
68
72
  ...(body ? { body: JSON.stringify(body) } : {})
69
73
  })
70
74
 
71
75
  const responseData = await response.json().catch(() => ({}))
72
76
 
73
77
  if (!response.ok) {
74
- // Handle 401 Unauthorized - token may have expired
78
+ // Handle 401 Unauthorized - force refresh then retry once
75
79
  if (response.status === 401 && !isRetry) {
76
- this.log('Got 401, retrying with fresh token...')
77
- // getToken() should refresh the token if expired
78
- // Retry the request once
80
+ this.log('Got 401, forcing token refresh and retrying...')
81
+ await this.config.getToken({ forceRefresh: true })
79
82
  return this.request<R>(method, path, body, true)
80
83
  }
81
84
 
@@ -83,16 +86,27 @@ export class RemoteCollection<T extends { id: string } = Record<string, any> & {
83
86
  console.error(`[RemoteDB] Error ${response.status}:`, responseData)
84
87
  }
85
88
 
86
- // Call onAuthError callback if provided and this is an auth error
87
- if (response.status === 401 && this.config.onAuthError) {
88
- this.config.onAuthError({
89
- status: response.status,
90
- message: 'Authentication failed',
91
- response: responseData
92
- })
89
+ // Call onAuthError callback for auth/authz errors
90
+ if (this.config.onAuthError) {
91
+ if (response.status === 401) {
92
+ this.config.onAuthError({
93
+ status: response.status,
94
+ message: 'Authentication failed',
95
+ response: responseData,
96
+ errorType: 'expired',
97
+ afterRetry: isRetry,
98
+ })
99
+ } else if (response.status === 403) {
100
+ this.config.onAuthError({
101
+ status: response.status,
102
+ message: responseData.message || 'Forbidden - insufficient permissions or missing scope',
103
+ response: responseData,
104
+ errorType: 'forbidden',
105
+ afterRetry: isRetry,
106
+ })
107
+ }
93
108
  }
94
109
 
95
- // Try different error message fields that APIs commonly use
96
110
  const errorMessage = responseData.message || responseData.error || responseData.detail ||
97
111
  (typeof responseData === 'string' ? responseData : `API request failed: ${response.status}`)
98
112
  throw new RemoteDBError(errorMessage, response.status, responseData)
@@ -1,5 +1,5 @@
1
1
  // Core DB exports
2
- export type { Collection, BasicDB, DBMode, RemoteDBConfig } from './types'
2
+ export type { Collection, BasicDB, DBMode, RemoteDBConfig, GetTokenOptions } from './types'
3
3
  export type { AuthError } from './types'
4
4
  export { RemoteDBError } from './types'
5
5
  export { RemoteDB } from './RemoteDB'
@@ -91,6 +91,10 @@ export interface AuthError {
91
91
  status: number
92
92
  message: string
93
93
  response?: any
94
+ /** Classifies the error for UI display (e.g. "session expired" vs "forbidden") */
95
+ errorType: 'expired' | 'forbidden' | 'revoked' | 'network' | 'unknown'
96
+ /** True if this error occurred after a retry with a refreshed token */
97
+ afterRetry: boolean
94
98
  }
95
99
 
96
100
  /**
@@ -109,13 +113,21 @@ export class RemoteDBError extends Error {
109
113
  }
110
114
  }
111
115
 
116
+ /**
117
+ * Options for getToken (e.g. force refresh after 401)
118
+ */
119
+ export interface GetTokenOptions {
120
+ /** When true, refresh the access token before returning (e.g. after server returned 401) */
121
+ forceRefresh?: boolean
122
+ }
123
+
112
124
  /**
113
125
  * Configuration for RemoteDB
114
126
  */
115
127
  export interface RemoteDBConfig {
116
128
  serverUrl: string
117
129
  projectId: string
118
- getToken: () => Promise<string>
130
+ getToken: (options?: GetTokenOptions) => Promise<string>
119
131
  schema?: any
120
132
  /** Enable debug logging (default: false) */
121
133
  debug?: boolean