@proveanything/smartlinks 1.2.4 → 1.3.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.
@@ -0,0 +1,103 @@
1
+ import type { AblyTokenRequest, RealtimeTokenRequest } from "../types/realtime";
2
+ /**
3
+ * Real-Time Communications API
4
+ *
5
+ * Provides access to real-time communication channels using Ably.
6
+ * Supports both public (user-scoped) and admin tokens for different permission levels.
7
+ *
8
+ * Channel Patterns:
9
+ * - With appId: collection:{collectionId}:app:{appId}:*
10
+ * - Without appId: collection:{collectionId}:*
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { realtime } from '@proveanything/smartlinks'
15
+ *
16
+ * // Get token for public user
17
+ * const tokenRequest = await realtime.getPublicToken({
18
+ * collectionId: 'abc123',
19
+ * appId: 'xyz789'
20
+ * })
21
+ *
22
+ * // Initialize Ably client
23
+ * const ably = new Ably.Realtime.Promise({
24
+ * authCallback: async (data, callback) => {
25
+ * callback(null, tokenRequest)
26
+ * }
27
+ * })
28
+ *
29
+ * // Subscribe to a channel
30
+ * const channel = ably.channels.get('collection:abc123:app:xyz789:chat:room-1')
31
+ * await channel.subscribe((message) => {
32
+ * console.log('Received:', message.data)
33
+ * })
34
+ *
35
+ * // Publish a message
36
+ * await channel.publish('message', { text: 'Hello!', userId: '123' })
37
+ * ```
38
+ */
39
+ /**
40
+ * Get an Ably token for public (user-scoped) real-time communication.
41
+ *
42
+ * This endpoint returns an Ably TokenRequest that can be used to initialize an Ably client
43
+ * with appropriate permissions for the specified collection and optional app.
44
+ *
45
+ * Requires user authentication (Bearer token).
46
+ *
47
+ * @param params - Token request parameters
48
+ * @param params.collectionId - The collection scope (required)
49
+ * @param params.appId - Optional app scope for more specific permissions
50
+ * @returns Ably TokenRequest that can be used with Ably.Realtime client
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const tokenRequest = await realtime.getPublicToken({
55
+ * collectionId: 'my-collection-id',
56
+ * appId: 'my-app-id'
57
+ * })
58
+ *
59
+ * // Use with Ably
60
+ * const ably = new Ably.Realtime.Promise({
61
+ * authCallback: async (data, callback) => {
62
+ * callback(null, tokenRequest)
63
+ * }
64
+ * })
65
+ *
66
+ * // Subscribe to channels matching the pattern
67
+ * const channel = ably.channels.get('collection:my-collection-id:app:my-app-id:chat:general')
68
+ * await channel.subscribe('message', (msg) => console.log(msg.data))
69
+ * ```
70
+ */
71
+ export declare function getPublicToken(params: RealtimeTokenRequest): Promise<AblyTokenRequest>;
72
+ /**
73
+ * Get an Ably token for admin real-time communication.
74
+ *
75
+ * This endpoint returns an Ably TokenRequest that can be used to initialize an Ably client
76
+ * with admin permissions to receive system notifications and alerts.
77
+ *
78
+ * Admin users get subscribe-only (read-only) access to the interaction:{userId} channel pattern.
79
+ *
80
+ * Requires admin authentication (Bearer token).
81
+ *
82
+ * @returns Ably TokenRequest that can be used with Ably.Realtime client
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const tokenRequest = await realtime.getAdminToken()
87
+ *
88
+ * // Use with Ably
89
+ * const ably = new Ably.Realtime.Promise({
90
+ * authCallback: async (data, callback) => {
91
+ * callback(null, tokenRequest)
92
+ * }
93
+ * })
94
+ *
95
+ * // Subscribe to admin interaction channel
96
+ * const userId = 'my-user-id'
97
+ * const channel = ably.channels.get(`interaction:${userId}`)
98
+ * await channel.subscribe((message) => {
99
+ * console.log('Admin notification:', message.data)
100
+ * })
101
+ * ```
102
+ */
103
+ export declare function getAdminToken(): Promise<AblyTokenRequest>;
@@ -0,0 +1,113 @@
1
+ // src/api/realtime.ts
2
+ import { request } from "../http";
3
+ /**
4
+ * Real-Time Communications API
5
+ *
6
+ * Provides access to real-time communication channels using Ably.
7
+ * Supports both public (user-scoped) and admin tokens for different permission levels.
8
+ *
9
+ * Channel Patterns:
10
+ * - With appId: collection:{collectionId}:app:{appId}:*
11
+ * - Without appId: collection:{collectionId}:*
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { realtime } from '@proveanything/smartlinks'
16
+ *
17
+ * // Get token for public user
18
+ * const tokenRequest = await realtime.getPublicToken({
19
+ * collectionId: 'abc123',
20
+ * appId: 'xyz789'
21
+ * })
22
+ *
23
+ * // Initialize Ably client
24
+ * const ably = new Ably.Realtime.Promise({
25
+ * authCallback: async (data, callback) => {
26
+ * callback(null, tokenRequest)
27
+ * }
28
+ * })
29
+ *
30
+ * // Subscribe to a channel
31
+ * const channel = ably.channels.get('collection:abc123:app:xyz789:chat:room-1')
32
+ * await channel.subscribe((message) => {
33
+ * console.log('Received:', message.data)
34
+ * })
35
+ *
36
+ * // Publish a message
37
+ * await channel.publish('message', { text: 'Hello!', userId: '123' })
38
+ * ```
39
+ */
40
+ /**
41
+ * Get an Ably token for public (user-scoped) real-time communication.
42
+ *
43
+ * This endpoint returns an Ably TokenRequest that can be used to initialize an Ably client
44
+ * with appropriate permissions for the specified collection and optional app.
45
+ *
46
+ * Requires user authentication (Bearer token).
47
+ *
48
+ * @param params - Token request parameters
49
+ * @param params.collectionId - The collection scope (required)
50
+ * @param params.appId - Optional app scope for more specific permissions
51
+ * @returns Ably TokenRequest that can be used with Ably.Realtime client
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const tokenRequest = await realtime.getPublicToken({
56
+ * collectionId: 'my-collection-id',
57
+ * appId: 'my-app-id'
58
+ * })
59
+ *
60
+ * // Use with Ably
61
+ * const ably = new Ably.Realtime.Promise({
62
+ * authCallback: async (data, callback) => {
63
+ * callback(null, tokenRequest)
64
+ * }
65
+ * })
66
+ *
67
+ * // Subscribe to channels matching the pattern
68
+ * const channel = ably.channels.get('collection:my-collection-id:app:my-app-id:chat:general')
69
+ * await channel.subscribe('message', (msg) => console.log(msg.data))
70
+ * ```
71
+ */
72
+ export async function getPublicToken(params) {
73
+ const queryParams = new URLSearchParams();
74
+ queryParams.append('collectionId', params.collectionId);
75
+ if (params.appId) {
76
+ queryParams.append('appId', params.appId);
77
+ }
78
+ return request(`/api/public/push/token?${queryParams.toString()}`);
79
+ }
80
+ /**
81
+ * Get an Ably token for admin real-time communication.
82
+ *
83
+ * This endpoint returns an Ably TokenRequest that can be used to initialize an Ably client
84
+ * with admin permissions to receive system notifications and alerts.
85
+ *
86
+ * Admin users get subscribe-only (read-only) access to the interaction:{userId} channel pattern.
87
+ *
88
+ * Requires admin authentication (Bearer token).
89
+ *
90
+ * @returns Ably TokenRequest that can be used with Ably.Realtime client
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const tokenRequest = await realtime.getAdminToken()
95
+ *
96
+ * // Use with Ably
97
+ * const ably = new Ably.Realtime.Promise({
98
+ * authCallback: async (data, callback) => {
99
+ * callback(null, tokenRequest)
100
+ * }
101
+ * })
102
+ *
103
+ * // Subscribe to admin interaction channel
104
+ * const userId = 'my-user-id'
105
+ * const channel = ably.channels.get(`interaction:${userId}`)
106
+ * await channel.subscribe((message) => {
107
+ * console.log('Admin notification:', message.data)
108
+ * })
109
+ * ```
110
+ */
111
+ export async function getAdminToken() {
112
+ return request('/api/admin/auth/push');
113
+ }
@@ -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:12:23.404Z
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,