perspectapi-ts-sdk 1.2.0 → 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.
- package/README.md +127 -0
- package/dist/index.d.mts +26 -16
- package/dist/index.d.ts +26 -16
- package/dist/index.js +49 -24
- package/dist/index.mjs +49 -24
- package/package.json +1 -1
- package/src/client/base-client.ts +11 -8
- package/src/client/contact-client.ts +14 -4
- package/src/client/newsletter-client.ts +27 -6
- package/src/types/index.ts +1 -2
- package/src/utils/http-client.ts +13 -8
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
|
@@ -121,7 +121,6 @@ interface CreateNewsletterSubscriptionRequest {
|
|
|
121
121
|
source?: string;
|
|
122
122
|
source_url?: string;
|
|
123
123
|
double_opt_in?: boolean;
|
|
124
|
-
turnstile_token?: string;
|
|
125
124
|
metadata?: Record<string, any>;
|
|
126
125
|
}
|
|
127
126
|
interface NewsletterList {
|
|
@@ -374,7 +373,6 @@ interface CreateContactRequest {
|
|
|
374
373
|
email: string;
|
|
375
374
|
subject?: string;
|
|
376
375
|
message: string;
|
|
377
|
-
turnstileToken?: string;
|
|
378
376
|
}
|
|
379
377
|
interface ApiError {
|
|
380
378
|
message: string;
|
|
@@ -397,6 +395,7 @@ interface RequestOptions {
|
|
|
397
395
|
body?: any;
|
|
398
396
|
params?: Record<string, string | number | boolean>;
|
|
399
397
|
timeout?: number;
|
|
398
|
+
csrfToken?: string;
|
|
400
399
|
}
|
|
401
400
|
|
|
402
401
|
/**
|
|
@@ -433,19 +432,19 @@ declare class HttpClient {
|
|
|
433
432
|
/**
|
|
434
433
|
* POST request
|
|
435
434
|
*/
|
|
436
|
-
post<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
|
|
435
|
+
post<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
|
|
437
436
|
/**
|
|
438
437
|
* PUT request
|
|
439
438
|
*/
|
|
440
|
-
put<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
|
|
439
|
+
put<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
|
|
441
440
|
/**
|
|
442
441
|
* DELETE request
|
|
443
442
|
*/
|
|
444
|
-
delete<T = any>(endpoint: string): Promise<ApiResponse<T>>;
|
|
443
|
+
delete<T = any>(endpoint: string, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
|
|
445
444
|
/**
|
|
446
445
|
* PATCH request
|
|
447
446
|
*/
|
|
448
|
-
patch<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
|
|
447
|
+
patch<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
|
|
449
448
|
/**
|
|
450
449
|
* Build full URL with query parameters
|
|
451
450
|
*/
|
|
@@ -501,19 +500,19 @@ declare abstract class BaseClient {
|
|
|
501
500
|
/**
|
|
502
501
|
* Handle create operations
|
|
503
502
|
*/
|
|
504
|
-
protected create<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
|
|
503
|
+
protected create<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
|
|
505
504
|
/**
|
|
506
505
|
* Handle update operations
|
|
507
506
|
*/
|
|
508
|
-
protected update<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
|
|
507
|
+
protected update<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
|
|
509
508
|
/**
|
|
510
509
|
* Handle partial update operations
|
|
511
510
|
*/
|
|
512
|
-
protected patch<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
|
|
511
|
+
protected patch<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
|
|
513
512
|
/**
|
|
514
513
|
* Handle delete operations
|
|
515
514
|
*/
|
|
516
|
-
protected delete<T = any>(endpoint: string): Promise<ApiResponse<T>>;
|
|
515
|
+
protected delete<T = any>(endpoint: string, csrfToken?: string): Promise<ApiResponse<T>>;
|
|
517
516
|
}
|
|
518
517
|
|
|
519
518
|
/**
|
|
@@ -1457,8 +1456,11 @@ declare class ContactClient extends BaseClient {
|
|
|
1457
1456
|
private contactEndpoint;
|
|
1458
1457
|
/**
|
|
1459
1458
|
* Submit contact form
|
|
1459
|
+
* @param siteName - The site to submit contact form to
|
|
1460
|
+
* @param data - Contact form data
|
|
1461
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
1460
1462
|
*/
|
|
1461
|
-
submitContact(siteName: string, data: CreateContactRequest): Promise<ApiResponse<{
|
|
1463
|
+
submitContact(siteName: string, data: CreateContactRequest, csrfToken?: string): Promise<ApiResponse<{
|
|
1462
1464
|
id: string;
|
|
1463
1465
|
message: string;
|
|
1464
1466
|
status: string;
|
|
@@ -1568,7 +1570,6 @@ declare class ContactClient extends BaseClient {
|
|
|
1568
1570
|
* Get contact form configuration
|
|
1569
1571
|
*/
|
|
1570
1572
|
getContactConfig(siteName: string): Promise<ApiResponse<{
|
|
1571
|
-
turnstileEnabled: boolean;
|
|
1572
1573
|
rateLimitEnabled: boolean;
|
|
1573
1574
|
rateLimitWindow: number;
|
|
1574
1575
|
rateLimitMax: number;
|
|
@@ -1584,7 +1585,6 @@ declare class ContactClient extends BaseClient {
|
|
|
1584
1585
|
* Update contact form configuration (admin only)
|
|
1585
1586
|
*/
|
|
1586
1587
|
updateContactConfig(siteName: string, data: {
|
|
1587
|
-
turnstileEnabled?: boolean;
|
|
1588
1588
|
rateLimitEnabled?: boolean;
|
|
1589
1589
|
rateLimitWindow?: number;
|
|
1590
1590
|
rateLimitMax?: number;
|
|
@@ -1612,16 +1612,22 @@ declare class NewsletterClient extends BaseClient {
|
|
|
1612
1612
|
private newsletterEndpoint;
|
|
1613
1613
|
/**
|
|
1614
1614
|
* Subscribe to newsletter
|
|
1615
|
+
* @param siteName - The site to subscribe to
|
|
1616
|
+
* @param data - Subscription data
|
|
1617
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
1615
1618
|
*/
|
|
1616
|
-
subscribe(siteName: string, data: CreateNewsletterSubscriptionRequest): Promise<ApiResponse<NewsletterSubscribeResponse>>;
|
|
1619
|
+
subscribe(siteName: string, data: CreateNewsletterSubscriptionRequest, csrfToken?: string): Promise<ApiResponse<NewsletterSubscribeResponse>>;
|
|
1617
1620
|
/**
|
|
1618
1621
|
* Confirm newsletter subscription via token
|
|
1619
1622
|
*/
|
|
1620
1623
|
confirmSubscription(siteName: string, token: string): Promise<ApiResponse<NewsletterConfirmResponse>>;
|
|
1621
1624
|
/**
|
|
1622
1625
|
* Unsubscribe from newsletter
|
|
1626
|
+
* @param siteName - The site to unsubscribe from
|
|
1627
|
+
* @param data - Unsubscribe data
|
|
1628
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
1623
1629
|
*/
|
|
1624
|
-
unsubscribe(siteName: string, data: NewsletterUnsubscribeRequest): Promise<ApiResponse<{
|
|
1630
|
+
unsubscribe(siteName: string, data: NewsletterUnsubscribeRequest, csrfToken?: string): Promise<ApiResponse<{
|
|
1625
1631
|
message: string;
|
|
1626
1632
|
}>>;
|
|
1627
1633
|
/**
|
|
@@ -1630,8 +1636,12 @@ declare class NewsletterClient extends BaseClient {
|
|
|
1630
1636
|
unsubscribeByToken(siteName: string, token: string): Promise<ApiResponse<string>>;
|
|
1631
1637
|
/**
|
|
1632
1638
|
* Update subscription preferences
|
|
1639
|
+
* @param siteName - The site name
|
|
1640
|
+
* @param email - Subscriber email
|
|
1641
|
+
* @param preferences - New preferences
|
|
1642
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
1633
1643
|
*/
|
|
1634
|
-
updatePreferences(siteName: string, email: string, preferences: NewsletterPreferences): Promise<ApiResponse<{
|
|
1644
|
+
updatePreferences(siteName: string, email: string, preferences: NewsletterPreferences, csrfToken?: string): Promise<ApiResponse<{
|
|
1635
1645
|
message: string;
|
|
1636
1646
|
preferences: NewsletterPreferences;
|
|
1637
1647
|
}>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -121,7 +121,6 @@ interface CreateNewsletterSubscriptionRequest {
|
|
|
121
121
|
source?: string;
|
|
122
122
|
source_url?: string;
|
|
123
123
|
double_opt_in?: boolean;
|
|
124
|
-
turnstile_token?: string;
|
|
125
124
|
metadata?: Record<string, any>;
|
|
126
125
|
}
|
|
127
126
|
interface NewsletterList {
|
|
@@ -374,7 +373,6 @@ interface CreateContactRequest {
|
|
|
374
373
|
email: string;
|
|
375
374
|
subject?: string;
|
|
376
375
|
message: string;
|
|
377
|
-
turnstileToken?: string;
|
|
378
376
|
}
|
|
379
377
|
interface ApiError {
|
|
380
378
|
message: string;
|
|
@@ -397,6 +395,7 @@ interface RequestOptions {
|
|
|
397
395
|
body?: any;
|
|
398
396
|
params?: Record<string, string | number | boolean>;
|
|
399
397
|
timeout?: number;
|
|
398
|
+
csrfToken?: string;
|
|
400
399
|
}
|
|
401
400
|
|
|
402
401
|
/**
|
|
@@ -433,19 +432,19 @@ declare class HttpClient {
|
|
|
433
432
|
/**
|
|
434
433
|
* POST request
|
|
435
434
|
*/
|
|
436
|
-
post<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
|
|
435
|
+
post<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
|
|
437
436
|
/**
|
|
438
437
|
* PUT request
|
|
439
438
|
*/
|
|
440
|
-
put<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
|
|
439
|
+
put<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
|
|
441
440
|
/**
|
|
442
441
|
* DELETE request
|
|
443
442
|
*/
|
|
444
|
-
delete<T = any>(endpoint: string): Promise<ApiResponse<T>>;
|
|
443
|
+
delete<T = any>(endpoint: string, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
|
|
445
444
|
/**
|
|
446
445
|
* PATCH request
|
|
447
446
|
*/
|
|
448
|
-
patch<T = any>(endpoint: string, body?: any): Promise<ApiResponse<T>>;
|
|
447
|
+
patch<T = any>(endpoint: string, body?: any, options?: Partial<RequestOptions>): Promise<ApiResponse<T>>;
|
|
449
448
|
/**
|
|
450
449
|
* Build full URL with query parameters
|
|
451
450
|
*/
|
|
@@ -501,19 +500,19 @@ declare abstract class BaseClient {
|
|
|
501
500
|
/**
|
|
502
501
|
* Handle create operations
|
|
503
502
|
*/
|
|
504
|
-
protected create<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
|
|
503
|
+
protected create<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
|
|
505
504
|
/**
|
|
506
505
|
* Handle update operations
|
|
507
506
|
*/
|
|
508
|
-
protected update<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
|
|
507
|
+
protected update<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
|
|
509
508
|
/**
|
|
510
509
|
* Handle partial update operations
|
|
511
510
|
*/
|
|
512
|
-
protected patch<T, R = T>(endpoint: string, data: T): Promise<ApiResponse<R>>;
|
|
511
|
+
protected patch<T, R = T>(endpoint: string, data: T, csrfToken?: string): Promise<ApiResponse<R>>;
|
|
513
512
|
/**
|
|
514
513
|
* Handle delete operations
|
|
515
514
|
*/
|
|
516
|
-
protected delete<T = any>(endpoint: string): Promise<ApiResponse<T>>;
|
|
515
|
+
protected delete<T = any>(endpoint: string, csrfToken?: string): Promise<ApiResponse<T>>;
|
|
517
516
|
}
|
|
518
517
|
|
|
519
518
|
/**
|
|
@@ -1457,8 +1456,11 @@ declare class ContactClient extends BaseClient {
|
|
|
1457
1456
|
private contactEndpoint;
|
|
1458
1457
|
/**
|
|
1459
1458
|
* Submit contact form
|
|
1459
|
+
* @param siteName - The site to submit contact form to
|
|
1460
|
+
* @param data - Contact form data
|
|
1461
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
1460
1462
|
*/
|
|
1461
|
-
submitContact(siteName: string, data: CreateContactRequest): Promise<ApiResponse<{
|
|
1463
|
+
submitContact(siteName: string, data: CreateContactRequest, csrfToken?: string): Promise<ApiResponse<{
|
|
1462
1464
|
id: string;
|
|
1463
1465
|
message: string;
|
|
1464
1466
|
status: string;
|
|
@@ -1568,7 +1570,6 @@ declare class ContactClient extends BaseClient {
|
|
|
1568
1570
|
* Get contact form configuration
|
|
1569
1571
|
*/
|
|
1570
1572
|
getContactConfig(siteName: string): Promise<ApiResponse<{
|
|
1571
|
-
turnstileEnabled: boolean;
|
|
1572
1573
|
rateLimitEnabled: boolean;
|
|
1573
1574
|
rateLimitWindow: number;
|
|
1574
1575
|
rateLimitMax: number;
|
|
@@ -1584,7 +1585,6 @@ declare class ContactClient extends BaseClient {
|
|
|
1584
1585
|
* Update contact form configuration (admin only)
|
|
1585
1586
|
*/
|
|
1586
1587
|
updateContactConfig(siteName: string, data: {
|
|
1587
|
-
turnstileEnabled?: boolean;
|
|
1588
1588
|
rateLimitEnabled?: boolean;
|
|
1589
1589
|
rateLimitWindow?: number;
|
|
1590
1590
|
rateLimitMax?: number;
|
|
@@ -1612,16 +1612,22 @@ declare class NewsletterClient extends BaseClient {
|
|
|
1612
1612
|
private newsletterEndpoint;
|
|
1613
1613
|
/**
|
|
1614
1614
|
* Subscribe to newsletter
|
|
1615
|
+
* @param siteName - The site to subscribe to
|
|
1616
|
+
* @param data - Subscription data
|
|
1617
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
1615
1618
|
*/
|
|
1616
|
-
subscribe(siteName: string, data: CreateNewsletterSubscriptionRequest): Promise<ApiResponse<NewsletterSubscribeResponse>>;
|
|
1619
|
+
subscribe(siteName: string, data: CreateNewsletterSubscriptionRequest, csrfToken?: string): Promise<ApiResponse<NewsletterSubscribeResponse>>;
|
|
1617
1620
|
/**
|
|
1618
1621
|
* Confirm newsletter subscription via token
|
|
1619
1622
|
*/
|
|
1620
1623
|
confirmSubscription(siteName: string, token: string): Promise<ApiResponse<NewsletterConfirmResponse>>;
|
|
1621
1624
|
/**
|
|
1622
1625
|
* Unsubscribe from newsletter
|
|
1626
|
+
* @param siteName - The site to unsubscribe from
|
|
1627
|
+
* @param data - Unsubscribe data
|
|
1628
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
1623
1629
|
*/
|
|
1624
|
-
unsubscribe(siteName: string, data: NewsletterUnsubscribeRequest): Promise<ApiResponse<{
|
|
1630
|
+
unsubscribe(siteName: string, data: NewsletterUnsubscribeRequest, csrfToken?: string): Promise<ApiResponse<{
|
|
1625
1631
|
message: string;
|
|
1626
1632
|
}>>;
|
|
1627
1633
|
/**
|
|
@@ -1630,8 +1636,12 @@ declare class NewsletterClient extends BaseClient {
|
|
|
1630
1636
|
unsubscribeByToken(siteName: string, token: string): Promise<ApiResponse<string>>;
|
|
1631
1637
|
/**
|
|
1632
1638
|
* Update subscription preferences
|
|
1639
|
+
* @param siteName - The site name
|
|
1640
|
+
* @param email - Subscriber email
|
|
1641
|
+
* @param preferences - New preferences
|
|
1642
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
1633
1643
|
*/
|
|
1634
|
-
updatePreferences(siteName: string, email: string, preferences: NewsletterPreferences): Promise<ApiResponse<{
|
|
1644
|
+
updatePreferences(siteName: string, email: string, preferences: NewsletterPreferences, csrfToken?: string): Promise<ApiResponse<{
|
|
1635
1645
|
message: string;
|
|
1636
1646
|
preferences: NewsletterPreferences;
|
|
1637
1647
|
}>>;
|
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
|
-
|
|
1194
|
+
async submitContact(siteName, data, csrfToken) {
|
|
1195
|
+
if (typeof window !== "undefined" && !csrfToken) {
|
|
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) {
|
|
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
|
-
|
|
1143
|
+
async submitContact(siteName, data, csrfToken) {
|
|
1144
|
+
if (typeof window !== "undefined" && !csrfToken) {
|
|
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) {
|
|
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
|
@@ -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(
|
|
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) {
|
|
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
|
/**
|
|
@@ -167,7 +179,6 @@ export class ContactClient extends BaseClient {
|
|
|
167
179
|
* Get contact form configuration
|
|
168
180
|
*/
|
|
169
181
|
async getContactConfig(siteName: string): Promise<ApiResponse<{
|
|
170
|
-
turnstileEnabled: boolean;
|
|
171
182
|
rateLimitEnabled: boolean;
|
|
172
183
|
rateLimitWindow: number;
|
|
173
184
|
rateLimitMax: number;
|
|
@@ -186,7 +197,6 @@ export class ContactClient extends BaseClient {
|
|
|
186
197
|
* Update contact form configuration (admin only)
|
|
187
198
|
*/
|
|
188
199
|
async updateContactConfig(siteName: string, data: {
|
|
189
|
-
turnstileEnabled?: boolean;
|
|
190
200
|
rateLimitEnabled?: boolean;
|
|
191
201
|
rateLimitWindow?: number;
|
|
192
202
|
rateLimitMax?: number;
|
|
@@ -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) {
|
|
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
|
|
package/src/types/index.ts
CHANGED
|
@@ -144,7 +144,6 @@ export interface CreateNewsletterSubscriptionRequest {
|
|
|
144
144
|
source?: string;
|
|
145
145
|
source_url?: string;
|
|
146
146
|
double_opt_in?: boolean;
|
|
147
|
-
turnstile_token?: string;
|
|
148
147
|
metadata?: Record<string, any>;
|
|
149
148
|
}
|
|
150
149
|
|
|
@@ -442,7 +441,6 @@ export interface CreateContactRequest {
|
|
|
442
441
|
email: string;
|
|
443
442
|
subject?: string;
|
|
444
443
|
message: string;
|
|
445
|
-
turnstileToken?: string;
|
|
446
444
|
}
|
|
447
445
|
|
|
448
446
|
// Error Types
|
|
@@ -473,4 +471,5 @@ export interface RequestOptions {
|
|
|
473
471
|
body?: any;
|
|
474
472
|
params?: Record<string, string | number | boolean>;
|
|
475
473
|
timeout?: number;
|
|
474
|
+
csrfToken?: string; // Optional CSRF token for protected endpoints
|
|
476
475
|
}
|
package/src/utils/http-client.ts
CHANGED
|
@@ -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,
|