perspectapi-ts-sdk 1.2.0 → 1.3.0

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
@@ -12,6 +12,7 @@ A comprehensive TypeScript SDK for PerspectAPI, designed to work seamlessly with
12
12
  - 🔑 **Multiple Auth Methods** - Support for JWT tokens and API keys
13
13
  - 📊 **Comprehensive Coverage** - All PerspectAPI endpoints supported
14
14
  - 🧩 **High-Level Loaders** - Drop-in helpers for products, content, and checkout flows with fallbacks
15
+ - 📧 **Newsletter Management** - Complete newsletter subscription system with double opt-in, preferences, and lists
15
16
 
16
17
  ## Installation
17
18
 
@@ -376,6 +377,132 @@ const submissions = await client.contact.getContactSubmissions(siteName, {
376
377
  await client.contact.updateContactStatus(siteName, submission.data.id, 'read');
377
378
  ```
378
379
 
380
+ ### Newsletter Subscriptions
381
+
382
+ ```typescript
383
+ const siteName = 'your-site-name';
384
+
385
+ // Subscribe to newsletter (with double opt-in)
386
+ const subscription = await client.newsletter.subscribe(siteName, {
387
+ email: 'subscriber@example.com',
388
+ name: 'Jane Doe',
389
+ list_ids: ['list_default'], // Optional: subscribe to specific lists
390
+ frequency: 'weekly', // instant, daily, weekly, monthly
391
+ topics: ['news', 'updates'], // Optional: topic preferences
392
+ double_opt_in: true, // Default: true for GDPR compliance
393
+ turnstile_token: 'token' // Optional: Turnstile verification
394
+ });
395
+
396
+ // Confirm subscription via email token
397
+ const confirmed = await client.newsletter.confirmSubscription(
398
+ siteName,
399
+ 'confirmation-token-from-email'
400
+ );
401
+
402
+ // Unsubscribe
403
+ const unsubscribed = await client.newsletter.unsubscribe(siteName, {
404
+ email: 'subscriber@example.com',
405
+ reason: 'Too many emails' // Optional feedback
406
+ });
407
+
408
+ // One-click unsubscribe (from email link)
409
+ await client.newsletter.unsubscribeByToken(siteName, 'unsubscribe-token');
410
+
411
+ // Update preferences
412
+ await client.newsletter.updatePreferences(siteName, 'subscriber@example.com', {
413
+ frequency: 'monthly',
414
+ topics: ['product-updates'],
415
+ email_format: 'html', // html, text, or both
416
+ timezone: 'America/New_York',
417
+ track_opens: false,
418
+ track_clicks: false
419
+ });
420
+
421
+ // Check subscription status
422
+ const status = await client.newsletter.getStatus(siteName, 'subscriber@example.com');
423
+ console.log('Subscribed:', status.data.subscribed);
424
+ console.log('Status:', status.data.status); // pending, confirmed, unsubscribed
425
+
426
+ // Get available newsletter lists
427
+ const lists = await client.newsletter.getLists(siteName);
428
+ console.log('Available lists:', lists.data.lists);
429
+ ```
430
+
431
+ #### Newsletter Admin Functions
432
+
433
+ ```typescript
434
+ // Get all subscriptions (admin only)
435
+ const subscriptions = await client.newsletter.getSubscriptions(siteName, {
436
+ page: 1,
437
+ limit: 100,
438
+ status: 'confirmed',
439
+ list_id: 'list_weekly',
440
+ search: 'john',
441
+ startDate: '2024-01-01',
442
+ endDate: '2024-12-31'
443
+ });
444
+
445
+ // Update subscription status
446
+ await client.newsletter.updateSubscriptionStatus(
447
+ siteName,
448
+ 'sub_123',
449
+ 'unsubscribed',
450
+ 'Admin action: User requested via support'
451
+ );
452
+
453
+ // Bulk operations
454
+ await client.newsletter.bulkUpdateSubscriptions(siteName, {
455
+ ids: ['sub_123', 'sub_456'],
456
+ action: 'add_to_list',
457
+ list_id: 'list_special_offers'
458
+ });
459
+
460
+ // List management
461
+ const newList = await client.newsletter.createList(siteName, {
462
+ list_name: 'VIP Customers',
463
+ slug: 'vip-customers',
464
+ description: 'Exclusive updates for VIP customers',
465
+ is_public: false,
466
+ is_default: false,
467
+ double_opt_in: true,
468
+ welcome_email_enabled: true
469
+ });
470
+
471
+ await client.newsletter.updateList(siteName, 'list_123', {
472
+ description: 'Updated description',
473
+ is_public: true
474
+ });
475
+
476
+ // Get statistics
477
+ const stats = await client.newsletter.getStatistics(siteName, {
478
+ startDate: '2024-01-01',
479
+ endDate: '2024-12-31',
480
+ list_id: 'list_weekly'
481
+ });
482
+ console.log('Total subscribers:', stats.data.totalSubscribers);
483
+ console.log('Open rate:', stats.data.engagementMetrics.averageOpenRate);
484
+
485
+ // Export subscriptions
486
+ const exportData = await client.newsletter.exportSubscriptions(siteName, {
487
+ format: 'csv', // csv, json, or xlsx
488
+ status: 'confirmed',
489
+ list_id: 'list_weekly'
490
+ });
491
+ console.log('Download URL:', exportData.data.downloadUrl);
492
+
493
+ // Import subscriptions
494
+ const importResult = await client.newsletter.importSubscriptions(siteName, {
495
+ subscriptions: [
496
+ { email: 'user1@example.com', name: 'User One', lists: ['list_weekly'] },
497
+ { email: 'user2@example.com', name: 'User Two', lists: ['list_daily'] }
498
+ ],
499
+ skip_confirmation: false, // Skip double opt-in for imported users
500
+ update_existing: true // Update if email already exists
501
+ });
502
+ console.log('Imported:', importResult.data.imported);
503
+ console.log('Failed:', importResult.data.failed);
504
+ ```
505
+
379
506
  ## Configuration Options
380
507
 
381
508
  ```typescript
package/dist/index.d.mts CHANGED
@@ -397,6 +397,7 @@ interface RequestOptions {
397
397
  body?: any;
398
398
  params?: Record<string, string | number | boolean>;
399
399
  timeout?: number;
400
+ csrfToken?: string;
400
401
  }
401
402
 
402
403
  /**
@@ -433,19 +434,19 @@ declare class HttpClient {
433
434
  /**
434
435
  * POST request
435
436
  */
436
- post<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
437
+ post<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
437
438
  /**
438
439
  * PUT request
439
440
  */
440
- put<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
441
+ put<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
441
442
  /**
442
443
  * DELETE request
443
444
  */
444
- delete<T = any>(endpoint: string): Promise<ApiResponse<T>>;
445
+ delete<T = any>(endpoint: string, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
445
446
  /**
446
447
  * PATCH request
447
448
  */
448
- patch<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
449
+ patch<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
449
450
  /**
450
451
  * Build full URL with query parameters
451
452
  */
@@ -501,19 +502,19 @@ declare abstract class BaseClient {
501
502
  /**
502
503
  * Handle create operations
503
504
  */
504
- protected create<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
505
+ protected create<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
505
506
  /**
506
507
  * Handle update operations
507
508
  */
508
- protected update<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
509
+ protected update<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
509
510
  /**
510
511
  * Handle partial update operations
511
512
  */
512
- protected patch<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
513
+ protected patch<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
513
514
  /**
514
515
  * Handle delete operations
515
516
  */
516
- protected delete<T = any>(endpoint: string): Promise<ApiResponse<T>>;
517
+ protected delete<T = any>(endpoint: string, csrfToken?: string): Promise<ApiResponse<T>>;
517
518
  }
518
519
 
519
520
  /**
@@ -1457,8 +1458,11 @@ declare class ContactClient extends BaseClient {
1457
1458
  private contactEndpoint;
1458
1459
  /**
1459
1460
  * Submit contact form
1461
+ * @param siteName - The site to submit contact form to
1462
+ * @param data - Contact form data
1463
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1460
1464
  */
1461
- submitContact(siteName: string, data: CreateContactRequest): Promise<ApiResponse<{
1465
+ submitContact(siteName: string, data: CreateContactRequest, csrfToken?: string): Promise<ApiResponse<{
1462
1466
  id: string;
1463
1467
  message: string;
1464
1468
  status: string;
@@ -1612,16 +1616,22 @@ declare class NewsletterClient extends BaseClient {
1612
1616
  private newsletterEndpoint;
1613
1617
  /**
1614
1618
  * Subscribe to newsletter
1619
+ * @param siteName - The site to subscribe to
1620
+ * @param data - Subscription data
1621
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1615
1622
  */
1616
- subscribe(siteName: string, data: CreateNewsletterSubscriptionRequest): Promise<ApiResponse<NewsletterSubscribeResponse>>;
1623
+ subscribe(siteName: string, data: CreateNewsletterSubscriptionRequest, csrfToken?: string): Promise<ApiResponse<NewsletterSubscribeResponse>>;
1617
1624
  /**
1618
1625
  * Confirm newsletter subscription via token
1619
1626
  */
1620
1627
  confirmSubscription(siteName: string, token: string): Promise<ApiResponse<NewsletterConfirmResponse>>;
1621
1628
  /**
1622
1629
  * Unsubscribe from newsletter
1630
+ * @param siteName - The site to unsubscribe from
1631
+ * @param data - Unsubscribe data
1632
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1623
1633
  */
1624
- unsubscribe(siteName: string, data: NewsletterUnsubscribeRequest): Promise<ApiResponse<{
1634
+ unsubscribe(siteName: string, data: NewsletterUnsubscribeRequest, csrfToken?: string): Promise<ApiResponse<{
1625
1635
  message: string;
1626
1636
  }>>;
1627
1637
  /**
@@ -1630,8 +1640,12 @@ declare class NewsletterClient extends BaseClient {
1630
1640
  unsubscribeByToken(siteName: string, token: string): Promise<ApiResponse<string>>;
1631
1641
  /**
1632
1642
  * Update subscription preferences
1643
+ * @param siteName - The site name
1644
+ * @param email - Subscriber email
1645
+ * @param preferences - New preferences
1646
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1633
1647
  */
1634
- updatePreferences(siteName: string, email: string, preferences: NewsletterPreferences): Promise<ApiResponse<{
1648
+ updatePreferences(siteName: string, email: string, preferences: NewsletterPreferences, csrfToken?: string): Promise<ApiResponse<{
1635
1649
  message: string;
1636
1650
  preferences: NewsletterPreferences;
1637
1651
  }>>;
package/dist/index.d.ts CHANGED
@@ -397,6 +397,7 @@ interface RequestOptions {
397
397
  body?: any;
398
398
  params?: Record<string, string | number | boolean>;
399
399
  timeout?: number;
400
+ csrfToken?: string;
400
401
  }
401
402
 
402
403
  /**
@@ -433,19 +434,19 @@ declare class HttpClient {
433
434
  /**
434
435
  * POST request
435
436
  */
436
- post<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
437
+ post<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
437
438
  /**
438
439
  * PUT request
439
440
  */
440
- put<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
441
+ put<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
441
442
  /**
442
443
  * DELETE request
443
444
  */
444
- delete<T = any>(endpoint: string): Promise<ApiResponse<T>>;
445
+ delete<T = any>(endpoint: string, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
445
446
  /**
446
447
  * PATCH request
447
448
  */
448
- patch<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
449
+ patch<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
449
450
  /**
450
451
  * Build full URL with query parameters
451
452
  */
@@ -501,19 +502,19 @@ declare abstract class BaseClient {
501
502
  /**
502
503
  * Handle create operations
503
504
  */
504
- protected create<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
505
+ protected create<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
505
506
  /**
506
507
  * Handle update operations
507
508
  */
508
- protected update<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
509
+ protected update<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
509
510
  /**
510
511
  * Handle partial update operations
511
512
  */
512
- protected patch<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
513
+ protected patch<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
513
514
  /**
514
515
  * Handle delete operations
515
516
  */
516
- protected delete<T = any>(endpoint: string): Promise<ApiResponse<T>>;
517
+ protected delete<T = any>(endpoint: string, csrfToken?: string): Promise<ApiResponse<T>>;
517
518
  }
518
519
 
519
520
  /**
@@ -1457,8 +1458,11 @@ declare class ContactClient extends BaseClient {
1457
1458
  private contactEndpoint;
1458
1459
  /**
1459
1460
  * Submit contact form
1461
+ * @param siteName - The site to submit contact form to
1462
+ * @param data - Contact form data
1463
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1460
1464
  */
1461
- submitContact(siteName: string, data: CreateContactRequest): Promise<ApiResponse<{
1465
+ submitContact(siteName: string, data: CreateContactRequest, csrfToken?: string): Promise<ApiResponse<{
1462
1466
  id: string;
1463
1467
  message: string;
1464
1468
  status: string;
@@ -1612,16 +1616,22 @@ declare class NewsletterClient extends BaseClient {
1612
1616
  private newsletterEndpoint;
1613
1617
  /**
1614
1618
  * Subscribe to newsletter
1619
+ * @param siteName - The site to subscribe to
1620
+ * @param data - Subscription data
1621
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1615
1622
  */
1616
- subscribe(siteName: string, data: CreateNewsletterSubscriptionRequest): Promise<ApiResponse<NewsletterSubscribeResponse>>;
1623
+ subscribe(siteName: string, data: CreateNewsletterSubscriptionRequest, csrfToken?: string): Promise<ApiResponse<NewsletterSubscribeResponse>>;
1617
1624
  /**
1618
1625
  * Confirm newsletter subscription via token
1619
1626
  */
1620
1627
  confirmSubscription(siteName: string, token: string): Promise<ApiResponse<NewsletterConfirmResponse>>;
1621
1628
  /**
1622
1629
  * Unsubscribe from newsletter
1630
+ * @param siteName - The site to unsubscribe from
1631
+ * @param data - Unsubscribe data
1632
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1623
1633
  */
1624
- unsubscribe(siteName: string, data: NewsletterUnsubscribeRequest): Promise<ApiResponse<{
1634
+ unsubscribe(siteName: string, data: NewsletterUnsubscribeRequest, csrfToken?: string): Promise<ApiResponse<{
1625
1635
  message: string;
1626
1636
  }>>;
1627
1637
  /**
@@ -1630,8 +1640,12 @@ declare class NewsletterClient extends BaseClient {
1630
1640
  unsubscribeByToken(siteName: string, token: string): Promise<ApiResponse<string>>;
1631
1641
  /**
1632
1642
  * Update subscription preferences
1643
+ * @param siteName - The site name
1644
+ * @param email - Subscriber email
1645
+ * @param preferences - New preferences
1646
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1633
1647
  */
1634
- updatePreferences(siteName: string, email: string, preferences: NewsletterPreferences): Promise<ApiResponse<{
1648
+ updatePreferences(siteName: string, email: string, preferences: NewsletterPreferences, csrfToken?: string): Promise<ApiResponse<{
1635
1649
  message: string;
1636
1650
  preferences: NewsletterPreferences;
1637
1651
  }>>;
package/dist/index.js CHANGED
@@ -130,26 +130,26 @@ var HttpClient = class {
130
130
  /**
131
131
  * POST request
132
132
  */
133
- async post(endpoint, body) {
134
- return this.request(endpoint, { method: "POST", body });
133
+ async post(endpoint, body, options) {
134
+ return this.request(endpoint, { method: "POST", body, ...options });
135
135
  }
136
136
  /**
137
137
  * PUT request
138
138
  */
139
- async put(endpoint, body) {
140
- return this.request(endpoint, { method: "PUT", body });
139
+ async put(endpoint, body, options) {
140
+ return this.request(endpoint, { method: "PUT", body, ...options });
141
141
  }
142
142
  /**
143
143
  * DELETE request
144
144
  */
145
- async delete(endpoint) {
146
- return this.request(endpoint, { method: "DELETE" });
145
+ async delete(endpoint, options) {
146
+ return this.request(endpoint, { method: "DELETE", ...options });
147
147
  }
148
148
  /**
149
149
  * PATCH request
150
150
  */
151
- async patch(endpoint, body) {
152
- return this.request(endpoint, { method: "PATCH", body });
151
+ async patch(endpoint, body, options) {
152
+ return this.request(endpoint, { method: "PATCH", body, ...options });
153
153
  }
154
154
  /**
155
155
  * Build full URL with query parameters
@@ -175,6 +175,9 @@ var HttpClient = class {
175
175
  ...this.defaultHeaders,
176
176
  ...options.headers
177
177
  };
178
+ if (options.csrfToken) {
179
+ headers["X-CSRF-Token"] = options.csrfToken;
180
+ }
178
181
  const requestOptions = {
179
182
  method: options.method || "GET",
180
183
  headers
@@ -298,26 +301,26 @@ var BaseClient = class {
298
301
  /**
299
302
  * Handle create operations
300
303
  */
301
- async create(endpoint, data) {
302
- return this.http.post(this.buildPath(endpoint), data);
304
+ async create(endpoint, data, csrfToken) {
305
+ return this.http.post(this.buildPath(endpoint), data, { csrfToken });
303
306
  }
304
307
  /**
305
308
  * Handle update operations
306
309
  */
307
- async update(endpoint, data) {
308
- return this.http.put(this.buildPath(endpoint), data);
310
+ async update(endpoint, data, csrfToken) {
311
+ return this.http.put(this.buildPath(endpoint), data, { csrfToken });
309
312
  }
310
313
  /**
311
314
  * Handle partial update operations
312
315
  */
313
- async patch(endpoint, data) {
314
- return this.http.patch(this.buildPath(endpoint), data);
316
+ async patch(endpoint, data, csrfToken) {
317
+ return this.http.patch(this.buildPath(endpoint), data, { csrfToken });
315
318
  }
316
319
  /**
317
320
  * Handle delete operations
318
321
  */
319
- async delete(endpoint) {
320
- return this.http.delete(this.buildPath(endpoint));
322
+ async delete(endpoint, csrfToken) {
323
+ return this.http.delete(this.buildPath(endpoint), { csrfToken });
321
324
  }
322
325
  };
323
326
 
@@ -1184,9 +1187,15 @@ var ContactClient = class extends BaseClient {
1184
1187
  }
1185
1188
  /**
1186
1189
  * Submit contact form
1190
+ * @param siteName - The site to submit contact form to
1191
+ * @param data - Contact form data
1192
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1187
1193
  */
1188
- async submitContact(siteName, data) {
1189
- return this.create(this.contactEndpoint(siteName, "/contact/submit"), data);
1194
+ async submitContact(siteName, data, csrfToken) {
1195
+ if (typeof window !== "undefined" && !csrfToken && !data.turnstileToken) {
1196
+ console.warn("CSRF token recommended for browser-based contact form submissions");
1197
+ }
1198
+ return this.create(this.contactEndpoint(siteName, "/contact/submit"), data, csrfToken);
1190
1199
  }
1191
1200
  /**
1192
1201
  * Get contact submission status
@@ -1281,11 +1290,18 @@ var NewsletterClient = class extends BaseClient {
1281
1290
  }
1282
1291
  /**
1283
1292
  * Subscribe to newsletter
1293
+ * @param siteName - The site to subscribe to
1294
+ * @param data - Subscription data
1295
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1284
1296
  */
1285
- async subscribe(siteName, data) {
1297
+ async subscribe(siteName, data, csrfToken) {
1298
+ if (typeof window !== "undefined" && !csrfToken && !data.turnstile_token) {
1299
+ console.warn("CSRF token recommended for browser-based newsletter subscriptions");
1300
+ }
1286
1301
  return this.create(
1287
1302
  this.newsletterEndpoint(siteName, "/newsletter/subscribe"),
1288
- data
1303
+ data,
1304
+ csrfToken
1289
1305
  );
1290
1306
  }
1291
1307
  /**
@@ -1298,11 +1314,15 @@ var NewsletterClient = class extends BaseClient {
1298
1314
  }
1299
1315
  /**
1300
1316
  * Unsubscribe from newsletter
1317
+ * @param siteName - The site to unsubscribe from
1318
+ * @param data - Unsubscribe data
1319
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1301
1320
  */
1302
- async unsubscribe(siteName, data) {
1321
+ async unsubscribe(siteName, data, csrfToken) {
1303
1322
  return this.create(
1304
1323
  this.newsletterEndpoint(siteName, "/newsletter/unsubscribe"),
1305
- data
1324
+ data,
1325
+ csrfToken
1306
1326
  );
1307
1327
  }
1308
1328
  /**
@@ -1315,11 +1335,16 @@ var NewsletterClient = class extends BaseClient {
1315
1335
  }
1316
1336
  /**
1317
1337
  * Update subscription preferences
1338
+ * @param siteName - The site name
1339
+ * @param email - Subscriber email
1340
+ * @param preferences - New preferences
1341
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1318
1342
  */
1319
- async updatePreferences(siteName, email, preferences) {
1343
+ async updatePreferences(siteName, email, preferences, csrfToken) {
1320
1344
  return this.patch(
1321
1345
  this.newsletterEndpoint(siteName, "/newsletter/preferences"),
1322
- { email, ...preferences }
1346
+ { email, ...preferences },
1347
+ csrfToken
1323
1348
  );
1324
1349
  }
1325
1350
  /**
package/dist/index.mjs CHANGED
@@ -79,26 +79,26 @@ var HttpClient = class {
79
79
  /**
80
80
  * POST request
81
81
  */
82
- async post(endpoint, body) {
83
- return this.request(endpoint, { method: "POST", body });
82
+ async post(endpoint, body, options) {
83
+ return this.request(endpoint, { method: "POST", body, ...options });
84
84
  }
85
85
  /**
86
86
  * PUT request
87
87
  */
88
- async put(endpoint, body) {
89
- return this.request(endpoint, { method: "PUT", body });
88
+ async put(endpoint, body, options) {
89
+ return this.request(endpoint, { method: "PUT", body, ...options });
90
90
  }
91
91
  /**
92
92
  * DELETE request
93
93
  */
94
- async delete(endpoint) {
95
- return this.request(endpoint, { method: "DELETE" });
94
+ async delete(endpoint, options) {
95
+ return this.request(endpoint, { method: "DELETE", ...options });
96
96
  }
97
97
  /**
98
98
  * PATCH request
99
99
  */
100
- async patch(endpoint, body) {
101
- return this.request(endpoint, { method: "PATCH", body });
100
+ async patch(endpoint, body, options) {
101
+ return this.request(endpoint, { method: "PATCH", body, ...options });
102
102
  }
103
103
  /**
104
104
  * Build full URL with query parameters
@@ -124,6 +124,9 @@ var HttpClient = class {
124
124
  ...this.defaultHeaders,
125
125
  ...options.headers
126
126
  };
127
+ if (options.csrfToken) {
128
+ headers["X-CSRF-Token"] = options.csrfToken;
129
+ }
127
130
  const requestOptions = {
128
131
  method: options.method || "GET",
129
132
  headers
@@ -247,26 +250,26 @@ var BaseClient = class {
247
250
  /**
248
251
  * Handle create operations
249
252
  */
250
- async create(endpoint, data) {
251
- return this.http.post(this.buildPath(endpoint), data);
253
+ async create(endpoint, data, csrfToken) {
254
+ return this.http.post(this.buildPath(endpoint), data, { csrfToken });
252
255
  }
253
256
  /**
254
257
  * Handle update operations
255
258
  */
256
- async update(endpoint, data) {
257
- return this.http.put(this.buildPath(endpoint), data);
259
+ async update(endpoint, data, csrfToken) {
260
+ return this.http.put(this.buildPath(endpoint), data, { csrfToken });
258
261
  }
259
262
  /**
260
263
  * Handle partial update operations
261
264
  */
262
- async patch(endpoint, data) {
263
- return this.http.patch(this.buildPath(endpoint), data);
265
+ async patch(endpoint, data, csrfToken) {
266
+ return this.http.patch(this.buildPath(endpoint), data, { csrfToken });
264
267
  }
265
268
  /**
266
269
  * Handle delete operations
267
270
  */
268
- async delete(endpoint) {
269
- return this.http.delete(this.buildPath(endpoint));
271
+ async delete(endpoint, csrfToken) {
272
+ return this.http.delete(this.buildPath(endpoint), { csrfToken });
270
273
  }
271
274
  };
272
275
 
@@ -1133,9 +1136,15 @@ var ContactClient = class extends BaseClient {
1133
1136
  }
1134
1137
  /**
1135
1138
  * Submit contact form
1139
+ * @param siteName - The site to submit contact form to
1140
+ * @param data - Contact form data
1141
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1136
1142
  */
1137
- async submitContact(siteName, data) {
1138
- return this.create(this.contactEndpoint(siteName, "/contact/submit"), data);
1143
+ async submitContact(siteName, data, csrfToken) {
1144
+ if (typeof window !== "undefined" && !csrfToken && !data.turnstileToken) {
1145
+ console.warn("CSRF token recommended for browser-based contact form submissions");
1146
+ }
1147
+ return this.create(this.contactEndpoint(siteName, "/contact/submit"), data, csrfToken);
1139
1148
  }
1140
1149
  /**
1141
1150
  * Get contact submission status
@@ -1230,11 +1239,18 @@ var NewsletterClient = class extends BaseClient {
1230
1239
  }
1231
1240
  /**
1232
1241
  * Subscribe to newsletter
1242
+ * @param siteName - The site to subscribe to
1243
+ * @param data - Subscription data
1244
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1233
1245
  */
1234
- async subscribe(siteName, data) {
1246
+ async subscribe(siteName, data, csrfToken) {
1247
+ if (typeof window !== "undefined" && !csrfToken && !data.turnstile_token) {
1248
+ console.warn("CSRF token recommended for browser-based newsletter subscriptions");
1249
+ }
1235
1250
  return this.create(
1236
1251
  this.newsletterEndpoint(siteName, "/newsletter/subscribe"),
1237
- data
1252
+ data,
1253
+ csrfToken
1238
1254
  );
1239
1255
  }
1240
1256
  /**
@@ -1247,11 +1263,15 @@ var NewsletterClient = class extends BaseClient {
1247
1263
  }
1248
1264
  /**
1249
1265
  * Unsubscribe from newsletter
1266
+ * @param siteName - The site to unsubscribe from
1267
+ * @param data - Unsubscribe data
1268
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1250
1269
  */
1251
- async unsubscribe(siteName, data) {
1270
+ async unsubscribe(siteName, data, csrfToken) {
1252
1271
  return this.create(
1253
1272
  this.newsletterEndpoint(siteName, "/newsletter/unsubscribe"),
1254
- data
1273
+ data,
1274
+ csrfToken
1255
1275
  );
1256
1276
  }
1257
1277
  /**
@@ -1264,11 +1284,16 @@ var NewsletterClient = class extends BaseClient {
1264
1284
  }
1265
1285
  /**
1266
1286
  * Update subscription preferences
1287
+ * @param siteName - The site name
1288
+ * @param email - Subscriber email
1289
+ * @param preferences - New preferences
1290
+ * @param csrfToken - CSRF token (required for browser-based submissions)
1267
1291
  */
1268
- async updatePreferences(siteName, email, preferences) {
1292
+ async updatePreferences(siteName, email, preferences, csrfToken) {
1269
1293
  return this.patch(
1270
1294
  this.newsletterEndpoint(siteName, "/newsletter/preferences"),
1271
- { email, ...preferences }
1295
+ { email, ...preferences },
1296
+ csrfToken
1272
1297
  );
1273
1298
  }
1274
1299
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perspectapi-ts-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "TypeScript SDK for PerspectAPI - Cloudflare Workers compatible",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -66,9 +66,10 @@ export abstract class BaseClient {
66
66
  */
67
67
  protected async create<T, R = T>(
68
68
  endpoint: string,
69
- data: T
69
+ data: T,
70
+ csrfToken?: string
70
71
  ): Promise<ApiResponse<R>> {
71
- return this.http.post<R>(this.buildPath(endpoint), data);
72
+ return this.http.post<R>(this.buildPath(endpoint), data, { csrfToken });
72
73
  }
73
74
 
74
75
  /**
@@ -76,9 +77,10 @@ export abstract class BaseClient {
76
77
  */
77
78
  protected async update<T, R = T>(
78
79
  endpoint: string,
79
- data: T
80
+ data: T,
81
+ csrfToken?: string
80
82
  ): Promise<ApiResponse<R>> {
81
- return this.http.put<R>(this.buildPath(endpoint), data);
83
+ return this.http.put<R>(this.buildPath(endpoint), data, { csrfToken });
82
84
  }
83
85
 
84
86
  /**
@@ -86,15 +88,16 @@ export abstract class BaseClient {
86
88
  */
87
89
  protected async patch<T, R = T>(
88
90
  endpoint: string,
89
- data: T
91
+ data: T,
92
+ csrfToken?: string
90
93
  ): Promise<ApiResponse<R>> {
91
- return this.http.patch<R>(this.buildPath(endpoint), data);
94
+ return this.http.patch<R>(this.buildPath(endpoint), data, { csrfToken });
92
95
  }
93
96
 
94
97
  /**
95
98
  * Handle delete operations
96
99
  */
97
- protected async delete<T = any>(endpoint: string): Promise<ApiResponse<T>> {
98
- return this.http.delete<T>(this.buildPath(endpoint));
100
+ protected async delete<T = any>(endpoint: string, csrfToken?: string): Promise<ApiResponse<T>> {
101
+ return this.http.delete<T>(this.buildPath(endpoint), { csrfToken });
99
102
  }
100
103
  }
@@ -24,17 +24,29 @@ export class ContactClient extends BaseClient {
24
24
 
25
25
  /**
26
26
  * Submit contact form
27
+ * @param siteName - The site to submit contact form to
28
+ * @param data - Contact form data
29
+ * @param csrfToken - CSRF token (required for browser-based submissions)
27
30
  */
28
- async submitContact(siteName: string, data: CreateContactRequest): Promise<ApiResponse<{
31
+ async submitContact(
32
+ siteName: string,
33
+ data: CreateContactRequest,
34
+ csrfToken?: string
35
+ ): Promise<ApiResponse<{
29
36
  id: string;
30
37
  message: string;
31
38
  status: string;
32
39
  }>> {
40
+ // CSRF token is required for browser submissions
41
+ if (typeof window !== 'undefined' && !csrfToken && !data.turnstileToken) {
42
+ console.warn('CSRF token recommended for browser-based contact form submissions');
43
+ }
44
+
33
45
  return this.create<CreateContactRequest, {
34
46
  id: string;
35
47
  message: string;
36
48
  status: string;
37
- }>(this.contactEndpoint(siteName, '/contact/submit'), data);
49
+ }>(this.contactEndpoint(siteName, '/contact/submit'), data, csrfToken);
38
50
  }
39
51
 
40
52
  /**
@@ -30,14 +30,24 @@ export class NewsletterClient extends BaseClient {
30
30
 
31
31
  /**
32
32
  * Subscribe to newsletter
33
+ * @param siteName - The site to subscribe to
34
+ * @param data - Subscription data
35
+ * @param csrfToken - CSRF token (required for browser-based submissions)
33
36
  */
34
37
  async subscribe(
35
38
  siteName: string,
36
- data: CreateNewsletterSubscriptionRequest
39
+ data: CreateNewsletterSubscriptionRequest,
40
+ csrfToken?: string
37
41
  ): Promise<ApiResponse<NewsletterSubscribeResponse>> {
42
+ // CSRF token is required for browser submissions
43
+ if (typeof window !== 'undefined' && !csrfToken && !data.turnstile_token) {
44
+ console.warn('CSRF token recommended for browser-based newsletter subscriptions');
45
+ }
46
+
38
47
  return this.create<CreateNewsletterSubscriptionRequest, NewsletterSubscribeResponse>(
39
48
  this.newsletterEndpoint(siteName, '/newsletter/subscribe'),
40
- data
49
+ data,
50
+ csrfToken
41
51
  );
42
52
  }
43
53
 
@@ -55,14 +65,19 @@ export class NewsletterClient extends BaseClient {
55
65
 
56
66
  /**
57
67
  * Unsubscribe from newsletter
68
+ * @param siteName - The site to unsubscribe from
69
+ * @param data - Unsubscribe data
70
+ * @param csrfToken - CSRF token (required for browser-based submissions)
58
71
  */
59
72
  async unsubscribe(
60
73
  siteName: string,
61
- data: NewsletterUnsubscribeRequest
74
+ data: NewsletterUnsubscribeRequest,
75
+ csrfToken?: string
62
76
  ): Promise<ApiResponse<{ message: string }>> {
63
77
  return this.create<NewsletterUnsubscribeRequest, { message: string }>(
64
78
  this.newsletterEndpoint(siteName, '/newsletter/unsubscribe'),
65
- data
79
+ data,
80
+ csrfToken
66
81
  );
67
82
  }
68
83
 
@@ -81,15 +96,21 @@ export class NewsletterClient extends BaseClient {
81
96
 
82
97
  /**
83
98
  * Update subscription preferences
99
+ * @param siteName - The site name
100
+ * @param email - Subscriber email
101
+ * @param preferences - New preferences
102
+ * @param csrfToken - CSRF token (required for browser-based submissions)
84
103
  */
85
104
  async updatePreferences(
86
105
  siteName: string,
87
106
  email: string,
88
- preferences: NewsletterPreferences
107
+ preferences: NewsletterPreferences,
108
+ csrfToken?: string
89
109
  ): Promise<ApiResponse<{ message: string; preferences: NewsletterPreferences }>> {
90
110
  return this.patch(
91
111
  this.newsletterEndpoint(siteName, '/newsletter/preferences'),
92
- { email, ...preferences }
112
+ { email, ...preferences },
113
+ csrfToken
93
114
  );
94
115
  }
95
116
 
@@ -473,4 +473,5 @@ export interface RequestOptions {
473
473
  body?: any;
474
474
  params?: Record<string, string | number | boolean>;
475
475
  timeout?: number;
476
+ csrfToken?: string; // Optional CSRF token for protected endpoints
476
477
  }
@@ -115,29 +115,29 @@ export class HttpClient {
115
115
  /**
116
116
  * POST request
117
117
  */
118
- async post<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>> {
119
- return this.request<T>(endpoint, { method: 'POST', body });
118
+ async post<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>> {
119
+ return this.request<T>(endpoint, { method: 'POST', body, ...options });
120
120
  }
121
121
 
122
122
  /**
123
123
  * PUT request
124
124
  */
125
- async put<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>> {
126
- return this.request<T>(endpoint, { method: 'PUT', body });
125
+ async put<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>> {
126
+ return this.request<T>(endpoint, { method: 'PUT', body, ...options });
127
127
  }
128
128
 
129
129
  /**
130
130
  * DELETE request
131
131
  */
132
- async delete<T = any>(endpoint: string): Promise<ApiResponse<T>> {
133
- return this.request<T>(endpoint, { method: 'DELETE' });
132
+ async delete<T = any>(endpoint: string, options?: Partial<RequestOptions>): Promise<ApiResponse<T>> {
133
+ return this.request<T>(endpoint, { method: 'DELETE', ...options });
134
134
  }
135
135
 
136
136
  /**
137
137
  * PATCH request
138
138
  */
139
- async patch<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>> {
140
- return this.request<T>(endpoint, { method: 'PATCH', body });
139
+ async patch<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>> {
140
+ return this.request<T>(endpoint, { method: 'PATCH', body, ...options });
141
141
  }
142
142
 
143
143
  /**
@@ -169,6 +169,11 @@ export class HttpClient {
169
169
  ...options.headers,
170
170
  };
171
171
 
172
+ // Add CSRF token if provided
173
+ if (options.csrfToken) {
174
+ headers['X-CSRF-Token'] = options.csrfToken;
175
+ }
176
+
172
177
  const requestOptions: RequestInit = {
173
178
  method: options.method || 'GET',
174
179
  headers,