@foru-ms/sdk 1.1.1 → 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 +293 -11
- package/dist/Client.d.ts +63 -4
- package/dist/Client.js +124 -22
- package/dist/errors.d.ts +52 -0
- package/dist/errors.js +95 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/resources/Integrations.d.ts +7 -0
- package/dist/resources/Integrations.js +6 -0
- package/dist/resources/Posts.d.ts +12 -0
- package/dist/resources/Posts.js +44 -0
- package/dist/resources/PrivateMessages.d.ts +4 -0
- package/dist/resources/PrivateMessages.js +6 -0
- package/dist/resources/SSO.d.ts +10 -0
- package/dist/resources/SSO.js +11 -0
- package/dist/resources/Tags.d.ts +8 -0
- package/dist/resources/Tags.js +24 -0
- package/dist/resources/Threads.d.ts +20 -0
- package/dist/resources/Threads.js +85 -0
- package/dist/resources/Users.d.ts +10 -0
- package/dist/resources/Users.js +26 -0
- package/dist/resources/Webhooks.d.ts +69 -0
- package/dist/resources/Webhooks.js +115 -0
- package/dist/response-types.d.ts +105 -0
- package/dist/response-types.js +2 -0
- package/dist/utils.d.ts +80 -0
- package/dist/utils.js +138 -0
- package/examples/README.md +38 -0
- package/examples/authentication.ts +79 -0
- package/examples/error-handling.ts +133 -0
- package/examples/managing-threads.ts +130 -0
- package/examples/pagination.ts +81 -0
- package/examples/webhooks.ts +176 -0
- package/package.json +1 -1
- package/src/Client.ts +165 -25
- package/src/errors.ts +95 -0
- package/src/index.ts +3 -0
- package/src/resources/Integrations.ts +11 -0
- package/src/resources/Posts.ts +56 -0
- package/src/resources/PrivateMessages.ts +10 -0
- package/src/resources/SSO.ts +17 -0
- package/src/resources/Tags.ts +32 -0
- package/src/resources/Threads.ts +109 -0
- package/src/resources/Users.ts +36 -0
- package/src/resources/Webhooks.ts +131 -0
- package/src/response-types.ts +113 -0
- package/src/utils.ts +182 -0
package/README.md
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
# @foru-ms/sdk
|
|
2
2
|
|
|
3
|
-
The official JavaScript/TypeScript SDK for [Foru.ms](https://foru.ms). Build powerful community features directly into your application.
|
|
3
|
+
The official JavaScript/ TypeScript SDK for [Foru.ms](https://foru.ms). Build powerful community features directly into your application.
|
|
4
4
|
|
|
5
|
-
This SDK is fully typed and provides a comprehensive interface to the Foru.ms API.
|
|
5
|
+
This SDK is fully typed and provides a comprehensive interface to the Foru.ms API with built-in error handling, automatic retries, and pagination helpers.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
**Fully Typed** - Complete TypeScript support with detailed type definitions
|
|
10
|
+
**Auto Retry** - Automatic retry logic for rate limits and server errors
|
|
11
|
+
**Easy Pagination** - Built-in helpers for cursor-based pagination
|
|
12
|
+
**Rich Error Classes** - Specific error types for better error handling
|
|
13
|
+
**Webhook Verification** - Built-in signature verification for webhooks
|
|
14
|
+
**Rate Limit Tracking** - Automatic tracking of API rate limits
|
|
15
|
+
**Comprehensive Examples** - Detailed examples for common use cases
|
|
6
16
|
|
|
7
17
|
## Installation
|
|
8
18
|
|
|
@@ -14,20 +24,156 @@ yarn add @foru-ms/sdk
|
|
|
14
24
|
pnpm add @foru-ms/sdk
|
|
15
25
|
```
|
|
16
26
|
|
|
17
|
-
##
|
|
27
|
+
## Quick Start
|
|
18
28
|
|
|
19
29
|
```typescript
|
|
20
30
|
import { ForumClient } from '@foru-ms/sdk';
|
|
21
31
|
|
|
22
32
|
const client = new ForumClient({
|
|
23
33
|
apiKey: 'your_api_key',
|
|
24
|
-
|
|
34
|
+
baseUrl: 'https://api.foru.ms/v1', // Optional
|
|
35
|
+
maxRetries: 3, // Optional, default: 3
|
|
36
|
+
enableRetry: true, // Optional, default: true
|
|
25
37
|
});
|
|
26
38
|
|
|
27
39
|
// Set an authentication token (JWT) for user-scoped requests
|
|
28
40
|
client.setToken('user_jwt_token');
|
|
41
|
+
|
|
42
|
+
// Create a thread
|
|
43
|
+
const thread = await client.threads.create({
|
|
44
|
+
title: 'My First Thread',
|
|
45
|
+
body: 'Hello, Foru.ms!',
|
|
46
|
+
userId: 'user-123',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// List threads with auto-pagination
|
|
50
|
+
for await (const thread of client.pagination.paginateAll(
|
|
51
|
+
(cursor) => client.threads.list({ cursor })
|
|
52
|
+
)) {
|
|
53
|
+
console.log(thread.title);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Error Handling
|
|
58
|
+
|
|
59
|
+
The SDK provides specific error classes for different scenarios:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import {
|
|
63
|
+
ForumClient,
|
|
64
|
+
AuthenticationError,
|
|
65
|
+
NotFoundError,
|
|
66
|
+
RateLimitError,
|
|
67
|
+
ValidationError
|
|
68
|
+
} from '@foru-ms/sdk';
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await client.threads.retrieve('thread-id');
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof NotFoundError) {
|
|
74
|
+
console.log('Thread not found');
|
|
75
|
+
} else if (error instanceof RateLimitError) {
|
|
76
|
+
console.log('Rate limited, retry after:', error.retryAfter);
|
|
77
|
+
} else if (error instanceof AuthenticationError) {
|
|
78
|
+
console.log('Authentication failed');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Available Error Classes:**
|
|
84
|
+
- `ForumAPIError` - Base class for all API errors
|
|
85
|
+
- `AuthenticationError` - 401 errors
|
|
86
|
+
- `AuthorizationError` - 403 errors
|
|
87
|
+
- `NotFoundError` - 404 errors
|
|
88
|
+
- `ValidationError` - 422 errors
|
|
89
|
+
- `RateLimitError` - 429 errors
|
|
90
|
+
- `ServerError` - 5xx errors
|
|
91
|
+
- `NetworkError` - Network/connection errors
|
|
92
|
+
|
|
93
|
+
## Pagination
|
|
94
|
+
|
|
95
|
+
The SDK provides multiple ways to handle pagination:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// Auto-pagination with async iterator
|
|
99
|
+
for await (const thread of client.pagination.paginateAll(
|
|
100
|
+
(cursor) => client.threads.list({ cursor, filter: 'newest' })
|
|
101
|
+
)) {
|
|
102
|
+
console.log(thread.title);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Fetch all pages at once
|
|
106
|
+
const allThreads = await client.pagination.fetchAllPages(
|
|
107
|
+
(cursor) => client.threads.list({ cursor }),
|
|
108
|
+
5 // max pages
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Manual pagination
|
|
112
|
+
let cursor: string | undefined;
|
|
113
|
+
do {
|
|
114
|
+
const response = await client.threads.list({ cursor });
|
|
115
|
+
// Process threads...
|
|
116
|
+
cursor = response.nextThreadCursor;
|
|
117
|
+
} while (cursor);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Webhooks
|
|
121
|
+
|
|
122
|
+
Verify webhook signatures for security. All webhooks include:
|
|
123
|
+
- `X-Webhook-Signature`: HMAC-SHA256 signature
|
|
124
|
+
- `X-Webhook-Timestamp`: Unix timestamp (milliseconds)
|
|
125
|
+
- `X-Webhook-Event`: Event type
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
app.post('/webhooks/foru', (req, res) => {
|
|
129
|
+
const signature = req.headers['x-webhook-signature'];
|
|
130
|
+
const timestamp = req.headers['x-webhook-timestamp'];
|
|
131
|
+
const event = req.headers['x-webhook-event'];
|
|
132
|
+
|
|
133
|
+
// Verify signature with timestamp (prevents replay attacks)
|
|
134
|
+
const isValid = client.webhooks.verifySignature(
|
|
135
|
+
req.body,
|
|
136
|
+
signature,
|
|
137
|
+
timestamp,
|
|
138
|
+
'your_webhook_secret'
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!isValid) {
|
|
142
|
+
return res.status(401).send('Invalid signature');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Process webhook...
|
|
146
|
+
console.log('Event:', event);
|
|
147
|
+
res.send('OK');
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Rate Limiting
|
|
152
|
+
|
|
153
|
+
Track rate limit information automatically:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
await client.threads.list();
|
|
157
|
+
|
|
158
|
+
if (client.lastRateLimitInfo) {
|
|
159
|
+
console.log('Limit:', client.lastRateLimitInfo.limit);
|
|
160
|
+
console.log('Remaining:', client.lastRateLimitInfo.remaining);
|
|
161
|
+
console.log('Resets at:', new Date(client.lastRateLimitInfo.reset * 1000));
|
|
162
|
+
}
|
|
29
163
|
```
|
|
30
164
|
|
|
165
|
+
## Examples
|
|
166
|
+
|
|
167
|
+
Check the `/examples` directory for detailed examples:
|
|
168
|
+
|
|
169
|
+
- [Authentication](./examples/authentication.ts) - Login, registration, token management
|
|
170
|
+
- [Managing Threads](./examples/managing-threads.ts) - CRUD operations, polls, interactions
|
|
171
|
+
- [Pagination](./examples/pagination.ts) - Different pagination strategies
|
|
172
|
+
- [Error Handling](./examples/error-handling.ts) - Comprehensive error handling
|
|
173
|
+
- [Webhooks](./examples/webhooks.ts) - Setup and verification
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
31
177
|
## API Reference
|
|
32
178
|
|
|
33
179
|
### Auth (`client.auth`)
|
|
@@ -48,10 +194,20 @@ client.setToken('user_jwt_token');
|
|
|
48
194
|
* `getPosts(id: string, params: { cursor?: string; filter?: 'newest' | 'oldest' })`: Get posts in a thread.
|
|
49
195
|
* `like(id: string, userId: string, extendedData?: any)`: Like a thread.
|
|
50
196
|
* `unlike(id: string, userId: string)`: Unlike a thread.
|
|
197
|
+
* `getLikes(id: string, params?: { cursor?: string })`: Get users who liked a thread.
|
|
51
198
|
* `dislike(id: string, userId: string, extendedData?: any)`: Dislike a thread.
|
|
52
199
|
* `undislike(id: string, userId: string)`: Remove dislike from a thread.
|
|
200
|
+
* `getDislikes(id: string, params?: { cursor?: string })`: Get users who disliked a thread.
|
|
53
201
|
* `subscribe(id: string, userId: string)`: Subscribe to a thread.
|
|
54
202
|
* `unsubscribe(id: string, userId: string)`: Unsubscribe from a thread.
|
|
203
|
+
* `getSubscribers(id: string, params?: { cursor?: string })`: Get users subscribed to a thread.
|
|
204
|
+
* `upvote(id: string, userId: string, extendedData?: any)`: Upvote a thread.
|
|
205
|
+
* `unupvote(id: string, userId: string)`: Remove upvote from a thread.
|
|
206
|
+
* `getUpvotes(id: string, params?: { cursor?: string })`: Get users who upvoted a thread.
|
|
207
|
+
* `downvote(id: string, userId: string, extendedData?: any)`: Downvote a thread.
|
|
208
|
+
* `undownvote(id: string, userId: string)`: Remove downvote from a thread.
|
|
209
|
+
* `getDownvotes(id: string, params?: { cursor?: string })`: Get users who downvoted a thread.
|
|
210
|
+
* `getPoll(id: string, userId?: string)`: Get poll results.
|
|
55
211
|
* `vote(id: string, userId: string, optionId: string)`: Vote in a thread poll.
|
|
56
212
|
* `voteUpdate(id: string, userId: string, optionId: string)`: Change vote.
|
|
57
213
|
* `unvote(id: string, userId: string)`: Remove vote.
|
|
@@ -66,12 +222,16 @@ client.setToken('user_jwt_token');
|
|
|
66
222
|
* `getChildren(id: string, params: { cursor?: string; filter?: 'newest' | 'oldest' })`: Get child posts (nested replies).
|
|
67
223
|
* `like(id: string, userId: string, extendedData?: any)`: Like a post.
|
|
68
224
|
* `unlike(id: string, userId: string)`: Unlike a post.
|
|
225
|
+
* `getLikes(id: string, params?: { cursor?: string })`: Get users who liked a post.
|
|
69
226
|
* `dislike(id: string, userId: string, extendedData?: any)`: Dislike a post.
|
|
70
227
|
* `undislike(id: string, userId: string)`: Remove dislike.
|
|
228
|
+
* `getDislikes(id: string, params?: { cursor?: string })`: Get users who disliked a post.
|
|
71
229
|
* `upvote(id: string, userId: string, extendedData?: any)`: Upvote a post.
|
|
72
230
|
* `unupvote(id: string, userId: string)`: Remove upvote.
|
|
231
|
+
* `getUpvotes(id: string, params?: { cursor?: string })`: Get users who upvoted a post.
|
|
73
232
|
* `downvote(id: string, userId: string, extendedData?: any)`: Downvote a post.
|
|
74
233
|
* `undownvote(id: string, userId: string)`: Remove downvote.
|
|
234
|
+
* `getDownvotes(id: string, params?: { cursor?: string })`: Get users who downvoted a post.
|
|
75
235
|
|
|
76
236
|
### Users (`client.users`)
|
|
77
237
|
|
|
@@ -80,6 +240,8 @@ client.setToken('user_jwt_token');
|
|
|
80
240
|
* `create(payload: { username: string; email: string; password: string; displayName?: string; emailVerified?: boolean; roles?: string[]; bio?: string; signature?: string; url?: string; extendedData?: Record<string, any> })`: Create a user (Admin).
|
|
81
241
|
* `update(id: string, payload: { username?: string; email?: string; password?: string; displayName?: string; emailVerified?: boolean; roles?: string[]; bio?: string; signature?: string; url?: string; extendedData?: Record<string, any> })`: Update a user.
|
|
82
242
|
* `delete(id: string)`: Delete a user.
|
|
243
|
+
* `getThreads(id: string, params?: { query?: string; cursor?: string; filter?: 'newest' | 'oldest' })`: Get all threads created by a user.
|
|
244
|
+
* `getPosts(id: string, params?: { query?: string; cursor?: string; filter?: 'newest' | 'oldest' })`: Get all posts created by a user.
|
|
83
245
|
* `getFollowers(id: string, params?: { query?: string; cursor?: string; filter?: 'newest' | 'oldest' })`: Get user's followers.
|
|
84
246
|
* `getFollowing(id: string, params?: { query?: string; cursor?: string; filter?: 'newest' | 'oldest' })`: Get who a user follows.
|
|
85
247
|
* `follow(id: string, followerId: string, extendedData?: any)`: Follow a user.
|
|
@@ -93,8 +255,10 @@ client.setToken('user_jwt_token');
|
|
|
93
255
|
* `retrieve(id: string, params?: { userId?: string })`: Get a tag.
|
|
94
256
|
* `update(id: string, payload: { name?: string; description?: string; color?: string; extendedData?: Record<string, any> })`: Update a tag.
|
|
95
257
|
* `delete(id: string)`: Delete a tag.
|
|
258
|
+
* `getThreads(id: string, params?: { query?: string; cursor?: string; filter?: 'newest' | 'oldest' })`: Get all threads with a specific tag.
|
|
96
259
|
* `subscribe(id: string, userId: string)`: Subscribe to a tag.
|
|
97
260
|
* `unsubscribe(id: string, userId: string)`: Unsubscribe from a tag.
|
|
261
|
+
* `getSubscribers(id: string, params?: { cursor?: string })`: Get users subscribed to a tag.
|
|
98
262
|
* `listSubscribed(params: { userId: string; query?: string; cursor?: string })`: List tags a user is subscribed to.
|
|
99
263
|
|
|
100
264
|
|
|
@@ -129,6 +293,7 @@ client.setToken('user_jwt_token');
|
|
|
129
293
|
* `list()`: Get all configured integrations.
|
|
130
294
|
* `create(payload: { type: 'SLACK' | 'DISCORD' | 'SALESFORCE' | 'HUBSPOT' | 'OKTA' | 'AUTH0'; name: string; config: any })`: Configure an integration (Slack, Discord, etc.).
|
|
131
295
|
* `retrieve(id: string)`: Get integration details.
|
|
296
|
+
* `update(id: string, payload: { name?: string; config?: any; active?: boolean })`: Update an integration.
|
|
132
297
|
* `delete(id: string)`: Remove an integration.
|
|
133
298
|
|
|
134
299
|
### Private Messages (`client.privateMessages`)
|
|
@@ -137,6 +302,7 @@ client.setToken('user_jwt_token');
|
|
|
137
302
|
* `create(payload: { title?: string; body: string; recipientId: string; senderId?: string; extendedData?: Record<string, any> })`: Send a direct message.
|
|
138
303
|
* `retrieve(id: string)`: Get a message thread.
|
|
139
304
|
* `reply(id: string, payload: { body: string; senderId: string; recipientId: string; extendedData?: Record<string, any> })`: Reply to a message.
|
|
305
|
+
* `update(id: string, payload: { read?: boolean; extendedData?: Record<string, any> })`: Update a message (e.g., mark as read).
|
|
140
306
|
* `delete(id: string)`: Delete a message.
|
|
141
307
|
|
|
142
308
|
### Reports (`client.reports`)
|
|
@@ -161,6 +327,8 @@ client.setToken('user_jwt_token');
|
|
|
161
327
|
|
|
162
328
|
* `list()`: List SSO providers.
|
|
163
329
|
* `create(payload: { provider: 'OKTA' | 'AUTH0' | 'SAML'; domain: string; config: any })`: Configure SSO.
|
|
330
|
+
* `retrieve(id: string)`: Get SSO provider details.
|
|
331
|
+
* `update(id: string, payload: { domain?: string; config?: any; active?: boolean })`: Update SSO configuration.
|
|
164
332
|
* `delete(id: string)`: Remove SSO provider.
|
|
165
333
|
|
|
166
334
|
## Types
|
|
@@ -230,22 +398,136 @@ import {
|
|
|
230
398
|
|
|
231
399
|
// Utility Types
|
|
232
400
|
PaginatedResponse,
|
|
233
|
-
InteractionType
|
|
401
|
+
InteractionType,
|
|
402
|
+
|
|
403
|
+
// Error Classes
|
|
404
|
+
ForumAPIError,
|
|
405
|
+
AuthenticationError,
|
|
406
|
+
AuthorizationError,
|
|
407
|
+
NotFoundError,
|
|
408
|
+
ValidationError,
|
|
409
|
+
RateLimitError,
|
|
410
|
+
ServerError,
|
|
411
|
+
NetworkError,
|
|
412
|
+
|
|
413
|
+
// Response Types
|
|
414
|
+
RateLimitInfo,
|
|
415
|
+
ResponseMetadata,
|
|
416
|
+
APIResponse,
|
|
417
|
+
InteractionListResponse,
|
|
418
|
+
PollResponse,
|
|
419
|
+
PollOption,
|
|
420
|
+
|
|
421
|
+
// Utilities
|
|
422
|
+
PaginationHelper,
|
|
423
|
+
Validator,
|
|
424
|
+
RetryHelper
|
|
234
425
|
} from '@foru-ms/sdk';
|
|
235
426
|
```
|
|
236
427
|
|
|
237
|
-
##
|
|
428
|
+
## Advanced Usage
|
|
238
429
|
|
|
239
|
-
|
|
430
|
+
### Custom Client Options
|
|
240
431
|
|
|
241
432
|
```typescript
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
433
|
+
const client = new ForumClient({
|
|
434
|
+
apiKey: 'your_api_key',
|
|
435
|
+
baseUrl: 'https://api.foru.ms/v1',
|
|
436
|
+
maxRetries: 5, // Increase retry attempts
|
|
437
|
+
enableRetry: true, // Enable auto-retry (default: true)
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Access rate limit info after any request
|
|
441
|
+
await client.threads.list();
|
|
442
|
+
console.log('Rate limit:', client.lastRateLimitInfo);
|
|
443
|
+
|
|
444
|
+
// Check authentication status
|
|
445
|
+
if (client.isAuthenticated()) {
|
|
446
|
+
const user = await client.auth.me();
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Async Iteration for Large Datasets
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
// Automatically fetch all pages
|
|
454
|
+
for await (const user of client.pagination.paginateAll(
|
|
455
|
+
(cursor) => client.users.list({ cursor })
|
|
456
|
+
)) {
|
|
457
|
+
await processUser(user);
|
|
458
|
+
// Pagination happens automatically in the background
|
|
246
459
|
}
|
|
247
460
|
```
|
|
248
461
|
|
|
462
|
+
### Batch Operations
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
// Fetch multiple items efficiently
|
|
466
|
+
const threadIds = ['id1', 'id2', 'id3'];
|
|
467
|
+
const threads = await Promise.all(
|
|
468
|
+
threadIds.map(id => client.threads.retrieve(id))
|
|
469
|
+
);
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Best Practices
|
|
473
|
+
|
|
474
|
+
1. **Error Handling**: Always use try-catch with specific error types
|
|
475
|
+
2. **Rate Limits**: Monitor `client.lastRateLimitInfo` for API usage
|
|
476
|
+
3. **Webhooks**: Always verify signatures to prevent unauthorized requests
|
|
477
|
+
4. **Pagination**: Use async iterators for large result sets
|
|
478
|
+
5. **Authentication**: Store tokens securely, never in client-side code
|
|
479
|
+
|
|
480
|
+
## Troubleshooting
|
|
481
|
+
|
|
482
|
+
### Rate Limit Errors
|
|
483
|
+
|
|
484
|
+
If you encounter rate limit errors frequently, consider:
|
|
485
|
+
- Implementing caching for frequently accessed data
|
|
486
|
+
- Using pagination helpers to avoid fetching too much data
|
|
487
|
+
- Batching operations where possible
|
|
488
|
+
|
|
489
|
+
### Authentication Issues
|
|
490
|
+
|
|
491
|
+
- Ensure your API key is valid and active
|
|
492
|
+
- Check that tokens haven't expired
|
|
493
|
+
- Use `client.isAuthenticated()` to verify authentication status
|
|
494
|
+
|
|
495
|
+
### Webhook Verification Failures
|
|
496
|
+
|
|
497
|
+
- Verify you're using the correct webhook secret
|
|
498
|
+
- Check that timestamps aren't too old (default: 5 minutes)
|
|
499
|
+
- Ensure you're passing the raw payload object, not a string
|
|
500
|
+
|
|
501
|
+
## Contributing
|
|
502
|
+
|
|
503
|
+
We welcome contributions! Please see our contributing guidelines for more information.
|
|
504
|
+
|
|
505
|
+
## Support
|
|
506
|
+
|
|
507
|
+
- Support: https://foru.ms/support
|
|
508
|
+
- Documentation: https://docs.foru.ms
|
|
509
|
+
- Issues: https://github.com/foru-ms/sdk/issues
|
|
510
|
+
|
|
511
|
+
## Changelog
|
|
512
|
+
|
|
513
|
+
### v1.2.1
|
|
514
|
+
- README.md formatting and updates
|
|
515
|
+
|
|
516
|
+
### v1.2.0
|
|
517
|
+
- Added custom error classes for better error handling
|
|
518
|
+
- Added automatic retry logic with exponential backoff
|
|
519
|
+
- Added pagination helpers for easy iteration
|
|
520
|
+
- Added webhook signature verification
|
|
521
|
+
- Added 22 new endpoints
|
|
522
|
+
- Enhanced documentation and examples
|
|
523
|
+
|
|
524
|
+
### v1.1.0
|
|
525
|
+
- Added 22 new endpoints
|
|
526
|
+
- Enhanced documentation and examples
|
|
527
|
+
|
|
528
|
+
### v1.0.0
|
|
529
|
+
- Initial SDK release with core functionality
|
|
530
|
+
|
|
249
531
|
## License
|
|
250
532
|
|
|
251
533
|
MIT
|
package/dist/Client.d.ts
CHANGED
|
@@ -12,10 +12,42 @@ import { PrivateMessagesResource } from './resources/PrivateMessages';
|
|
|
12
12
|
import { ReportsResource } from './resources/Reports';
|
|
13
13
|
import { RolesResource } from './resources/Roles';
|
|
14
14
|
import { SSOResource } from './resources/SSO';
|
|
15
|
+
import { PaginationHelper } from './utils';
|
|
16
|
+
import { RateLimitInfo } from './response-types';
|
|
17
|
+
export interface ClientOptions {
|
|
18
|
+
/** API key for authentication */
|
|
19
|
+
apiKey: string;
|
|
20
|
+
/** Base URL for the API (default: https://api.foru.ms/v1) */
|
|
21
|
+
baseUrl?: string;
|
|
22
|
+
/** Maximum number of retry attempts for failed requests (default: 3) */
|
|
23
|
+
maxRetries?: number;
|
|
24
|
+
/** Enable automatic retry for rate limits and server errors (default: true) */
|
|
25
|
+
enableRetry?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Main client for interacting with the Foru.ms API
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const client = new ForumClient({
|
|
32
|
+
* apiKey: 'your_api_key',
|
|
33
|
+
* baseUrl: 'https://api.foru.ms/v1'
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Set user token
|
|
37
|
+
* client.setToken('user_jwt_token');
|
|
38
|
+
*
|
|
39
|
+
* // Use resources
|
|
40
|
+
* const threads = await client.threads.list();
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
15
43
|
export declare class ForumClient {
|
|
16
44
|
apiKey: string;
|
|
17
45
|
token: string | null;
|
|
18
46
|
baseUrl: string;
|
|
47
|
+
maxRetries: number;
|
|
48
|
+
enableRetry: boolean;
|
|
49
|
+
/** Last known rate limit info */
|
|
50
|
+
lastRateLimitInfo?: RateLimitInfo;
|
|
19
51
|
auth: AuthResource;
|
|
20
52
|
threads: ThreadsResource;
|
|
21
53
|
posts: PostsResource;
|
|
@@ -30,10 +62,37 @@ export declare class ForumClient {
|
|
|
30
62
|
reports: ReportsResource;
|
|
31
63
|
roles: RolesResource;
|
|
32
64
|
sso: SSOResource;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
65
|
+
/** Pagination helper for auto-paginating through results */
|
|
66
|
+
pagination: PaginationHelper;
|
|
67
|
+
constructor(options: ClientOptions);
|
|
68
|
+
/**
|
|
69
|
+
* Make an HTTP request to the API
|
|
70
|
+
* @param path - API endpoint path
|
|
71
|
+
* @param options - Fetch options
|
|
72
|
+
* @returns Promise resolving to the response data
|
|
73
|
+
* @throws {ForumAPIError} When the API returns an error
|
|
74
|
+
* @throws {NetworkError} When the network request fails
|
|
75
|
+
*/
|
|
37
76
|
request<T>(path: string, options?: RequestInit): Promise<T>;
|
|
77
|
+
/**
|
|
78
|
+
* Set the authentication token for user-scoped requests
|
|
79
|
+
* @param token - JWT token
|
|
80
|
+
*/
|
|
38
81
|
setToken(token: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Clear the authentication token
|
|
84
|
+
*/
|
|
85
|
+
clearToken(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Check if client is authenticated
|
|
88
|
+
*/
|
|
89
|
+
isAuthenticated(): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Extract and store rate limit information from response headers
|
|
92
|
+
*/
|
|
93
|
+
private extractRateLimitInfo;
|
|
94
|
+
/**
|
|
95
|
+
* Handle error responses by throwing appropriate error types
|
|
96
|
+
*/
|
|
97
|
+
private handleErrorResponse;
|
|
39
98
|
}
|
package/dist/Client.js
CHANGED
|
@@ -15,13 +15,34 @@ const PrivateMessages_1 = require("./resources/PrivateMessages");
|
|
|
15
15
|
const Reports_1 = require("./resources/Reports");
|
|
16
16
|
const Roles_1 = require("./resources/Roles");
|
|
17
17
|
const SSO_1 = require("./resources/SSO");
|
|
18
|
+
const errors_1 = require("./errors");
|
|
19
|
+
const utils_1 = require("./utils");
|
|
18
20
|
// Polyfill fetch if needed (e.g. older Node versions)
|
|
19
21
|
const fetch = globalThis.fetch || require('cross-fetch');
|
|
22
|
+
/**
|
|
23
|
+
* Main client for interacting with the Foru.ms API
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const client = new ForumClient({
|
|
27
|
+
* apiKey: 'your_api_key',
|
|
28
|
+
* baseUrl: 'https://api.foru.ms/v1'
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Set user token
|
|
32
|
+
* client.setToken('user_jwt_token');
|
|
33
|
+
*
|
|
34
|
+
* // Use resources
|
|
35
|
+
* const threads = await client.threads.list();
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
20
38
|
class ForumClient {
|
|
21
39
|
constructor(options) {
|
|
40
|
+
var _a, _b;
|
|
22
41
|
this.token = null;
|
|
23
42
|
this.apiKey = options.apiKey;
|
|
24
43
|
this.baseUrl = options.baseUrl || 'https://api.foru.ms/v1';
|
|
44
|
+
this.maxRetries = (_a = options.maxRetries) !== null && _a !== void 0 ? _a : 3;
|
|
45
|
+
this.enableRetry = (_b = options.enableRetry) !== null && _b !== void 0 ? _b : true;
|
|
25
46
|
this.auth = new Auth_1.AuthResource(this);
|
|
26
47
|
this.threads = new Threads_1.ThreadsResource(this);
|
|
27
48
|
this.posts = new Posts_1.PostsResource(this);
|
|
@@ -36,35 +57,116 @@ class ForumClient {
|
|
|
36
57
|
this.reports = new Reports_1.ReportsResource(this);
|
|
37
58
|
this.roles = new Roles_1.RolesResource(this);
|
|
38
59
|
this.sso = new SSO_1.SSOResource(this);
|
|
60
|
+
this.pagination = new utils_1.PaginationHelper();
|
|
39
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Make an HTTP request to the API
|
|
64
|
+
* @param path - API endpoint path
|
|
65
|
+
* @param options - Fetch options
|
|
66
|
+
* @returns Promise resolving to the response data
|
|
67
|
+
* @throws {ForumAPIError} When the API returns an error
|
|
68
|
+
* @throws {NetworkError} When the network request fails
|
|
69
|
+
*/
|
|
40
70
|
async request(path, options = {}) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
const makeRequest = async () => {
|
|
72
|
+
const headers = {
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
'x-api-key': this.apiKey,
|
|
75
|
+
...options.headers,
|
|
76
|
+
};
|
|
77
|
+
if (this.token) {
|
|
78
|
+
headers['Authorization'] = `Bearer ${this.token}`;
|
|
79
|
+
}
|
|
80
|
+
let response;
|
|
81
|
+
try {
|
|
82
|
+
response = await fetch(`${this.baseUrl}${path}`, {
|
|
83
|
+
...options,
|
|
84
|
+
headers,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
throw new errors_1.NetworkError('Network request failed', error);
|
|
89
|
+
}
|
|
90
|
+
// Extract rate limit info
|
|
91
|
+
this.extractRateLimitInfo(response);
|
|
92
|
+
const contentType = response.headers.get('content-type');
|
|
93
|
+
let data;
|
|
94
|
+
if (contentType && contentType.includes('application/json')) {
|
|
95
|
+
data = await response.json();
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
data = await response.text();
|
|
99
|
+
}
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
this.handleErrorResponse(response.status, data);
|
|
102
|
+
}
|
|
103
|
+
return data;
|
|
45
104
|
};
|
|
46
|
-
if
|
|
47
|
-
|
|
105
|
+
// Apply retry logic if enabled
|
|
106
|
+
if (this.enableRetry) {
|
|
107
|
+
return utils_1.RetryHelper.withRetry(makeRequest, this.maxRetries);
|
|
48
108
|
}
|
|
49
|
-
|
|
50
|
-
...options,
|
|
51
|
-
headers,
|
|
52
|
-
});
|
|
53
|
-
const contentType = response.headers.get('content-type');
|
|
54
|
-
let data;
|
|
55
|
-
if (contentType && contentType.includes('application/json')) {
|
|
56
|
-
data = await response.json();
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
data = await response.text();
|
|
60
|
-
}
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
throw new Error((data && data.message) || data.error || 'API Error');
|
|
63
|
-
}
|
|
64
|
-
return data;
|
|
109
|
+
return makeRequest();
|
|
65
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Set the authentication token for user-scoped requests
|
|
113
|
+
* @param token - JWT token
|
|
114
|
+
*/
|
|
66
115
|
setToken(token) {
|
|
67
116
|
this.token = token;
|
|
68
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Clear the authentication token
|
|
120
|
+
*/
|
|
121
|
+
clearToken() {
|
|
122
|
+
this.token = null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check if client is authenticated
|
|
126
|
+
*/
|
|
127
|
+
isAuthenticated() {
|
|
128
|
+
return this.token !== null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Extract and store rate limit information from response headers
|
|
132
|
+
*/
|
|
133
|
+
extractRateLimitInfo(response) {
|
|
134
|
+
const limit = response.headers.get('x-ratelimit-limit');
|
|
135
|
+
const remaining = response.headers.get('x-ratelimit-remaining');
|
|
136
|
+
const reset = response.headers.get('x-ratelimit-reset');
|
|
137
|
+
if (limit && remaining && reset) {
|
|
138
|
+
this.lastRateLimitInfo = {
|
|
139
|
+
limit: parseInt(limit, 10),
|
|
140
|
+
remaining: parseInt(remaining, 10),
|
|
141
|
+
reset: parseInt(reset, 10),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Handle error responses by throwing appropriate error types
|
|
147
|
+
*/
|
|
148
|
+
handleErrorResponse(status, data) {
|
|
149
|
+
const message = (data && data.message) || data.error || 'API Error';
|
|
150
|
+
switch (status) {
|
|
151
|
+
case 401:
|
|
152
|
+
throw new errors_1.AuthenticationError(message, data);
|
|
153
|
+
case 403:
|
|
154
|
+
throw new errors_1.AuthorizationError(message, data);
|
|
155
|
+
case 404:
|
|
156
|
+
throw new errors_1.NotFoundError(message, data);
|
|
157
|
+
case 422:
|
|
158
|
+
throw new errors_1.ValidationError(message, data);
|
|
159
|
+
case 429:
|
|
160
|
+
const retryAfter = data.retryAfter || 60;
|
|
161
|
+
throw new errors_1.RateLimitError(message, retryAfter, data);
|
|
162
|
+
case 500:
|
|
163
|
+
case 502:
|
|
164
|
+
case 503:
|
|
165
|
+
case 504:
|
|
166
|
+
throw new errors_1.ServerError(message, status, data);
|
|
167
|
+
default:
|
|
168
|
+
throw new errors_1.ForumAPIError(message, status, data);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
69
171
|
}
|
|
70
172
|
exports.ForumClient = ForumClient;
|