@mobilizehub/payload-plugin 0.0.1 → 0.2.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/dist/access/authenticated.d.ts +4 -0
- package/dist/access/authenticated.js +5 -0
- package/dist/access/authenticated.js.map +1 -0
- package/dist/access/authenticated.spec.d.ts +1 -0
- package/dist/access/authenticated.spec.js +33 -0
- package/dist/access/authenticated.spec.js.map +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/resend-adapter.d.ts +34 -0
- package/dist/adapters/resend-adapter.js +219 -0
- package/dist/adapters/resend-adapter.js.map +1 -0
- package/dist/collections/broadcasts/generateBroadcastsCollection.d.ts +3 -0
- package/dist/collections/broadcasts/generateBroadcastsCollection.js +241 -0
- package/dist/collections/broadcasts/generateBroadcastsCollection.js.map +1 -0
- package/dist/collections/contacts/generateContactsCollection.d.ts +22 -0
- package/dist/collections/contacts/generateContactsCollection.js +124 -0
- package/dist/collections/contacts/generateContactsCollection.js.map +1 -0
- package/dist/collections/emails/generateEmailsCollection.d.ts +3 -0
- package/dist/collections/emails/generateEmailsCollection.js +204 -0
- package/dist/collections/emails/generateEmailsCollection.js.map +1 -0
- package/dist/collections/emails/hooks/sync-status-from-activity.d.ts +5 -0
- package/dist/collections/emails/hooks/sync-status-from-activity.js +64 -0
- package/dist/collections/emails/hooks/sync-status-from-activity.js.map +1 -0
- package/dist/collections/tags/generateTagsCollection.d.ts +3 -0
- package/dist/collections/tags/generateTagsCollection.js +29 -0
- package/dist/collections/tags/generateTagsCollection.js.map +1 -0
- package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.d.ts +2 -0
- package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.js +48 -0
- package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.js.map +1 -0
- package/dist/components/broadcast-metrics-card.d.ts +7 -0
- package/dist/components/broadcast-metrics-card.js +159 -0
- package/dist/components/broadcast-metrics-card.js.map +1 -0
- package/dist/components/broadcast-send-modal.d.ts +9 -0
- package/dist/components/broadcast-send-modal.js +51 -0
- package/dist/components/broadcast-send-modal.js.map +1 -0
- package/dist/components/broadcast-send-test-drawer.d.ts +7 -0
- package/dist/components/broadcast-send-test-drawer.js +154 -0
- package/dist/components/broadcast-send-test-drawer.js.map +1 -0
- package/dist/components/email-activity.d.ts +4 -0
- package/dist/components/email-activity.js +359 -0
- package/dist/components/email-activity.js.map +1 -0
- package/dist/components/email-preview.d.ts +2 -0
- package/dist/components/email-preview.js +95 -0
- package/dist/components/email-preview.js.map +1 -0
- package/dist/endpoints/sendBroadcastHandler.d.ts +9 -0
- package/dist/endpoints/sendBroadcastHandler.js +107 -0
- package/dist/endpoints/sendBroadcastHandler.js.map +1 -0
- package/dist/endpoints/sendTestBroadcastHandler.d.ts +10 -0
- package/dist/endpoints/sendTestBroadcastHandler.js +143 -0
- package/dist/endpoints/sendTestBroadcastHandler.js.map +1 -0
- package/dist/endpoints/unsubscribeHandler.d.ts +9 -0
- package/dist/endpoints/unsubscribeHandler.js +153 -0
- package/dist/endpoints/unsubscribeHandler.js.map +1 -0
- package/dist/exports/client.d.ts +3 -1
- package/dist/exports/client.js +3 -0
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/rsc.d.ts +2 -1
- package/dist/exports/rsc.js +2 -0
- package/dist/exports/rsc.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +51 -2
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +3 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/unsubscribe.d.ts +6 -0
- package/dist/react/unsubscribe.js +16 -0
- package/dist/react/unsubscribe.js.map +1 -0
- package/dist/tasks/sendBroadcastsTask.d.ts +11 -0
- package/dist/tasks/sendBroadcastsTask.js +196 -0
- package/dist/tasks/sendBroadcastsTask.js.map +1 -0
- package/dist/tasks/sendEmailTask.d.ts +9 -0
- package/dist/tasks/sendEmailTask.js +167 -0
- package/dist/tasks/sendEmailTask.js.map +1 -0
- package/dist/types/index.d.ts +135 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/api-response.d.ts +72 -0
- package/dist/utils/api-response.js +66 -0
- package/dist/utils/api-response.js.map +1 -0
- package/dist/utils/email.d.ts +36 -0
- package/dist/utils/email.js +40 -0
- package/dist/utils/email.js.map +1 -0
- package/dist/utils/lexical.d.ts +13 -0
- package/dist/utils/lexical.js +27 -0
- package/dist/utils/lexical.js.map +1 -0
- package/dist/utils/unsubscribe-token.d.ts +67 -0
- package/dist/utils/unsubscribe-token.js +103 -0
- package/dist/utils/unsubscribe-token.js.map +1 -0
- package/package.json +25 -9
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard API success response format
|
|
3
|
+
* @template T - Type of the data payload
|
|
4
|
+
*/ /**
|
|
5
|
+
* Create a standardized success response
|
|
6
|
+
*
|
|
7
|
+
* @template T - Type of the response data
|
|
8
|
+
* @param data - Optional data payload to include in the response
|
|
9
|
+
* @param status - HTTP status code (default: 200)
|
|
10
|
+
* @returns Response object with JSON body
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* return successResponse({ message: 'Email sent successfully' }, 200)
|
|
15
|
+
* // Returns: { success: true, data: { message: 'Email sent successfully' } }
|
|
16
|
+
* ```
|
|
17
|
+
*/ export function successResponse(data, status = 200) {
|
|
18
|
+
return Response.json({
|
|
19
|
+
data,
|
|
20
|
+
success: true
|
|
21
|
+
}, {
|
|
22
|
+
status
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a standardized error response
|
|
27
|
+
*
|
|
28
|
+
* @param code - Error code from ErrorCodes constant
|
|
29
|
+
* @param message - Human-readable error message
|
|
30
|
+
* @param status - HTTP status code (default: 400)
|
|
31
|
+
* @returns Response object with JSON error body
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* return errorResponse(ErrorCodes.UNAUTHORIZED, 'You must be logged in', 401)
|
|
36
|
+
* // Returns: { success: false, error: { code: 'UNAUTHORIZED', message: '...' } }
|
|
37
|
+
* ```
|
|
38
|
+
*/ export function errorResponse(code, message, status = 400) {
|
|
39
|
+
return Response.json({
|
|
40
|
+
error: {
|
|
41
|
+
code,
|
|
42
|
+
message
|
|
43
|
+
},
|
|
44
|
+
success: false
|
|
45
|
+
}, {
|
|
46
|
+
status
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Standard error codes used across the MobilizeHub plugin API
|
|
51
|
+
*/ export const ErrorCodes = {
|
|
52
|
+
BAD_REQUEST: 'BAD_REQUEST',
|
|
53
|
+
BROADCAST_INVALID_STATUS: 'BROADCAST_INVALID_STATUS',
|
|
54
|
+
BROADCAST_NOT_FOUND: 'BROADCAST_NOT_FOUND',
|
|
55
|
+
CONTACT_NOT_FOUND: 'CONTACT_NOT_FOUND',
|
|
56
|
+
EMAIL_SEND_FAILED: 'EMAIL_SEND_FAILED',
|
|
57
|
+
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
58
|
+
NOT_FOUND: 'NOT_FOUND',
|
|
59
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
60
|
+
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
|
|
61
|
+
TOKEN_INVALID: 'TOKEN_INVALID',
|
|
62
|
+
UNAUTHORIZED: 'UNAUTHORIZED',
|
|
63
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR'
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=api-response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/api-response.ts"],"sourcesContent":["/**\n * Standard API success response format\n * @template T - Type of the data payload\n */\nexport type ApiSuccessResponse<T = unknown> = {\n data?: T\n success: true\n}\n\n/**\n * Standard API error response format\n */\nexport type ApiErrorResponse = {\n error: {\n /** Error code constant (e.g., 'BAD_REQUEST', 'UNAUTHORIZED') */\n code: string\n /** Human-readable error message */\n message: string\n }\n success: false\n}\n\n/**\n * Union type for all API responses\n * @template T - Type of the data payload for success responses\n */\nexport type ApiResponse<T = unknown> = ApiErrorResponse | ApiSuccessResponse<T>\n\n/**\n * Create a standardized success response\n *\n * @template T - Type of the response data\n * @param data - Optional data payload to include in the response\n * @param status - HTTP status code (default: 200)\n * @returns Response object with JSON body\n *\n * @example\n * ```typescript\n * return successResponse({ message: 'Email sent successfully' }, 200)\n * // Returns: { success: true, data: { message: 'Email sent successfully' } }\n * ```\n */\nexport function successResponse<T>(data?: T, status = 200): Response {\n return Response.json({ data, success: true }, { status })\n}\n\n/**\n * Create a standardized error response\n *\n * @param code - Error code from ErrorCodes constant\n * @param message - Human-readable error message\n * @param status - HTTP status code (default: 400)\n * @returns Response object with JSON error body\n *\n * @example\n * ```typescript\n * return errorResponse(ErrorCodes.UNAUTHORIZED, 'You must be logged in', 401)\n * // Returns: { success: false, error: { code: 'UNAUTHORIZED', message: '...' } }\n * ```\n */\nexport function errorResponse(code: string, message: string, status = 400): Response {\n return Response.json({ error: { code, message }, success: false }, { status })\n}\n\n/**\n * Standard error codes used across the MobilizeHub plugin API\n */\nexport const ErrorCodes = {\n BAD_REQUEST: 'BAD_REQUEST',\n BROADCAST_INVALID_STATUS: 'BROADCAST_INVALID_STATUS',\n BROADCAST_NOT_FOUND: 'BROADCAST_NOT_FOUND',\n CONTACT_NOT_FOUND: 'CONTACT_NOT_FOUND',\n EMAIL_SEND_FAILED: 'EMAIL_SEND_FAILED',\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n NOT_FOUND: 'NOT_FOUND',\n RATE_LIMITED: 'RATE_LIMITED',\n TOKEN_EXPIRED: 'TOKEN_EXPIRED',\n TOKEN_INVALID: 'TOKEN_INVALID',\n UNAUTHORIZED: 'UNAUTHORIZED',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n} as const\n"],"names":["successResponse","data","status","Response","json","success","errorResponse","code","message","error","ErrorCodes","BAD_REQUEST","BROADCAST_INVALID_STATUS","BROADCAST_NOT_FOUND","CONTACT_NOT_FOUND","EMAIL_SEND_FAILED","INTERNAL_ERROR","NOT_FOUND","RATE_LIMITED","TOKEN_EXPIRED","TOKEN_INVALID","UNAUTHORIZED","VALIDATION_ERROR"],"mappings":"AAAA;;;CAGC,GAyBD;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASA,gBAAmBC,IAAQ,EAAEC,SAAS,GAAG;IACvD,OAAOC,SAASC,IAAI,CAAC;QAAEH;QAAMI,SAAS;IAAK,GAAG;QAAEH;IAAO;AACzD;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASI,cAAcC,IAAY,EAAEC,OAAe,EAAEN,SAAS,GAAG;IACvE,OAAOC,SAASC,IAAI,CAAC;QAAEK,OAAO;YAAEF;YAAMC;QAAQ;QAAGH,SAAS;IAAM,GAAG;QAAEH;IAAO;AAC9E;AAEA;;CAEC,GACD,OAAO,MAAMQ,aAAa;IACxBC,aAAa;IACbC,0BAA0B;IAC1BC,qBAAqB;IACrBC,mBAAmB;IACnBC,mBAAmB;IACnBC,gBAAgB;IAChBC,WAAW;IACXC,cAAc;IACdC,eAAe;IACfC,eAAe;IACfC,cAAc;IACdC,kBAAkB;AACpB,EAAU"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats an email address with an optional display name according to RFC 5322.
|
|
3
|
+
*
|
|
4
|
+
* @param fromName - The display name to show (e.g., "John Doe")
|
|
5
|
+
* @param fromAddress - The actual email address (e.g., "john@example.com")
|
|
6
|
+
* @returns Formatted email string. If fromName is provided: `"John Doe" <john@example.com>`
|
|
7
|
+
* Otherwise just returns the email address.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* formatFromAddress("Acme Corp", "hello@acme.com")
|
|
12
|
+
* // Returns: '"Acme Corp" <hello@acme.com>'
|
|
13
|
+
*
|
|
14
|
+
* formatFromAddress("", "hello@acme.com")
|
|
15
|
+
* // Returns: 'hello@acme.com'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatFromAddress(fromName: string, fromAddress: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Validates if a string is a properly formatted email address.
|
|
21
|
+
*
|
|
22
|
+
* Uses a permissive regex that catches most invalid formats while allowing
|
|
23
|
+
* valid edge cases like plus-addressing (user+tag@domain.com).
|
|
24
|
+
*
|
|
25
|
+
* @param email - The string to validate
|
|
26
|
+
* @returns `true` if the string appears to be a valid email format
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* isValidEmail("user@example.com") // true
|
|
31
|
+
* isValidEmail("user+tag@example.com") // true
|
|
32
|
+
* isValidEmail("invalid") // false
|
|
33
|
+
* isValidEmail("missing@domain") // false
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function isValidEmail(email: string): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats an email address with an optional display name according to RFC 5322.
|
|
3
|
+
*
|
|
4
|
+
* @param fromName - The display name to show (e.g., "John Doe")
|
|
5
|
+
* @param fromAddress - The actual email address (e.g., "john@example.com")
|
|
6
|
+
* @returns Formatted email string. If fromName is provided: `"John Doe" <john@example.com>`
|
|
7
|
+
* Otherwise just returns the email address.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* formatFromAddress("Acme Corp", "hello@acme.com")
|
|
12
|
+
* // Returns: '"Acme Corp" <hello@acme.com>'
|
|
13
|
+
*
|
|
14
|
+
* formatFromAddress("", "hello@acme.com")
|
|
15
|
+
* // Returns: 'hello@acme.com'
|
|
16
|
+
* ```
|
|
17
|
+
*/ export function formatFromAddress(fromName, fromAddress) {
|
|
18
|
+
return fromName ? `"${fromName}" <${fromAddress}>` : fromAddress;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validates if a string is a properly formatted email address.
|
|
22
|
+
*
|
|
23
|
+
* Uses a permissive regex that catches most invalid formats while allowing
|
|
24
|
+
* valid edge cases like plus-addressing (user+tag@domain.com).
|
|
25
|
+
*
|
|
26
|
+
* @param email - The string to validate
|
|
27
|
+
* @returns `true` if the string appears to be a valid email format
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* isValidEmail("user@example.com") // true
|
|
32
|
+
* isValidEmail("user+tag@example.com") // true
|
|
33
|
+
* isValidEmail("invalid") // false
|
|
34
|
+
* isValidEmail("missing@domain") // false
|
|
35
|
+
* ```
|
|
36
|
+
*/ export function isValidEmail(email) {
|
|
37
|
+
return /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(email);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=email.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/email.ts"],"sourcesContent":["/**\n * Formats an email address with an optional display name according to RFC 5322.\n *\n * @param fromName - The display name to show (e.g., \"John Doe\")\n * @param fromAddress - The actual email address (e.g., \"john@example.com\")\n * @returns Formatted email string. If fromName is provided: `\"John Doe\" <john@example.com>`\n * Otherwise just returns the email address.\n *\n * @example\n * ```typescript\n * formatFromAddress(\"Acme Corp\", \"hello@acme.com\")\n * // Returns: '\"Acme Corp\" <hello@acme.com>'\n *\n * formatFromAddress(\"\", \"hello@acme.com\")\n * // Returns: 'hello@acme.com'\n * ```\n */\nexport function formatFromAddress(fromName: string, fromAddress: string): string {\n return fromName ? `\"${fromName}\" <${fromAddress}>` : fromAddress\n}\n\n/**\n * Validates if a string is a properly formatted email address.\n *\n * Uses a permissive regex that catches most invalid formats while allowing\n * valid edge cases like plus-addressing (user+tag@domain.com).\n *\n * @param email - The string to validate\n * @returns `true` if the string appears to be a valid email format\n *\n * @example\n * ```typescript\n * isValidEmail(\"user@example.com\") // true\n * isValidEmail(\"user+tag@example.com\") // true\n * isValidEmail(\"invalid\") // false\n * isValidEmail(\"missing@domain\") // false\n * ```\n */\nexport function isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@][^\\s.@]*\\.[^\\s@]+$/.test(email)\n}\n"],"names":["formatFromAddress","fromName","fromAddress","isValidEmail","email","test"],"mappings":"AAAA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASA,kBAAkBC,QAAgB,EAAEC,WAAmB;IACrE,OAAOD,WAAW,CAAC,CAAC,EAAEA,SAAS,GAAG,EAAEC,YAAY,CAAC,CAAC,GAAGA;AACvD;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,aAAaC,KAAa;IACxC,OAAO,oCAAoCC,IAAI,CAACD;AAClD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical';
|
|
2
|
+
import type { Payload } from 'payload';
|
|
3
|
+
/**
|
|
4
|
+
* Parses Lexical editor content into multiple formats (HTML, Markdown, Plain Text)
|
|
5
|
+
* @param content - The serialized Lexical editor state
|
|
6
|
+
* @param payloadConfig - The Payload CMS configuration
|
|
7
|
+
* @returns Parsed content in HTML, Markdown, and Plain Text formats
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseLexicalContent(content: SerializedEditorState, payloadConfig: Payload['config']): Promise<{
|
|
10
|
+
html: string;
|
|
11
|
+
markdown: string;
|
|
12
|
+
plainText: string;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { convertLexicalToMarkdown, editorConfigFactory } from '@payloadcms/richtext-lexical';
|
|
2
|
+
import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html';
|
|
3
|
+
import { convertLexicalToPlaintext } from '@payloadcms/richtext-lexical/plaintext';
|
|
4
|
+
/**
|
|
5
|
+
* Parses Lexical editor content into multiple formats (HTML, Markdown, Plain Text)
|
|
6
|
+
* @param content - The serialized Lexical editor state
|
|
7
|
+
* @param payloadConfig - The Payload CMS configuration
|
|
8
|
+
* @returns Parsed content in HTML, Markdown, and Plain Text formats
|
|
9
|
+
*/ export async function parseLexicalContent(content, payloadConfig) {
|
|
10
|
+
const editorConfig = await editorConfigFactory.default({
|
|
11
|
+
config: payloadConfig
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
html: convertLexicalToHTML({
|
|
15
|
+
data: content
|
|
16
|
+
}),
|
|
17
|
+
markdown: convertLexicalToMarkdown({
|
|
18
|
+
data: content,
|
|
19
|
+
editorConfig
|
|
20
|
+
}),
|
|
21
|
+
plainText: convertLexicalToPlaintext({
|
|
22
|
+
data: content
|
|
23
|
+
})
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=lexical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/lexical.ts"],"sourcesContent":["import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'\nimport type { Payload } from 'payload'\n\nimport { convertLexicalToMarkdown, editorConfigFactory } from '@payloadcms/richtext-lexical'\nimport { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html'\nimport { convertLexicalToPlaintext } from '@payloadcms/richtext-lexical/plaintext'\n\n/**\n * Parses Lexical editor content into multiple formats (HTML, Markdown, Plain Text)\n * @param content - The serialized Lexical editor state\n * @param payloadConfig - The Payload CMS configuration\n * @returns Parsed content in HTML, Markdown, and Plain Text formats\n */\nexport async function parseLexicalContent(\n content: SerializedEditorState,\n payloadConfig: Payload['config'],\n): Promise<{\n html: string\n markdown: string\n plainText: string\n}> {\n const editorConfig = await editorConfigFactory.default({ config: payloadConfig })\n\n return {\n html: convertLexicalToHTML({ data: content }),\n markdown: convertLexicalToMarkdown({ data: content, editorConfig }),\n plainText: convertLexicalToPlaintext({ data: content }),\n }\n}\n"],"names":["convertLexicalToMarkdown","editorConfigFactory","convertLexicalToHTML","convertLexicalToPlaintext","parseLexicalContent","content","payloadConfig","editorConfig","default","config","html","data","markdown","plainText"],"mappings":"AAGA,SAASA,wBAAwB,EAAEC,mBAAmB,QAAQ,+BAA8B;AAC5F,SAASC,oBAAoB,QAAQ,oCAAmC;AACxE,SAASC,yBAAyB,QAAQ,yCAAwC;AAElF;;;;;CAKC,GACD,OAAO,eAAeC,oBACpBC,OAA8B,EAC9BC,aAAgC;IAMhC,MAAMC,eAAe,MAAMN,oBAAoBO,OAAO,CAAC;QAAEC,QAAQH;IAAc;IAE/E,OAAO;QACLI,MAAMR,qBAAqB;YAAES,MAAMN;QAAQ;QAC3CO,UAAUZ,yBAAyB;YAAEW,MAAMN;YAASE;QAAa;QACjEM,WAAWV,0BAA0B;YAAEQ,MAAMN;QAAQ;IACvD;AACF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
type UnsubscribeToken = {
|
|
2
|
+
timestamp: number;
|
|
3
|
+
tokenId: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Creates a secure unsubscribe token for the given token ID.
|
|
7
|
+
*
|
|
8
|
+
* ## Token Format
|
|
9
|
+
* The token consists of two parts separated by a dot:
|
|
10
|
+
* - Base64URL-encoded JSON payload containing tokenId and timestamp
|
|
11
|
+
* - HMAC-SHA256 signature of the payload
|
|
12
|
+
*
|
|
13
|
+
* ## Expiration
|
|
14
|
+
* Tokens expire after 30 days from creation
|
|
15
|
+
*
|
|
16
|
+
* ## Security Considerations
|
|
17
|
+
* - Tokens are signed with HMAC-SHA256 using PAYLOAD_SECRET
|
|
18
|
+
* - Verification uses timing-safe comparison to prevent timing attacks
|
|
19
|
+
* - Tokens cannot be forged without knowing PAYLOAD_SECRET
|
|
20
|
+
*
|
|
21
|
+
* @param args - Object containing tokenId
|
|
22
|
+
* @param args.tokenId - Unique identifier for the unsubscribe token
|
|
23
|
+
* @returns Base64URL-encoded token string in format: `{payload}.{signature}`
|
|
24
|
+
* @throws Error if PAYLOAD_SECRET environment variable is not defined
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const token = generateUnsubscribeToken({ tokenId: 'uuid-1234' })
|
|
29
|
+
* // Returns: 'eyJ0b2tlbklkIjoidXVpZC0xMjM0IiwidGltZXN0YW1wIjoxNzAwMDAwMDAwfQ.abc123...'
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function generateUnsubscribeToken(args: {
|
|
33
|
+
tokenId: string;
|
|
34
|
+
}): string;
|
|
35
|
+
/**
|
|
36
|
+
* Verifies an unsubscribe token and returns the decoded payload if valid.
|
|
37
|
+
*
|
|
38
|
+
* ## Verification Process
|
|
39
|
+
* 1. Splits token into payload and signature
|
|
40
|
+
* 2. Verifies HMAC signature using timing-safe comparison
|
|
41
|
+
* 3. Decodes and parses the JSON payload
|
|
42
|
+
* 4. Checks if token has expired (30 day limit)
|
|
43
|
+
*
|
|
44
|
+
* ## Security
|
|
45
|
+
* - Uses `crypto.timingSafeEqual()` to prevent timing attacks
|
|
46
|
+
* - Returns null for any invalid or expired token
|
|
47
|
+
* - Catches and handles all errors gracefully
|
|
48
|
+
*
|
|
49
|
+
* @param args - Object containing the token to verify
|
|
50
|
+
* @param args.token - The unsubscribe token to verify
|
|
51
|
+
* @returns Decoded token input with tokenId and timestamp if valid, null otherwise
|
|
52
|
+
* @throws Error if PAYLOAD_SECRET environment variable is not defined
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const result = verifyUnsubscribeToken({ token: 'eyJ0b2...' })
|
|
57
|
+
* if (result) {
|
|
58
|
+
* console.log(`Token ID: ${result.tokenId}, Created: ${new Date(result.timestamp)}`)
|
|
59
|
+
* } else {
|
|
60
|
+
* console.log('Invalid or expired token')
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function verifyUnsubscribeToken(args: {
|
|
65
|
+
token: string;
|
|
66
|
+
}): null | UnsubscribeToken;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
const UNSUBSCRIBE_TOKEN_EXPIRATION = 30 * 24 * 60 * 60 * 1000 // 30 days in milliseconds
|
|
3
|
+
;
|
|
4
|
+
const HMAC_ALGORITHM = 'sha256';
|
|
5
|
+
const TOKEN_ENCODING = 'base64url';
|
|
6
|
+
/**
|
|
7
|
+
* Creates a secure unsubscribe token for the given token ID.
|
|
8
|
+
*
|
|
9
|
+
* ## Token Format
|
|
10
|
+
* The token consists of two parts separated by a dot:
|
|
11
|
+
* - Base64URL-encoded JSON payload containing tokenId and timestamp
|
|
12
|
+
* - HMAC-SHA256 signature of the payload
|
|
13
|
+
*
|
|
14
|
+
* ## Expiration
|
|
15
|
+
* Tokens expire after 30 days from creation
|
|
16
|
+
*
|
|
17
|
+
* ## Security Considerations
|
|
18
|
+
* - Tokens are signed with HMAC-SHA256 using PAYLOAD_SECRET
|
|
19
|
+
* - Verification uses timing-safe comparison to prevent timing attacks
|
|
20
|
+
* - Tokens cannot be forged without knowing PAYLOAD_SECRET
|
|
21
|
+
*
|
|
22
|
+
* @param args - Object containing tokenId
|
|
23
|
+
* @param args.tokenId - Unique identifier for the unsubscribe token
|
|
24
|
+
* @returns Base64URL-encoded token string in format: `{payload}.{signature}`
|
|
25
|
+
* @throws Error if PAYLOAD_SECRET environment variable is not defined
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const token = generateUnsubscribeToken({ tokenId: 'uuid-1234' })
|
|
30
|
+
* // Returns: 'eyJ0b2tlbklkIjoidXVpZC0xMjM0IiwidGltZXN0YW1wIjoxNzAwMDAwMDAwfQ.abc123...'
|
|
31
|
+
* ```
|
|
32
|
+
*/ export function generateUnsubscribeToken(args) {
|
|
33
|
+
const secret = process.env.PAYLOAD_SECRET;
|
|
34
|
+
if (!secret) {
|
|
35
|
+
throw new Error('PAYLOAD_SECRET environment variable is not defined');
|
|
36
|
+
}
|
|
37
|
+
const input = {
|
|
38
|
+
timestamp: Date.now(),
|
|
39
|
+
tokenId: args.tokenId
|
|
40
|
+
};
|
|
41
|
+
const inputBase64 = Buffer.from(JSON.stringify(input)).toString(TOKEN_ENCODING);
|
|
42
|
+
const hmac = crypto.createHmac(HMAC_ALGORITHM, secret);
|
|
43
|
+
hmac.update(inputBase64);
|
|
44
|
+
const signature = hmac.digest(TOKEN_ENCODING);
|
|
45
|
+
return `${inputBase64}.${signature}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Verifies an unsubscribe token and returns the decoded payload if valid.
|
|
49
|
+
*
|
|
50
|
+
* ## Verification Process
|
|
51
|
+
* 1. Splits token into payload and signature
|
|
52
|
+
* 2. Verifies HMAC signature using timing-safe comparison
|
|
53
|
+
* 3. Decodes and parses the JSON payload
|
|
54
|
+
* 4. Checks if token has expired (30 day limit)
|
|
55
|
+
*
|
|
56
|
+
* ## Security
|
|
57
|
+
* - Uses `crypto.timingSafeEqual()` to prevent timing attacks
|
|
58
|
+
* - Returns null for any invalid or expired token
|
|
59
|
+
* - Catches and handles all errors gracefully
|
|
60
|
+
*
|
|
61
|
+
* @param args - Object containing the token to verify
|
|
62
|
+
* @param args.token - The unsubscribe token to verify
|
|
63
|
+
* @returns Decoded token input with tokenId and timestamp if valid, null otherwise
|
|
64
|
+
* @throws Error if PAYLOAD_SECRET environment variable is not defined
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const result = verifyUnsubscribeToken({ token: 'eyJ0b2...' })
|
|
69
|
+
* if (result) {
|
|
70
|
+
* console.log(`Token ID: ${result.tokenId}, Created: ${new Date(result.timestamp)}`)
|
|
71
|
+
* } else {
|
|
72
|
+
* console.log('Invalid or expired token')
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/ export function verifyUnsubscribeToken(args) {
|
|
76
|
+
try {
|
|
77
|
+
const secret = process.env.PAYLOAD_SECRET;
|
|
78
|
+
if (!secret) {
|
|
79
|
+
throw new Error('PAYLOAD_SECRET environment variable is not defined');
|
|
80
|
+
}
|
|
81
|
+
const parts = args.token.split('.');
|
|
82
|
+
if (parts.length !== 2) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const [inputBase64, signature] = parts;
|
|
86
|
+
const hmac = crypto.createHmac(HMAC_ALGORITHM, secret);
|
|
87
|
+
hmac.update(inputBase64);
|
|
88
|
+
const expectedSignature = hmac.digest(TOKEN_ENCODING);
|
|
89
|
+
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const inputJson = Buffer.from(inputBase64, TOKEN_ENCODING).toString('utf-8');
|
|
93
|
+
const input = JSON.parse(inputJson);
|
|
94
|
+
if (Date.now() - input.timestamp > UNSUBSCRIBE_TOKEN_EXPIRATION) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return input;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=unsubscribe-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/unsubscribe-token.ts"],"sourcesContent":["import crypto from 'crypto'\n\nconst UNSUBSCRIBE_TOKEN_EXPIRATION = 30 * 24 * 60 * 60 * 1000 // 30 days in milliseconds\n\nconst HMAC_ALGORITHM = 'sha256'\n\nconst TOKEN_ENCODING = 'base64url'\n\ntype UnsubscribeToken = {\n timestamp: number\n tokenId: string\n}\n\n/**\n * Creates a secure unsubscribe token for the given token ID.\n *\n * ## Token Format\n * The token consists of two parts separated by a dot:\n * - Base64URL-encoded JSON payload containing tokenId and timestamp\n * - HMAC-SHA256 signature of the payload\n *\n * ## Expiration\n * Tokens expire after 30 days from creation\n *\n * ## Security Considerations\n * - Tokens are signed with HMAC-SHA256 using PAYLOAD_SECRET\n * - Verification uses timing-safe comparison to prevent timing attacks\n * - Tokens cannot be forged without knowing PAYLOAD_SECRET\n *\n * @param args - Object containing tokenId\n * @param args.tokenId - Unique identifier for the unsubscribe token\n * @returns Base64URL-encoded token string in format: `{payload}.{signature}`\n * @throws Error if PAYLOAD_SECRET environment variable is not defined\n *\n * @example\n * ```typescript\n * const token = generateUnsubscribeToken({ tokenId: 'uuid-1234' })\n * // Returns: 'eyJ0b2tlbklkIjoidXVpZC0xMjM0IiwidGltZXN0YW1wIjoxNzAwMDAwMDAwfQ.abc123...'\n * ```\n */\nexport function generateUnsubscribeToken(args: { tokenId: string }): string {\n const secret = process.env.PAYLOAD_SECRET\n if (!secret) {\n throw new Error('PAYLOAD_SECRET environment variable is not defined')\n }\n\n const input: UnsubscribeToken = {\n timestamp: Date.now(),\n tokenId: args.tokenId,\n }\n\n const inputBase64 = Buffer.from(JSON.stringify(input)).toString(TOKEN_ENCODING)\n\n const hmac = crypto.createHmac(HMAC_ALGORITHM, secret)\n hmac.update(inputBase64)\n const signature = hmac.digest(TOKEN_ENCODING)\n\n return `${inputBase64}.${signature}`\n}\n\n/**\n * Verifies an unsubscribe token and returns the decoded payload if valid.\n *\n * ## Verification Process\n * 1. Splits token into payload and signature\n * 2. Verifies HMAC signature using timing-safe comparison\n * 3. Decodes and parses the JSON payload\n * 4. Checks if token has expired (30 day limit)\n *\n * ## Security\n * - Uses `crypto.timingSafeEqual()` to prevent timing attacks\n * - Returns null for any invalid or expired token\n * - Catches and handles all errors gracefully\n *\n * @param args - Object containing the token to verify\n * @param args.token - The unsubscribe token to verify\n * @returns Decoded token input with tokenId and timestamp if valid, null otherwise\n * @throws Error if PAYLOAD_SECRET environment variable is not defined\n *\n * @example\n * ```typescript\n * const result = verifyUnsubscribeToken({ token: 'eyJ0b2...' })\n * if (result) {\n * console.log(`Token ID: ${result.tokenId}, Created: ${new Date(result.timestamp)}`)\n * } else {\n * console.log('Invalid or expired token')\n * }\n * ```\n */\nexport function verifyUnsubscribeToken(args: { token: string }): null | UnsubscribeToken {\n try {\n const secret = process.env.PAYLOAD_SECRET\n if (!secret) {\n throw new Error('PAYLOAD_SECRET environment variable is not defined')\n }\n\n const parts = args.token.split('.')\n if (parts.length !== 2) {\n return null\n }\n\n const [inputBase64, signature] = parts\n\n const hmac = crypto.createHmac(HMAC_ALGORITHM, secret)\n hmac.update(inputBase64)\n const expectedSignature = hmac.digest(TOKEN_ENCODING)\n\n if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {\n return null\n }\n\n const inputJson = Buffer.from(inputBase64, TOKEN_ENCODING).toString('utf-8')\n const input: UnsubscribeToken = JSON.parse(inputJson)\n\n if (Date.now() - input.timestamp > UNSUBSCRIBE_TOKEN_EXPIRATION) {\n return null\n }\n\n return input\n } catch {\n return null\n }\n}\n"],"names":["crypto","UNSUBSCRIBE_TOKEN_EXPIRATION","HMAC_ALGORITHM","TOKEN_ENCODING","generateUnsubscribeToken","args","secret","process","env","PAYLOAD_SECRET","Error","input","timestamp","Date","now","tokenId","inputBase64","Buffer","from","JSON","stringify","toString","hmac","createHmac","update","signature","digest","verifyUnsubscribeToken","parts","token","split","length","expectedSignature","timingSafeEqual","inputJson","parse"],"mappings":"AAAA,OAAOA,YAAY,SAAQ;AAE3B,MAAMC,+BAA+B,KAAK,KAAK,KAAK,KAAK,KAAK,0BAA0B;;AAExF,MAAMC,iBAAiB;AAEvB,MAAMC,iBAAiB;AAOvB;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC,GACD,OAAO,SAASC,yBAAyBC,IAAyB;IAChE,MAAMC,SAASC,QAAQC,GAAG,CAACC,cAAc;IACzC,IAAI,CAACH,QAAQ;QACX,MAAM,IAAII,MAAM;IAClB;IAEA,MAAMC,QAA0B;QAC9BC,WAAWC,KAAKC,GAAG;QACnBC,SAASV,KAAKU,OAAO;IACvB;IAEA,MAAMC,cAAcC,OAAOC,IAAI,CAACC,KAAKC,SAAS,CAACT,QAAQU,QAAQ,CAAClB;IAEhE,MAAMmB,OAAOtB,OAAOuB,UAAU,CAACrB,gBAAgBI;IAC/CgB,KAAKE,MAAM,CAACR;IACZ,MAAMS,YAAYH,KAAKI,MAAM,CAACvB;IAE9B,OAAO,GAAGa,YAAY,CAAC,EAAES,WAAW;AACtC;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,SAASE,uBAAuBtB,IAAuB;IAC5D,IAAI;QACF,MAAMC,SAASC,QAAQC,GAAG,CAACC,cAAc;QACzC,IAAI,CAACH,QAAQ;YACX,MAAM,IAAII,MAAM;QAClB;QAEA,MAAMkB,QAAQvB,KAAKwB,KAAK,CAACC,KAAK,CAAC;QAC/B,IAAIF,MAAMG,MAAM,KAAK,GAAG;YACtB,OAAO;QACT;QAEA,MAAM,CAACf,aAAaS,UAAU,GAAGG;QAEjC,MAAMN,OAAOtB,OAAOuB,UAAU,CAACrB,gBAAgBI;QAC/CgB,KAAKE,MAAM,CAACR;QACZ,MAAMgB,oBAAoBV,KAAKI,MAAM,CAACvB;QAEtC,IAAI,CAACH,OAAOiC,eAAe,CAAChB,OAAOC,IAAI,CAACO,YAAYR,OAAOC,IAAI,CAACc,qBAAqB;YACnF,OAAO;QACT;QAEA,MAAME,YAAYjB,OAAOC,IAAI,CAACF,aAAab,gBAAgBkB,QAAQ,CAAC;QACpE,MAAMV,QAA0BQ,KAAKgB,KAAK,CAACD;QAE3C,IAAIrB,KAAKC,GAAG,KAAKH,MAAMC,SAAS,GAAGX,8BAA8B;YAC/D,OAAO;QACT;QAEA,OAAOU;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mobilizehub/payload-plugin",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Edvocacy plugin for Payload",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
7
7
|
"type": "module",
|
|
@@ -20,6 +20,16 @@
|
|
|
20
20
|
"import": "./dist/exports/rsc.js",
|
|
21
21
|
"types": "./dist/exports/rsc.d.ts",
|
|
22
22
|
"default": "./dist/exports/rsc.js"
|
|
23
|
+
},
|
|
24
|
+
"./react": {
|
|
25
|
+
"import": "./dist/react/index.js",
|
|
26
|
+
"types": "./dist/react/index.d.ts",
|
|
27
|
+
"default": "./dist/react/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./adapters": {
|
|
30
|
+
"import": "./dist/adapters/index.js",
|
|
31
|
+
"types": "./dist/adapters/index.d.ts",
|
|
32
|
+
"default": "./dist/adapters/index.js"
|
|
23
33
|
}
|
|
24
34
|
},
|
|
25
35
|
"main": "./dist/index.js",
|
|
@@ -30,12 +40,12 @@
|
|
|
30
40
|
"devDependencies": {
|
|
31
41
|
"@changesets/cli": "^2.29.8",
|
|
32
42
|
"@eslint/eslintrc": "^3.2.0",
|
|
33
|
-
"@payloadcms/db-postgres": "3.
|
|
34
|
-
"@payloadcms/db-sqlite": "3.
|
|
43
|
+
"@payloadcms/db-postgres": "3.68.5",
|
|
44
|
+
"@payloadcms/db-sqlite": "3.68.5",
|
|
35
45
|
"@payloadcms/eslint-config": "3.9.0",
|
|
36
|
-
"@payloadcms/next": "3.
|
|
37
|
-
"@payloadcms/richtext-lexical": "3.
|
|
38
|
-
"@payloadcms/ui": "3.
|
|
46
|
+
"@payloadcms/next": "3.68.5",
|
|
47
|
+
"@payloadcms/richtext-lexical": "3.68.5",
|
|
48
|
+
"@payloadcms/ui": "3.68.5",
|
|
39
49
|
"@playwright/test": "1.56.1",
|
|
40
50
|
"@swc-node/register": "1.10.9",
|
|
41
51
|
"@swc/cli": "0.6.0",
|
|
@@ -50,7 +60,7 @@
|
|
|
50
60
|
"husky": "^9.1.7",
|
|
51
61
|
"next": "15.4.10",
|
|
52
62
|
"open": "^10.1.0",
|
|
53
|
-
"payload": "3.
|
|
63
|
+
"payload": "3.68.5",
|
|
54
64
|
"prettier": "^3.4.2",
|
|
55
65
|
"qs-esm": "7.0.2",
|
|
56
66
|
"react": "19.2.1",
|
|
@@ -63,13 +73,17 @@
|
|
|
63
73
|
"vitest": "^3.1.2"
|
|
64
74
|
},
|
|
65
75
|
"peerDependencies": {
|
|
66
|
-
"payload": "^3.
|
|
76
|
+
"payload": "^3.68.5"
|
|
67
77
|
},
|
|
68
78
|
"engines": {
|
|
69
79
|
"node": "^18.20.2 || >=20.9.0",
|
|
70
80
|
"pnpm": "^9 || ^10"
|
|
71
81
|
},
|
|
72
82
|
"registry": "https://registry.npmjs.org/",
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"i18n-iso-countries": "^7.14.0",
|
|
85
|
+
"zod": "^4.2.1"
|
|
86
|
+
},
|
|
73
87
|
"scripts": {
|
|
74
88
|
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
|
75
89
|
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
@@ -79,9 +93,11 @@
|
|
|
79
93
|
"dev": "next dev dev --turbo",
|
|
80
94
|
"dev:generate-importmap": "pnpm dev:payload generate:importmap",
|
|
81
95
|
"dev:generate-types": "pnpm dev:payload generate:types",
|
|
96
|
+
"dev:generate": "pnpm dev:generate-importmap && pnpm dev:generate-types",
|
|
82
97
|
"dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
|
|
83
98
|
"generate:importmap": "pnpm dev:generate-importmap",
|
|
84
99
|
"generate:types": "pnpm dev:generate-types",
|
|
100
|
+
"generate": "pnpm generate:importmap && pnpm generate:types",
|
|
85
101
|
"lint": "eslint",
|
|
86
102
|
"lint:fix": "eslint ./src --fix",
|
|
87
103
|
"test": "pnpm test:int && pnpm test:e2e",
|