@proveanything/smartlinks 1.2.4 → 1.3.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.
package/README.md CHANGED
@@ -80,6 +80,79 @@ const jwt = await auth.requestAdminJWT('collectionId')
80
80
  const publicJwt = await auth.requestPublicJWT('collectionId', 'productId', 'proofId')
81
81
  ```
82
82
 
83
+ ## Error Handling
84
+
85
+ The SDK throws `SmartlinksApiError` for all API errors, providing structured access to:
86
+ - **HTTP status code** (`statusCode` / `code`) - Numeric HTTP status (400, 401, 500, etc.)
87
+ - **Server error code** (`errorCode`) - String identifier ("NOT_AUTHORIZED", "broadcasts.topic.invalid", etc.)
88
+ - **Error message** (`message`) - Human-readable description
89
+ - **Additional details** (`details`) - Server-specific fields
90
+
91
+ ### Automatic Error Normalization
92
+
93
+ The SDK automatically normalizes various server error response formats into a consistent structure:
94
+
95
+ ```ts
96
+ // Server may return errors in different formats:
97
+ // { errorText: "...", errorCode: "..." }
98
+ // { error: "...", message: "..." }
99
+ // { ok: false, error: "..." }
100
+ // { error: "..." }
101
+
102
+ // All are normalized to SmartlinksApiError with consistent access:
103
+ import { SmartlinksApiError, product } from '@proveanything/smartlinks'
104
+
105
+ try {
106
+ const item = await product.get('collectionId', 'productId', false)
107
+ } catch (error) {
108
+ if (error instanceof SmartlinksApiError) {
109
+ console.error({
110
+ message: error.message, // "Error 404: Product not found"
111
+ statusCode: error.statusCode, // 404 (HTTP status)
112
+ code: error.code, // 404 (same as statusCode)
113
+ errorCode: error.errorCode, // "NOT_FOUND" (server error code string)
114
+ details: error.details, // Additional server details
115
+ url: error.url, // Failed URL
116
+ })
117
+
118
+ // Handle specific server error codes (primary identifier)
119
+ switch (error.errorCode) {
120
+ case 'NOT_AUTHORIZED':
121
+ // Invalid credentials
122
+ break
123
+ case 'broadcasts.topic.invalid':
124
+ // Invalid broadcast topic
125
+ break
126
+ default:
127
+ // Fall back to HTTP status code
128
+ if (error.isNotFound()) {
129
+ // Handle 404
130
+ } else if (error.isAuthError()) {
131
+ // Handle 401/403 - redirect to login
132
+ }
133
+ }
134
+
135
+ // Use helper methods for HTTP status-based handling
136
+ if (error.isRateLimited()) {
137
+ // Handle 429 - implement retry logic
138
+ } else if (error.isServerError()) {
139
+ // Handle 5xx - show maintenance message
140
+ }
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### Helper Methods
146
+
147
+ - `error.isClientError()` - 4xx status codes
148
+ - `error.isServerError()` - 5xx status codes
149
+ - `error.isAuthError()` - 401 or 403
150
+ - `error.isNotFound()` - 404
151
+ - `error.isRateLimited()` - 429
152
+ - `error.toJSON()` - Serializable object for logging
153
+
154
+ For comprehensive error handling examples and migration guidance, see [examples/error-handling-demo.ts](examples/error-handling-demo.ts).
155
+
83
156
  ## Common tasks
84
157
 
85
158
  ### Products
@@ -481,7 +554,18 @@ setExtraHeaders({ 'X-Debug': '1' })
481
554
 
482
555
  Explore every function, parameter, and type here:
483
556
 
484
- - API Summary (API_SUMMARY.md)
557
+ - [API_SUMMARY.md](docs/API_SUMMARY.md) - Complete API reference
558
+
559
+ ## Additional Documentation
560
+
561
+ The SDK includes comprehensive guides for advanced features:
562
+
563
+ - **[Liquid Templates](docs/liquid-templates.md)** - Dynamic templating for emails and notifications with personalized data
564
+ - **[Real-Time Messaging](docs/realtime.md)** - Ably integration for live chat, presence, and real-time updates
565
+ - **[Theme System](docs/theme.system.md)** - Dynamic theming for iframe apps with CSS variables and postMessage
566
+ - **[Theme Defaults](docs/theme-defaults.md)** - Default theme configuration reference
567
+ - **[Widgets](docs/widgets.md)** - React widget system for embeddable components
568
+ - **[Internationalization](docs/i18n.md)** - Multi-language support and localization
485
569
 
486
570
  ## Requirements
487
571
 
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.2.4 | Generated: 2026-01-26T15:42:39.449Z
3
+ Version: 1.3.1 | Generated: 2026-01-30T18:16:59.130Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -51,6 +51,7 @@ The Smartlinks SDK is organized into the following namespaces:
51
51
  - **jobs** - Functions for jobs operations
52
52
  - **journeysAnalytics** - Functions for journeysAnalytics operations
53
53
  - **location** - Functions for location operations
54
+ - **realtime** - Functions for realtime operations
54
55
  - **template** - Functions for template operations
55
56
 
56
57
  ## HTTP Utilities
@@ -118,6 +119,194 @@ Returns the common headers used for API requests, including apiKey and bearerTok
118
119
  **sendCustomProxyMessage**(request: string, params: any) → `Promise<T>`
119
120
  Sends a custom proxy message to the parent Smartlinks application when running in an iframe. This function is used to communicate with the parent window when the SDK is embedded in an iframe and proxyMode is enabled. It sends a message to the parent and waits for a response.
120
121
 
122
+ ## Error Handling
123
+
124
+ All API functions throw `SmartlinksApiError` when requests fail. This error class provides structured access to HTTP status codes, server error codes, and additional context.
125
+
126
+ ### SmartlinksApiError
127
+
128
+ **Properties:**
129
+ - **message** `string` - Human-readable error message in English (e.g., "Error 400: Not Authorized")
130
+ - **statusCode** `number` - HTTP status code (400, 401, 404, 500, etc.)
131
+ - **code** `number` - Numeric error code (same as statusCode)
132
+ - **details** `Record<string, any> | undefined` - Additional server response data, including string error codes
133
+ - **url** `string | undefined` - The URL that was requested
134
+
135
+ **Helper Methods:**
136
+ - **isAuthError()** `boolean` - Returns true for 401 or 403 status codes
137
+ - **isNotFound()** `boolean` - Returns true for 404 status code
138
+ - **isRateLimited()** `boolean` - Returns true for 429 status code
139
+ - **isClientError()** `boolean` - Returns true for 4xx status codes
140
+ - **isServerError()** `boolean` - Returns true for 5xx status codes
141
+ - **toJSON()** `object` - Returns a serializable object for logging
142
+
143
+ ### Error Format Normalization
144
+
145
+ The SDK automatically normalizes various server error response formats into a consistent structure. The server may return errors in different formats, but they are all accessible through the same properties.
146
+
147
+ **Server String Error Codes:**
148
+ Server-specific error identifiers are preserved in `error.details`:
149
+ - Access via: `error.details?.errorCode` or `error.details?.error`
150
+ - Format examples: `"NOT_AUTHORIZED"`, `"broadcasts.topic.invalid"`, `"sendgrid.provision.failed"`
151
+ - Use these for programmatic error handling (switch statements, conditional logic)
152
+
153
+ ### Usage Examples
154
+
155
+ **Basic Error Handling:**
156
+ ```typescript
157
+ import { SmartlinksApiError, product } from '@proveanything/smartlinks'
158
+
159
+ try {
160
+ const item = await product.get('collectionId', 'productId')
161
+ } catch (error) {
162
+ if (error instanceof SmartlinksApiError) {
163
+ console.error('Status:', error.statusCode) // 404
164
+ console.error('Message:', error.message) // "Error 404: Not found"
165
+ console.error('URL:', error.url) // "/public/collection/..."
166
+ }
167
+ }
168
+ ```
169
+
170
+ **Using Helper Methods:**
171
+ ```typescript
172
+ try {
173
+ await product.create('collectionId', data)
174
+ } catch (error) {
175
+ if (error instanceof SmartlinksApiError) {
176
+ if (error.isAuthError()) {
177
+ // Handle 401/403 - redirect to login
178
+ redirectToLogin()
179
+ } else if (error.isNotFound()) {
180
+ // Handle 404
181
+ showNotFound()
182
+ } else if (error.isRateLimited()) {
183
+ // Handle 429 - implement retry with backoff
184
+ await retryAfterDelay()
185
+ } else if (error.isServerError()) {
186
+ // Handle 5xx
187
+ showMaintenanceMessage()
188
+ }
189
+ }
190
+ }
191
+ ```
192
+
193
+ **Accessing Server Error Codes:**
194
+ ```typescript
195
+ try {
196
+ await broadcasts.send('collectionId', 'broadcastId', options)
197
+ } catch (error) {
198
+ if (error instanceof SmartlinksApiError) {
199
+ // Extract server-defined string error code
200
+ const serverCode = error.details?.errorCode || error.details?.error
201
+
202
+ switch (serverCode) {
203
+ case 'NOT_AUTHORIZED':
204
+ redirectToLogin()
205
+ break
206
+ case 'broadcasts.topic.invalid':
207
+ showTopicSelector()
208
+ break
209
+ case 'sendgrid.provision.failed':
210
+ alertAdmin('Email service error')
211
+ break
212
+ default:
213
+ showError(error.message)
214
+ }
215
+ }
216
+ }
217
+ ```
218
+
219
+ **Error Logging for Monitoring:**
220
+ ```typescript
221
+ try {
222
+ await api.someMethod()
223
+ } catch (error) {
224
+ if (error instanceof SmartlinksApiError) {
225
+ // Log structured error data
226
+ logger.error('API Error', error.toJSON())
227
+
228
+ // Send to monitoring service
229
+ Sentry.captureException(error, {
230
+ extra: error.toJSON(),
231
+ tags: {
232
+ statusCode: error.statusCode,
233
+ serverErrorCode: error.details?.errorCode || error.details?.error,
234
+ }
235
+ })
236
+ }
237
+ }
238
+ ```
239
+
240
+ **Handling Validation Errors:**
241
+ ```typescript
242
+ try {
243
+ await product.create('collectionId', formData)
244
+ } catch (error) {
245
+ if (error instanceof SmartlinksApiError && error.statusCode === 400) {
246
+ // Access field-specific validation errors if available
247
+ if (error.details?.fields) {
248
+ Object.entries(error.details.fields).forEach(([field, message]) => {
249
+ showFieldError(field, String(message))
250
+ })
251
+ } else {
252
+ showError(error.message)
253
+ }
254
+ }
255
+ }
256
+ ```
257
+
258
+ **Retry Logic for Transient Errors:**
259
+ ```typescript
260
+ async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
261
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
262
+ try {
263
+ return await fn()
264
+ } catch (error) {
265
+ if (error instanceof SmartlinksApiError) {
266
+ // Only retry server errors and rate limiting
267
+ const shouldRetry = error.isServerError() || error.isRateLimited()
268
+
269
+ if (!shouldRetry || attempt === maxRetries - 1) {
270
+ throw error
271
+ }
272
+
273
+ // Exponential backoff
274
+ const delay = 1000 * Math.pow(2, attempt)
275
+ await new Promise(resolve => setTimeout(resolve, delay))
276
+ } else {
277
+ throw error
278
+ }
279
+ }
280
+ }
281
+ throw new Error('Max retries exceeded')
282
+ }
283
+
284
+ // Usage
285
+ const collections = await withRetry(() => collection.list())
286
+ ```
287
+
288
+ ### Error Code Reference
289
+
290
+ **HTTP Status Codes (numeric):**
291
+ - `400` - Bad Request (invalid input)
292
+ - `401` - Unauthorized (authentication required)
293
+ - `403` - Forbidden (insufficient permissions)
294
+ - `404` - Not Found (resource doesn't exist)
295
+ - `429` - Too Many Requests (rate limited)
296
+ - `500` - Internal Server Error
297
+ - `502` - Bad Gateway
298
+ - `503` - Service Unavailable
299
+
300
+ **Server Error Codes (strings in `details.errorCode` or `details.error`):**
301
+ Examples include:
302
+ - `"NOT_AUTHORIZED"` - Not authorized for this action
303
+ - `"broadcasts.topic.invalid"` - Invalid communication topic
304
+ - `"broadcasts.manual.segment.missing"` - Missing required segment
305
+ - `"sendgrid.provision.failed"` - Email service provisioning failed
306
+ - `"validation.failed"` - Request validation failed
307
+
308
+ *Note: Server error codes use either `UPPERCASE_UNDERSCORE` or `dotted.notation` format. Both are supported.*
309
+
121
310
  ## Types
122
311
 
123
312
  ### appConfiguration
@@ -1250,7 +1439,9 @@ interface ContactSchema {
1250
1439
  ```typescript
1251
1440
  interface ErrorResponse {
1252
1441
  code: number
1442
+ errorCode?: string
1253
1443
  message: string
1444
+ details?: Record<string, any>
1254
1445
  }
1255
1446
  ```
1256
1447
 
@@ -1830,6 +2021,31 @@ interface QrShortCodeLookupResponse {
1830
2021
  }
1831
2022
  ```
1832
2023
 
2024
+ ### realtime
2025
+
2026
+ **RealtimeTokenRequest** (interface)
2027
+ ```typescript
2028
+ interface RealtimeTokenRequest {
2029
+ collectionId: string
2030
+ appId?: string
2031
+ }
2032
+ ```
2033
+
2034
+ **AblyTokenRequest** (interface)
2035
+ ```typescript
2036
+ interface AblyTokenRequest {
2037
+ keyName: string
2038
+ ttl: number
2039
+ timestamp: number
2040
+ capability: string
2041
+ nonce: string
2042
+ mac: string
2043
+ clientId: string
2044
+ }
2045
+ ```
2046
+
2047
+ **RealtimeChannelPattern** = `string`
2048
+
1833
2049
  ### segments
1834
2050
 
1835
2051
  **SegmentRecord** (interface)
@@ -2901,6 +3117,14 @@ Get proofs for a batch (admin only). GET /admin/collection/:collectionId/product
2901
3117
  **lookupShortCode**(shortId: string, code: string) → `Promise<QrShortCodeLookupResponse>`
2902
3118
  Resolve a short code to related resource identifiers.
2903
3119
 
3120
+ ### realtime
3121
+
3122
+ **getPublicToken**(params: RealtimeTokenRequest) → `Promise<AblyTokenRequest>`
3123
+ Get an Ably token for public (user-scoped) real-time communication. This endpoint returns an Ably TokenRequest that can be used to initialize an Ably client with appropriate permissions for the specified collection and optional app. Requires user authentication (Bearer token). ```ts const tokenRequest = await realtime.getPublicToken({ collectionId: 'my-collection-id', appId: 'my-app-id' }) // Use with Ably const ably = new Ably.Realtime.Promise({ authCallback: async (data, callback) => { callback(null, tokenRequest) } }) // Subscribe to channels matching the pattern const channel = ably.channels.get('collection:my-collection-id:app:my-app-id:chat:general') await channel.subscribe('message', (msg) => console.log(msg.data)) ```
3124
+
3125
+ **getAdminToken**() → `Promise<AblyTokenRequest>`
3126
+ Get an Ably token for admin real-time communication. This endpoint returns an Ably TokenRequest that can be used to initialize an Ably client with admin permissions to receive system notifications and alerts. Admin users get subscribe-only (read-only) access to the interaction:{userId} channel pattern. Requires admin authentication (Bearer token). ```ts const tokenRequest = await realtime.getAdminToken() // Use with Ably const ably = new Ably.Realtime.Promise({ authCallback: async (data, callback) => { callback(null, tokenRequest) } }) // Subscribe to admin interaction channel const userId = 'my-user-id' const channel = ably.channels.get(`interaction:${userId}`) await channel.subscribe((message) => { console.log('Admin notification:', message.data) }) ```
3127
+
2904
3128
  ### segments
2905
3129
 
2906
3130
  **create**(collectionId: string,