payload-subscribers-plugin 0.0.9 → 0.0.11
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 +233 -26
- package/dist/components/app/RequestMagicLink.d.ts +21 -4
- package/dist/components/app/RequestMagicLink.js +11 -56
- package/dist/components/app/RequestMagicLink.js.map +1 -1
- package/dist/components/app/RequestOrSubscribe.d.ts +21 -4
- package/dist/components/app/RequestOrSubscribe.js +5 -8
- package/dist/components/app/RequestOrSubscribe.js.map +1 -1
- package/dist/components/app/SelectOptInChannels.d.ts +26 -3
- package/dist/components/app/SelectOptInChannels.js +4 -1
- package/dist/components/app/SelectOptInChannels.js.map +1 -1
- package/dist/components/app/Subscribe.d.ts +26 -5
- package/dist/components/app/Subscribe.js +40 -73
- package/dist/components/app/Subscribe.js.map +1 -1
- package/dist/components/app/SubscriberMenu.d.ts +18 -6
- package/dist/components/app/SubscriberMenu.js +9 -8
- package/dist/components/app/SubscriberMenu.js.map +1 -1
- package/dist/components/app/Unsubscribe.d.ts +26 -12
- package/dist/components/app/Unsubscribe.js +63 -127
- package/dist/components/app/Unsubscribe.js.map +1 -1
- package/dist/components/app/VerifyMagicLink.d.ts +32 -14
- package/dist/components/app/VerifyMagicLink.js +60 -110
- package/dist/components/app/VerifyMagicLink.js.map +1 -1
- package/dist/contexts/SubscriberProvider.js +1 -1
- package/dist/contexts/SubscriberProvider.js.map +1 -1
- package/dist/endpoints/requestMagicLink.d.ts +6 -4
- package/dist/endpoints/requestMagicLink.js +16 -12
- package/dist/endpoints/requestMagicLink.js.map +1 -1
- package/dist/endpoints/subscribe.d.ts +6 -2
- package/dist/endpoints/subscribe.js +19 -17
- package/dist/endpoints/subscribe.js.map +1 -1
- package/dist/endpoints/unsubscribe.js +17 -15
- package/dist/endpoints/unsubscribe.js.map +1 -1
- package/dist/exports/ui.d.ts +4 -0
- package/dist/exports/ui.js +4 -0
- package/dist/exports/ui.js.map +1 -1
- package/dist/helpers/utilities.d.ts +1 -0
- package/dist/helpers/utilities.js +6 -0
- package/dist/helpers/utilities.js.map +1 -0
- package/dist/hooks/useRequestMagicLink.d.ts +35 -0
- package/dist/hooks/useRequestMagicLink.js +61 -0
- package/dist/hooks/useRequestMagicLink.js.map +1 -0
- package/dist/hooks/useSubscribe.d.ts +38 -0
- package/dist/hooks/useSubscribe.js +62 -0
- package/dist/hooks/useSubscribe.js.map +1 -0
- package/dist/hooks/useUnsubscribe.d.ts +43 -0
- package/dist/hooks/useUnsubscribe.js +86 -0
- package/dist/hooks/useUnsubscribe.js.map +1 -0
- package/dist/hooks/useVerifyMagicLink.d.ts +31 -0
- package/dist/hooks/useVerifyMagicLink.js +74 -0
- package/dist/hooks/useVerifyMagicLink.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +12 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/contexts/SubscriberProvider.tsx"],"sourcesContent":["'use client'\n\nimport { PayloadSDK } from '@payloadcms/sdk'\nimport { type ReactNode, useCallback, useEffect } from 'react'\nimport { createContext, useContext, useMemo, useState } from 'react'\n\nimport type { Config, Subscriber } from '../copied/payload-types.js'\n\nimport { useServerUrl } from '../react-hooks/useServerUrl.js'\n\n/** Value provided by SubscriberProvider: current subscriber, auth state, and actions. */\nexport type SubscriberContextType = {\n isLoaded: boolean\n logOut: () => void\n permissions: any\n refreshSubscriber: () => void\n subscriber: null | Subscriber\n}\n\nconst SubscriberContext = createContext<SubscriberContextType | undefined>(undefined)\n\n/** Props for SubscriberProvider. */\ninterface ProviderProps {\n children?: ReactNode\n}\n\n/**\n * Provider that fetches and holds the current subscriber auth state (via POST /api/subscriberAuth).\n * Exposes subscriber, permissions, refreshSubscriber, and logOut to descendants. Must wrap any\n * component that uses useSubscriber().\n *\n * @param props.children - React tree to wrap\n * @returns SubscriberContext.Provider with current auth state and actions\n */\nexport function SubscriberProvider({ children }: ProviderProps) {\n // eslint-disable-next-line\n const [subscriber, setSubscriber] = useState<null | (Subscriber & { optIns: string[] })>(null)\n\n const { serverURL } = useServerUrl()\n\n // Keep track of if the selection content is loaded yet\n const [isLoaded, setIsLoaded] = useState(false)\n\n const [permissions, setPermissions] = useState<any>()\n\n const refreshSubscriber = useCallback(async () => {\n const initSubscriber = async () => {\n setIsLoaded(false)\n try {\n const authResponse = await fetch('/api/subscriberAuth', {\n // body: JSON.stringify({}),\n method: 'POST',\n })\n\n if (authResponse.ok) {\n // Call the server function to get the user data\n const { permissions, subscriber } = await authResponse.json()\n // console.log(`subscriber = `, subscriber)\n // console.log(`permissions = `, permissions)\n setPermissions(permissions)\n setSubscriber(subscriber)\n } else {\n setPermissions(null)\n setSubscriber(null)\n }\n } catch (error: unknown) {\n console.log(`authResponse error`, error)\n }\n setIsLoaded(true)\n }\n await initSubscriber()\n }, [serverURL])\n\n const logOut = useCallback(async () => {\n setIsLoaded(false)\n try {\n // const sdk = new PayloadSDK<Config>({\n // baseURL: serverURL || '',\n // })\n // const logoutResponse = await sdk.request({\n // json: {},\n // method: 'POST',\n // path: '/api/logout',\n // })\n // Unsure why sdk isn't working here\n const logoutResponse = await fetch('/api/logout', {\n method: 'POST',\n })\n\n // console.log(`logoutResponse`, logoutResponse)\n\n if (logoutResponse.ok) {\n setSubscriber(null)\n setPermissions(null)\n }\n } catch (error: unknown) {\n console.log(`logoutResponse error`, error)\n }\n setIsLoaded(true)\n }, [])\n\n useEffect(() => {\n void refreshSubscriber()\n }, [refreshSubscriber])
|
|
1
|
+
{"version":3,"sources":["../../src/contexts/SubscriberProvider.tsx"],"sourcesContent":["'use client'\n\nimport { PayloadSDK } from '@payloadcms/sdk'\nimport { type ReactNode, useCallback, useEffect } from 'react'\nimport { createContext, useContext, useMemo, useState } from 'react'\n\nimport type { Config, Subscriber } from '../copied/payload-types.js'\n\nimport { useServerUrl } from '../react-hooks/useServerUrl.js'\n\n/** Value provided by SubscriberProvider: current subscriber, auth state, and actions. */\nexport type SubscriberContextType = {\n isLoaded: boolean\n logOut: () => void\n permissions: any\n refreshSubscriber: () => void\n subscriber: null | Subscriber\n}\n\nconst SubscriberContext = createContext<SubscriberContextType | undefined>(undefined)\n\n/** Props for SubscriberProvider. */\ninterface ProviderProps {\n children?: ReactNode\n}\n\n/**\n * Provider that fetches and holds the current subscriber auth state (via POST /api/subscriberAuth).\n * Exposes subscriber, permissions, refreshSubscriber, and logOut to descendants. Must wrap any\n * component that uses useSubscriber().\n *\n * @param props.children - React tree to wrap\n * @returns SubscriberContext.Provider with current auth state and actions\n */\nexport function SubscriberProvider({ children }: ProviderProps) {\n // eslint-disable-next-line\n const [subscriber, setSubscriber] = useState<null | (Subscriber & { optIns: string[] })>(null)\n\n const { serverURL } = useServerUrl()\n\n // Keep track of if the selection content is loaded yet\n const [isLoaded, setIsLoaded] = useState(false)\n\n const [permissions, setPermissions] = useState<any>()\n\n const refreshSubscriber = useCallback(async () => {\n const initSubscriber = async () => {\n setIsLoaded(false)\n try {\n const authResponse = await fetch('/api/subscriberAuth', {\n // body: JSON.stringify({}),\n method: 'POST',\n })\n\n if (authResponse.ok) {\n // Call the server function to get the user data\n const { permissions, subscriber } = await authResponse.json()\n // console.log(`subscriber = `, subscriber)\n // console.log(`permissions = `, permissions)\n setPermissions(permissions)\n setSubscriber(subscriber)\n } else {\n setPermissions(null)\n setSubscriber(null)\n }\n } catch (error: unknown) {\n console.log(`authResponse error`, error)\n }\n setIsLoaded(true)\n }\n await initSubscriber()\n }, [serverURL])\n\n const logOut = useCallback(async () => {\n setIsLoaded(false)\n try {\n // const sdk = new PayloadSDK<Config>({\n // baseURL: serverURL || '',\n // })\n // const logoutResponse = await sdk.request({\n // json: {},\n // method: 'POST',\n // path: '/api/logout',\n // })\n // Unsure why sdk isn't working here\n const logoutResponse = await fetch('/api/logout', {\n method: 'POST',\n })\n\n // console.log(`logoutResponse`, logoutResponse)\n\n if (logoutResponse.ok) {\n setSubscriber(null)\n setPermissions(null)\n }\n } catch (error: unknown) {\n console.log(`logoutResponse error`, error)\n }\n setIsLoaded(true)\n }, [])\n\n useEffect(() => {\n void refreshSubscriber()\n }, [refreshSubscriber])\n\n // Memoize the value to prevent unnecessary re-renders in consumers\n const contextValue: SubscriberContextType = useMemo(\n () => ({\n isLoaded,\n logOut,\n permissions,\n refreshSubscriber,\n subscriber,\n }),\n [isLoaded, logOut, permissions, refreshSubscriber, subscriber],\n )\n\n return <SubscriberContext.Provider value={contextValue}>{children}</SubscriberContext.Provider>\n}\n\n/**\n * Consumes SubscriberContext. Use only inside a SubscriberProvider.\n *\n * @returns Current subscriber (or null), permissions, isLoaded, refreshSubscriber, and logOut\n * @throws Error if used outside SubscriberProvider\n */\nexport function useSubscriber() {\n const context = useContext(SubscriberContext)\n if (context === undefined) {\n throw new Error('useSubscriber must be used within a SubscriberProvider')\n }\n return context\n}\n"],"names":["useCallback","useEffect","createContext","useContext","useMemo","useState","useServerUrl","SubscriberContext","undefined","SubscriberProvider","children","subscriber","setSubscriber","serverURL","isLoaded","setIsLoaded","permissions","setPermissions","refreshSubscriber","initSubscriber","authResponse","fetch","method","ok","json","error","console","log","logOut","logoutResponse","contextValue","Provider","value","useSubscriber","context","Error"],"mappings":"AAAA;;AAGA,SAAyBA,WAAW,EAAEC,SAAS,QAAQ,QAAO;AAC9D,SAASC,aAAa,EAAEC,UAAU,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAIpE,SAASC,YAAY,QAAQ,iCAAgC;AAW7D,MAAMC,kCAAoBL,cAAiDM;AAO3E;;;;;;;CAOC,GACD,OAAO,SAASC,mBAAmB,EAAEC,QAAQ,EAAiB;IAC5D,2BAA2B;IAC3B,MAAM,CAACC,YAAYC,cAAc,GAAGP,SAAqD;IAEzF,MAAM,EAAEQ,SAAS,EAAE,GAAGP;IAEtB,uDAAuD;IACvD,MAAM,CAACQ,UAAUC,YAAY,GAAGV,SAAS;IAEzC,MAAM,CAACW,aAAaC,eAAe,GAAGZ;IAEtC,MAAMa,oBAAoBlB,YAAY;QACpC,MAAMmB,iBAAiB;YACrBJ,YAAY;YACZ,IAAI;gBACF,MAAMK,eAAe,MAAMC,MAAM,uBAAuB;oBACtD,4BAA4B;oBAC5BC,QAAQ;gBACV;gBAEA,IAAIF,aAAaG,EAAE,EAAE;oBACnB,gDAAgD;oBAChD,MAAM,EAAEP,WAAW,EAAEL,UAAU,EAAE,GAAG,MAAMS,aAAaI,IAAI;oBAC3D,2CAA2C;oBAC3C,6CAA6C;oBAC7CP,eAAeD;oBACfJ,cAAcD;gBAChB,OAAO;oBACLM,eAAe;oBACfL,cAAc;gBAChB;YACF,EAAE,OAAOa,OAAgB;gBACvBC,QAAQC,GAAG,CAAC,CAAC,kBAAkB,CAAC,EAAEF;YACpC;YACAV,YAAY;QACd;QACA,MAAMI;IACR,GAAG;QAACN;KAAU;IAEd,MAAMe,SAAS5B,YAAY;QACzBe,YAAY;QACZ,IAAI;YACF,uCAAuC;YACvC,8BAA8B;YAC9B,KAAK;YACL,6CAA6C;YAC7C,cAAc;YACd,oBAAoB;YACpB,yBAAyB;YACzB,KAAK;YACL,oCAAoC;YACpC,MAAMc,iBAAiB,MAAMR,MAAM,eAAe;gBAChDC,QAAQ;YACV;YAEA,gDAAgD;YAEhD,IAAIO,eAAeN,EAAE,EAAE;gBACrBX,cAAc;gBACdK,eAAe;YACjB;QACF,EAAE,OAAOQ,OAAgB;YACvBC,QAAQC,GAAG,CAAC,CAAC,oBAAoB,CAAC,EAAEF;QACtC;QACAV,YAAY;IACd,GAAG,EAAE;IAELd,UAAU;QACR,KAAKiB;IACP,GAAG;QAACA;KAAkB;IAEtB,mEAAmE;IACnE,MAAMY,eAAsC1B,QAC1C,IAAO,CAAA;YACLU;YACAc;YACAZ;YACAE;YACAP;QACF,CAAA,GACA;QAACG;QAAUc;QAAQZ;QAAaE;QAAmBP;KAAW;IAGhE,qBAAO,KAACJ,kBAAkBwB,QAAQ;QAACC,OAAOF;kBAAepB;;AAC3D;AAEA;;;;;CAKC,GACD,OAAO,SAASuB;IACd,MAAMC,UAAU/B,WAAWI;IAC3B,IAAI2B,YAAY1B,WAAW;QACzB,MAAM,IAAI2B,MAAM;IAClB;IACA,OAAOD;AACT"}
|
|
@@ -11,12 +11,14 @@ export type RequestMagicLinkResponse = {
|
|
|
11
11
|
* Sends a magic-link email to the given address (creates a pending subscriber if needed).
|
|
12
12
|
*
|
|
13
13
|
* @param options - Config options for the endpoint
|
|
14
|
-
* @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
|
|
15
|
-
* @param options.
|
|
14
|
+
* @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)
|
|
15
|
+
* @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links
|
|
16
|
+
* @param options.verifyURL - (required) The URL to use for verify links
|
|
16
17
|
* @returns Payload Endpoint config for POST /emailToken
|
|
17
18
|
*/
|
|
18
|
-
declare function createEndpointRequestMagicLink({ subscribersCollectionSlug,
|
|
19
|
+
declare function createEndpointRequestMagicLink({ subscribersCollectionSlug, unsubscribeURL, verifyURL, }: {
|
|
19
20
|
subscribersCollectionSlug: CollectionSlug;
|
|
20
|
-
|
|
21
|
+
unsubscribeURL?: URL;
|
|
22
|
+
verifyURL: URL;
|
|
21
23
|
}): Endpoint;
|
|
22
24
|
export default createEndpointRequestMagicLink;
|
|
@@ -5,24 +5,28 @@ import { getHmacHash, getTokenAndHash } from '../helpers/token.js';
|
|
|
5
5
|
* Sends a magic-link email to the given address (creates a pending subscriber if needed).
|
|
6
6
|
*
|
|
7
7
|
* @param options - Config options for the endpoint
|
|
8
|
-
* @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
|
|
9
|
-
* @param options.
|
|
8
|
+
* @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)
|
|
9
|
+
* @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links
|
|
10
|
+
* @param options.verifyURL - (required) The URL to use for verify links
|
|
10
11
|
* @returns Payload Endpoint config for POST /emailToken
|
|
11
|
-
*/ function createEndpointRequestMagicLink({ subscribersCollectionSlug = defaultCollectionSlug,
|
|
12
|
+
*/ function createEndpointRequestMagicLink({ subscribersCollectionSlug = defaultCollectionSlug, unsubscribeURL, verifyURL }) {
|
|
13
|
+
// verifyURL required
|
|
14
|
+
if (!verifyURL || !verifyURL.href) {
|
|
15
|
+
throw new Error('A verify URL is required');
|
|
16
|
+
}
|
|
12
17
|
/**
|
|
13
|
-
* Handler for POST /emailToken.
|
|
18
|
+
* Handler for POST /emailToken. Takes an email parameter. Creates/updates a pending
|
|
14
19
|
* subscriber with a verification token, and sends a magic-link email.
|
|
15
20
|
*
|
|
16
|
-
* @param req - Payload request
|
|
21
|
+
* @param req - Payload request. Expects body to be a json object { email, verifyData }
|
|
17
22
|
* @returns 200 with `emailResult` and `now` on success; 400 with `error` and `now` on bad data or email failure
|
|
18
23
|
*/ const requestMagicLinkHandler = async (req)=>{
|
|
19
24
|
const data = req?.json ? await req.json() : {};
|
|
20
|
-
const { email,
|
|
25
|
+
const { email, verifyData } = data // if by POST data
|
|
21
26
|
;
|
|
22
|
-
|
|
23
|
-
if (!email || !verifyUrl) {
|
|
27
|
+
if (!email) {
|
|
24
28
|
return Response.json({
|
|
25
|
-
error: '
|
|
29
|
+
error: 'Email required',
|
|
26
30
|
now: new Date().toISOString()
|
|
27
31
|
}, {
|
|
28
32
|
status: 400
|
|
@@ -55,7 +59,7 @@ import { getHmacHash, getTokenAndHash } from '../helpers/token.js';
|
|
|
55
59
|
});
|
|
56
60
|
if (!createResult) {
|
|
57
61
|
return Response.json({
|
|
58
|
-
error: '
|
|
62
|
+
error: 'Error creating subscriber',
|
|
59
63
|
now: new Date().toISOString()
|
|
60
64
|
}, {
|
|
61
65
|
status: 400
|
|
@@ -81,8 +85,8 @@ import { getHmacHash, getTokenAndHash } from '../helpers/token.js';
|
|
|
81
85
|
});
|
|
82
86
|
const { hashToken: unsubscribeHash } = getHmacHash(email);
|
|
83
87
|
// Send email
|
|
84
|
-
const magicLink = `${
|
|
85
|
-
const unsubscribeLink = !
|
|
88
|
+
const magicLink = `${verifyURL?.href}${verifyURL?.search ? '&' : '?'}token=${encodeURIComponent(token)}&email=${encodeURIComponent(email)}&verifyData=${encodeURIComponent(verifyData)}`;
|
|
89
|
+
const unsubscribeLink = !unsubscribeURL ? undefined : `${unsubscribeURL?.href}${unsubscribeURL?.search ? '&' : '?'}email=${encodeURIComponent(email)}&hash=${encodeURIComponent(unsubscribeHash)}`;
|
|
86
90
|
const subject = data.subject || 'Your Magic Login Link';
|
|
87
91
|
const message = `
|
|
88
92
|
${data.message || '<p>You requested a magic link to log in. Click the button below</p>'}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/requestMagicLink.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler, PayloadRequest, TypedUser } from 'payload'\n\nimport
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/requestMagicLink.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler, PayloadRequest, TypedUser } from 'payload'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash, getTokenAndHash } from '../helpers/token.js'\n\nexport type RequestMagicLinkResponse =\n | {\n emailResult: any\n now: string\n }\n | {\n error: string\n now: string\n }\n\n/**\n * Factory that creates the request-magic-link endpoint config and handler.\n * Sends a magic-link email to the given address (creates a pending subscriber if needed).\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)\n * @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links\n * @param options.verifyURL - (required) The URL to use for verify links\n * @returns Payload Endpoint config for POST /emailToken\n */\nfunction createEndpointRequestMagicLink({\n subscribersCollectionSlug = defaultCollectionSlug,\n unsubscribeURL,\n verifyURL,\n}: {\n subscribersCollectionSlug: CollectionSlug\n unsubscribeURL?: URL\n verifyURL: URL\n}): Endpoint {\n // verifyURL required\n if (!verifyURL || !verifyURL.href) {\n throw new Error('A verify URL is required')\n }\n /**\n * Handler for POST /emailToken. Takes an email parameter. Creates/updates a pending\n * subscriber with a verification token, and sends a magic-link email.\n *\n * @param req - Payload request. Expects body to be a json object { email, verifyData }\n * @returns 200 with `emailResult` and `now` on success; 400 with `error` and `now` on bad data or email failure\n */\n const requestMagicLinkHandler: PayloadHandler = async (req: PayloadRequest) => {\n const data = req?.json ? await req.json() : {}\n const { email, verifyData } = data // if by POST data\n\n if (!email) {\n return Response.json(\n {\n error: 'Email required',\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const user = userResults.docs[0] as TypedUser\n\n if (!user) {\n //\n // Create subscriber with status 'pending',\n // and an invisible unknowable password,\n //\n const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable\n const createResult = await req.payload.create({\n collection: subscribersCollectionSlug,\n data: {\n email,\n password: tokenHash2,\n status: 'pending',\n },\n draft: false,\n })\n if (!createResult) {\n return Response.json(\n {\n error: 'Error creating subscriber',\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n }\n\n // Update user with verificationToken\n // const token = crypto.randomBytes(32).toString('hex')\n // const tokenHash = crypto.createHash('sha256').update(token).digest('hex')\n // const expiresAt = new Date(Date.now() + 15 * 60 * 1000) // 15 mins\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000)\n await req.payload.update({\n collection: subscribersCollectionSlug,\n data: {\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt?.toISOString(),\n },\n where: {\n email: { equals: user.email },\n },\n })\n const { hashToken: unsubscribeHash } = getHmacHash(email)\n\n // Send email\n const magicLink = `${verifyURL?.href}${verifyURL?.search ? '&' : '?'}token=${encodeURIComponent(token)}&email=${encodeURIComponent(email)}&verifyData=${encodeURIComponent(verifyData)}`\n const unsubscribeLink = !unsubscribeURL\n ? undefined\n : `${unsubscribeURL?.href}${unsubscribeURL?.search ? '&' : '?'}email=${encodeURIComponent(email)}&hash=${encodeURIComponent(unsubscribeHash)}`\n const subject = data.subject || 'Your Magic Login Link'\n const message = `\n ${data.message || '<p>You requested a magic link to log in. Click the button below</p>'}\n <p><a href=\"${magicLink}\"><button><b>Login</b></button></a></p>\n ${unsubscribeLink ? `<p>Click here to <a href=\"${unsubscribeLink}\">unsubscribe</a></p>` : ``}\n `\n\n const emailResult = await req.payload.sendEmail({\n html: message,\n subject,\n to: user.email,\n })\n // req.payload.logger.info(`email result: ${JSON.stringify(emailResult)}`)\n // return data; // Return data to allow normal submission if needed\n if (!emailResult) {\n return Response.json(\n {\n error: 'Unknown email result',\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n req.payload.logger.info(`requestMagicLinkHandler email sent \\n ${magicLink}`)\n return Response.json({\n emailResult,\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse)\n }\n\n /** Endpoint config for requesting a magic link. Mount as POST /emailToken. */\n const requestMagicLinkEndpoint: Endpoint = {\n handler: requestMagicLinkHandler,\n method: 'post',\n path: '/emailToken',\n }\n\n return requestMagicLinkEndpoint\n}\n\nexport default createEndpointRequestMagicLink\n"],"names":["defaultCollectionSlug","getHmacHash","getTokenAndHash","createEndpointRequestMagicLink","subscribersCollectionSlug","unsubscribeURL","verifyURL","href","Error","requestMagicLinkHandler","req","data","json","email","verifyData","Response","error","now","Date","toISOString","status","userResults","payload","find","collection","where","equals","user","docs","tokenHash","tokenHash2","createResult","create","password","draft","expiresAt","token","update","verificationToken","verificationTokenExpires","hashToken","unsubscribeHash","magicLink","search","encodeURIComponent","unsubscribeLink","undefined","subject","message","emailResult","sendEmail","html","to","logger","info","requestMagicLinkEndpoint","handler","method","path"],"mappings":"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"}
|
|
@@ -16,10 +16,14 @@ export type SubscribeResponse = {
|
|
|
16
16
|
* opt-ins for already-verified subscribers.
|
|
17
17
|
*
|
|
18
18
|
* @param options - Config options for the endpoint
|
|
19
|
-
* @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
|
|
19
|
+
* @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)
|
|
20
|
+
* @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links
|
|
21
|
+
* @param options.verifyURL - (required) The URL to use for verify links
|
|
20
22
|
* @returns Payload Endpoint config for POST /subscribe
|
|
21
23
|
*/
|
|
22
|
-
declare function createEndpointSubscribe({ subscribersCollectionSlug, }: {
|
|
24
|
+
declare function createEndpointSubscribe({ subscribersCollectionSlug, unsubscribeURL, verifyURL, }: {
|
|
23
25
|
subscribersCollectionSlug: CollectionSlug;
|
|
26
|
+
unsubscribeURL?: URL;
|
|
27
|
+
verifyURL: URL;
|
|
24
28
|
}): Endpoint;
|
|
25
29
|
export default createEndpointSubscribe;
|
|
@@ -7,22 +7,21 @@ import { verifyOptIns } from '../helpers/verifyOptIns.js';
|
|
|
7
7
|
* opt-ins for already-verified subscribers.
|
|
8
8
|
*
|
|
9
9
|
* @param options - Config options for the endpoint
|
|
10
|
-
* @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
|
|
10
|
+
* @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)
|
|
11
|
+
* @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links
|
|
12
|
+
* @param options.verifyURL - (required) The URL to use for verify links
|
|
11
13
|
* @returns Payload Endpoint config for POST /subscribe
|
|
12
|
-
*/ function createEndpointSubscribe({ subscribersCollectionSlug = defaultCollectionSlug }) {
|
|
14
|
+
*/ function createEndpointSubscribe({ subscribersCollectionSlug = defaultCollectionSlug, unsubscribeURL, verifyURL }) {
|
|
13
15
|
/**
|
|
14
|
-
* Handler for POST /subscribe. Accepts email
|
|
16
|
+
* Handler for POST /subscribe. Accepts email and optIns. Creates pending
|
|
15
17
|
* subscribers and sends verify emails, or updates opt-ins for authenticated subscribers.
|
|
16
18
|
*
|
|
17
|
-
* @param req - Payload request
|
|
19
|
+
* @param req - Payload request. Expects body to be a json object { email, optIns, verifyData? }
|
|
18
20
|
* @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure
|
|
19
21
|
*/ const subscribeHandler = async (req)=>{
|
|
20
22
|
const data = req?.json ? await req.json() : {};
|
|
21
|
-
const { email, optIns,
|
|
23
|
+
const { email, optIns, verifyData } = data // if by POST data
|
|
22
24
|
;
|
|
23
|
-
// const { email } = req.routeParams // if by path
|
|
24
|
-
const verifyUrlObj = new URL(verifyUrl);
|
|
25
|
-
const unsubscribeUrlObj = unsubscribeUrl ? new URL(unsubscribeUrl) : undefined;
|
|
26
25
|
//
|
|
27
26
|
// HELPERS
|
|
28
27
|
// Some of these functions make use of the scope within handler,
|
|
@@ -57,9 +56,9 @@ import { verifyOptIns } from '../helpers/verifyOptIns.js';
|
|
|
57
56
|
});
|
|
58
57
|
return updateResults;
|
|
59
58
|
};
|
|
60
|
-
const sendVerifyEmail = async ({ email, linkText, message, subject, token, unsubscribeHash,
|
|
61
|
-
const magicLink = `${
|
|
62
|
-
const unsubscribeLink =
|
|
59
|
+
const sendVerifyEmail = async ({ email, linkText, message, subject, token, unsubscribeHash, unsubscribeURL, verifyData, verifyURL })=>{
|
|
60
|
+
const magicLink = `${verifyURL.href}${verifyURL?.search ? '&' : '?'}token=${encodeURIComponent(token)}&email=${encodeURIComponent(email)}${verifyData ? `&verifyData=${encodeURIComponent(verifyData)}` : ``}`;
|
|
61
|
+
const unsubscribeLink = unsubscribeURL ? `${unsubscribeURL.href}${unsubscribeURL.search ? '&' : '?'}email=${encodeURIComponent(email)}&hash=${encodeURIComponent(unsubscribeHash || '')}` : undefined;
|
|
63
62
|
const html = `
|
|
64
63
|
${message}<p><a href="${magicLink}">${linkText}</a></p>
|
|
65
64
|
${unsubscribeLink ? `<p>Click here to <a href="${unsubscribeLink}"><b>unsubscribe</b></a></p>` : ``}`;
|
|
@@ -168,8 +167,9 @@ ${unsubscribeLink ? `<p>Click here to <a href="${unsubscribeLink}"><b>unsubscrib
|
|
|
168
167
|
subject: data.subject || 'Please verify your subscription',
|
|
169
168
|
token,
|
|
170
169
|
unsubscribeHash,
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
unsubscribeURL,
|
|
171
|
+
verifyData,
|
|
172
|
+
verifyURL
|
|
173
173
|
});
|
|
174
174
|
if (!emailResult) {
|
|
175
175
|
req.payload.logger.error(JSON.stringify({
|
|
@@ -226,8 +226,9 @@ ${unsubscribeLink ? `<p>Click here to <a href="${unsubscribeLink}"><b>unsubscrib
|
|
|
226
226
|
subject: data.subject || 'Please verify your subscription',
|
|
227
227
|
token,
|
|
228
228
|
unsubscribeHash,
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
unsubscribeURL,
|
|
230
|
+
verifyData,
|
|
231
|
+
verifyURL
|
|
231
232
|
});
|
|
232
233
|
if (!emailResult) {
|
|
233
234
|
req.payload.logger.error(JSON.stringify({
|
|
@@ -281,8 +282,9 @@ ${unsubscribeLink ? `<p>Click here to <a href="${unsubscribeLink}"><b>unsubscrib
|
|
|
281
282
|
subject: data.subject || 'Please verify your subscription',
|
|
282
283
|
token,
|
|
283
284
|
unsubscribeHash,
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
unsubscribeURL,
|
|
286
|
+
verifyData,
|
|
287
|
+
verifyURL
|
|
286
288
|
});
|
|
287
289
|
if (!emailResult) {
|
|
288
290
|
req.payload.logger.error(JSON.stringify({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/subscribe.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\nimport type { Subscriber } from 'src/copied/payload-types.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash, getTokenAndHash } from '../helpers/token.js'\nimport { verifyOptIns } from '../helpers/verifyOptIns.js'\n\nexport type SubscribeResponse =\n // When subscriber optIns are updated...\n | {\n email: string\n now: string\n optIns: string[]\n }\n // When a verify link is emailed...\n | {\n emailResult: any\n now: string\n }\n // When any error occurs...\n | {\n error: string\n now: string\n }\n\n/**\n * Factory that creates the subscribe endpoint config and handler.\n * Handles new subscriptions (pending + verify email), magic-link resends, and updating\n * opt-ins for already-verified subscribers.\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)\n * @returns Payload Endpoint config for POST /subscribe\n */\nfunction createEndpointSubscribe({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n /**\n * Handler for POST /subscribe. Accepts email, optIns, and verifyUrl. Creates pending\n * subscribers and sends verify emails, or updates opt-ins for authenticated subscribers.\n *\n * @param req - Payload request; body: `email`, `optIns` (channel IDs), `verifyUrl`\n * @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure\n */\n const subscribeHandler: PayloadHandler = async (req) => {\n const data = req?.json ? await req.json() : {}\n const {\n email,\n optIns,\n unsubscribeUrl,\n verifyUrl,\n }: { email: string; optIns: string[]; unsubscribeUrl?: string; verifyUrl: string } = data // if by POST data\n // const { email } = req.routeParams // if by path\n const verifyUrlObj: URL = new URL(verifyUrl)\n const unsubscribeUrlObj: undefined | URL = unsubscribeUrl ? new URL(unsubscribeUrl) : undefined\n\n //\n // HELPERS\n // Some of these functions make use of the scope within handler,\n // and would have to be refactored if moved out.\n //\n const createSubscriber = async ({\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires,\n }: {\n email: string\n optIns?: string[]\n password?: string\n status?: 'pending' | 'subscribed' | 'unsubscribed'\n verificationToken?: string\n verificationTokenExpires?: Date\n }) => {\n await req.payload.create({\n collection: subscribersCollectionSlug,\n data: {\n email,\n optIns,\n password,\n status: status || 'pending',\n verificationToken,\n verificationTokenExpires: verificationTokenExpires?.toISOString(),\n },\n draft: false,\n })\n }\n const updateSubscriber = async ({\n id,\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires,\n }: {\n id: string\n optIns?: string[]\n password?: string\n status?: 'pending' | 'subscribed' | 'unsubscribed'\n verificationToken?: string\n verificationTokenExpires?: Date | null\n }) => {\n const updateResults = await req.payload.update({\n id,\n collection: subscribersCollectionSlug,\n data: {\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires: verificationTokenExpires?.toISOString() || null,\n },\n depth: 0,\n })\n return updateResults\n }\n const sendVerifyEmail = async ({\n email,\n linkText,\n message,\n subject,\n token,\n unsubscribeHash,\n unsubscribeUrl,\n verifyUrl,\n }: {\n email: string\n linkText: string\n message: string\n subject: string\n token: string\n unsubscribeHash?: string\n unsubscribeUrl?: URL\n verifyUrl: URL\n }) => {\n const magicLink = `${verifyUrl.href}${verifyUrl?.search ? '&' : '?'}token=${token}&email=${email}`\n const unsubscribeLink = unsubscribeUrl\n ? `${unsubscribeUrl.href}${unsubscribeUrl.search ? '&' : '?'}email=${email}&hash=${unsubscribeHash}`\n : undefined\n const html = `\n${message}<p><a href=\"${magicLink}\">${linkText}</a></p>\n${\n unsubscribeLink ? `<p>Click here to <a href=\"${unsubscribeLink}\"><b>unsubscribe</b></a></p>` : ``\n}`\n\n const emailResult = await req.payload.sendEmail({\n html,\n subject,\n to: email,\n })\n req.payload.logger.info(`subscribe email sent \\n ${magicLink}`)\n return emailResult\n }\n\n //\n // VALIDATE INPUT\n //\n // Require email\n if (!email) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Bad data', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Validate OptInChannels\n const { invalidOptInsInput, verifiedOptInIDs } = await verifyOptIns(req.payload, optIns)\n\n if (invalidOptInsInput) {\n req.payload.logger.error(\n JSON.stringify(\n {\n error: 'Invalid input: ' + JSON.stringify(optIns),\n now: new Date().toISOString(),\n } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n {\n error: 'Invalid input: ' + JSON.stringify(optIns),\n now: new Date().toISOString(),\n } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Verify subscriber exists\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const subscriber = userResults.docs[0] as Subscriber\n\n //\n // Now we have a subscriber and validatedOptIns\n // Handle scenarios\n\n //\n // Create the hash for an unsubscribe link\n const { hashToken: unsubscribeHash } = getHmacHash(email)\n\n //\n // ********************************************************\n //\n if (req.user && req.user.email != email) {\n //\n // Error: Auth-ed user doesn't match subscriber email\n //\n req.payload.logger.error(\n JSON.stringify(\n {\n error: 'Unauthorized: ' + email,\n now: new Date().toISOString(),\n } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n {\n error: 'Unauthorized: ' + email,\n now: new Date().toISOString(),\n } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // ********************************************************\n //\n if (!subscriber) {\n //\n // Create subscriber with status 'pending',\n // and an invisible unknowable password,\n // and send a verify email\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable\n await createSubscriber({\n email,\n optIns,\n password: tokenHash2,\n status: 'pending',\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n\n //\n // Send email\n const emailResult = await sendVerifyEmail({\n email,\n linkText: '<b>Verify</b>',\n message: data.message || `<p>Click here to verify your subscription:</p>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeUrl: unsubscribeUrlObj,\n verifyUrl: verifyUrlObj,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n //\n }\n //\n // ********************************************************\n //\n if (!req.user && subscriber) {\n //\n // Send magic link to log the user in\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n // Update subscriber with token for pending email\n const updateResults = await updateSubscriber({\n id: subscriber.id,\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n if (!updateResults) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Send email\n const emailResult = await sendVerifyEmail({\n email,\n linkText: 'Verify',\n message: data.message || `<h1>Click here to verify your subscription:</h1>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeUrl: unsubscribeUrlObj,\n verifyUrl: verifyUrlObj,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n }\n //\n // ********************************************************\n //\n if (req.user && subscriber && subscriber.status == 'pending') {\n //\n // Send magic link to verify the email and log the user in\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n // Create subscriber with token for pending email\n const updateResults = await updateSubscriber({\n id: subscriber.id,\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n if (!updateResults) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n const emailResult = await sendVerifyEmail({\n email,\n linkText: 'Verify',\n message: data.message || `<h1>Click here to verify your email:</h1>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeUrl: unsubscribeUrlObj,\n verifyUrl: verifyUrlObj,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n }\n\n //\n // ********************************************************\n //\n if (req.user && subscriber && subscriber.status != 'pending') {\n //\n // Update subscriber with status 'subscribed',\n // an invisible unknowable password,\n // and if any optIns input exists, set subscriber optIns\n // to EXACTLY verifiedOptInIDs (potentially unsubscribing from any not in verifiedOptInIDs)\n //\n const { tokenHash } = getTokenAndHash() // Use for magic link\n // Update subscriber with optIns\n const updateResults = (await updateSubscriber({\n id: subscriber.id,\n optIns: verifiedOptInIDs,\n password: tokenHash,\n status: 'subscribed',\n verificationToken: '',\n verificationTokenExpires: null,\n })) as Subscriber\n\n // Return results, including the verified optIns\n return Response.json({\n email: updateResults.email,\n now: new Date().toISOString(),\n optIns: updateResults.optIns,\n } as SubscribeResponse)\n }\n //\n // Uncaught case\n //\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n /** Endpoint config for subscription and opt-in updates. Mount as POST /subscribe. */\n const subscribeEndpoint: Endpoint = {\n handler: subscribeHandler,\n method: 'post',\n path: '/subscribe',\n }\n\n return subscribeEndpoint\n}\n\nexport default createEndpointSubscribe\n"],"names":["defaultCollectionSlug","getHmacHash","getTokenAndHash","verifyOptIns","createEndpointSubscribe","subscribersCollectionSlug","subscribeHandler","req","data","json","email","optIns","unsubscribeUrl","verifyUrl","verifyUrlObj","URL","unsubscribeUrlObj","undefined","createSubscriber","password","status","verificationToken","verificationTokenExpires","payload","create","collection","toISOString","draft","updateSubscriber","id","updateResults","update","depth","sendVerifyEmail","linkText","message","subject","token","unsubscribeHash","magicLink","href","search","unsubscribeLink","html","emailResult","sendEmail","to","logger","info","error","JSON","stringify","now","Date","Response","invalidOptInsInput","verifiedOptInIDs","userResults","find","where","equals","subscriber","docs","hashToken","user","expiresAt","tokenHash","tokenHash2","subscribeEndpoint","handler","method","path"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/subscribe.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\nimport type { Subscriber } from 'src/copied/payload-types.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash, getTokenAndHash } from '../helpers/token.js'\nimport { verifyOptIns } from '../helpers/verifyOptIns.js'\n\nexport type SubscribeResponse =\n // When subscriber optIns are updated...\n | {\n email: string\n now: string\n optIns: string[]\n }\n // When a verify link is emailed...\n | {\n emailResult: any\n now: string\n }\n // When any error occurs...\n | {\n error: string\n now: string\n }\n\n/**\n * Factory that creates the subscribe endpoint config and handler.\n * Handles new subscriptions (pending + verify email), magic-link resends, and updating\n * opt-ins for already-verified subscribers.\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)\n * @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links\n * @param options.verifyURL - (required) The URL to use for verify links\n * @returns Payload Endpoint config for POST /subscribe\n */\nfunction createEndpointSubscribe({\n subscribersCollectionSlug = defaultCollectionSlug,\n unsubscribeURL,\n verifyURL,\n}: {\n subscribersCollectionSlug: CollectionSlug\n unsubscribeURL?: URL\n verifyURL: URL\n}): Endpoint {\n /**\n * Handler for POST /subscribe. Accepts email and optIns. Creates pending\n * subscribers and sends verify emails, or updates opt-ins for authenticated subscribers.\n *\n * @param req - Payload request. Expects body to be a json object { email, optIns, verifyData? }\n * @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure\n */\n const subscribeHandler: PayloadHandler = async (req) => {\n const data = req?.json ? await req.json() : {}\n const {\n email,\n optIns,\n verifyData,\n }: {\n email: string\n optIns: string[]\n verifyData?: string\n } = data // if by POST data\n\n //\n // HELPERS\n // Some of these functions make use of the scope within handler,\n // and would have to be refactored if moved out.\n //\n const createSubscriber = async ({\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires,\n }: {\n email: string\n optIns?: string[]\n password?: string\n status?: 'pending' | 'subscribed' | 'unsubscribed'\n verificationToken?: string\n verificationTokenExpires?: Date\n }) => {\n await req.payload.create({\n collection: subscribersCollectionSlug,\n data: {\n email,\n optIns,\n password,\n status: status || 'pending',\n verificationToken,\n verificationTokenExpires: verificationTokenExpires?.toISOString(),\n },\n draft: false,\n })\n }\n const updateSubscriber = async ({\n id,\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires,\n }: {\n id: string\n optIns?: string[]\n password?: string\n status?: 'pending' | 'subscribed' | 'unsubscribed'\n verificationToken?: string\n verificationTokenExpires?: Date | null\n }) => {\n const updateResults = await req.payload.update({\n id,\n collection: subscribersCollectionSlug,\n data: {\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires: verificationTokenExpires?.toISOString() || null,\n },\n depth: 0,\n })\n return updateResults\n }\n const sendVerifyEmail = async ({\n email,\n linkText,\n message,\n subject,\n token,\n unsubscribeHash,\n unsubscribeURL,\n verifyData,\n verifyURL,\n }: {\n email: string\n linkText: string\n message: string\n subject: string\n token: string\n unsubscribeHash?: string\n unsubscribeURL?: URL\n verifyData?: string\n verifyURL: URL\n }) => {\n const magicLink = `${verifyURL.href}${verifyURL?.search ? '&' : '?'}token=${encodeURIComponent(token)}&email=${encodeURIComponent(email)}${verifyData ? `&verifyData=${encodeURIComponent(verifyData)}` : ``}`\n const unsubscribeLink = unsubscribeURL\n ? `${unsubscribeURL.href}${unsubscribeURL.search ? '&' : '?'}email=${encodeURIComponent(email)}&hash=${encodeURIComponent(unsubscribeHash || '')}`\n : undefined\n const html = `\n${message}<p><a href=\"${magicLink}\">${linkText}</a></p>\n${\n unsubscribeLink ? `<p>Click here to <a href=\"${unsubscribeLink}\"><b>unsubscribe</b></a></p>` : ``\n}`\n\n const emailResult = await req.payload.sendEmail({\n html,\n subject,\n to: email,\n })\n req.payload.logger.info(`subscribe email sent \\n ${magicLink}`)\n return emailResult\n }\n\n //\n // VALIDATE INPUT\n //\n // Require email\n if (!email) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Bad data', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Validate OptInChannels\n const { invalidOptInsInput, verifiedOptInIDs } = await verifyOptIns(req.payload, optIns)\n\n if (invalidOptInsInput) {\n req.payload.logger.error(\n JSON.stringify(\n {\n error: 'Invalid input: ' + JSON.stringify(optIns),\n now: new Date().toISOString(),\n } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n {\n error: 'Invalid input: ' + JSON.stringify(optIns),\n now: new Date().toISOString(),\n } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Verify subscriber exists\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const subscriber = userResults.docs[0] as Subscriber\n\n //\n // Now we have a subscriber and validatedOptIns\n // Handle scenarios\n\n //\n // Create the hash for an unsubscribe link\n const { hashToken: unsubscribeHash } = getHmacHash(email)\n\n //\n // ********************************************************\n //\n if (req.user && req.user.email != email) {\n //\n // Error: Auth-ed user doesn't match subscriber email\n //\n req.payload.logger.error(\n JSON.stringify(\n {\n error: 'Unauthorized: ' + email,\n now: new Date().toISOString(),\n } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n {\n error: 'Unauthorized: ' + email,\n now: new Date().toISOString(),\n } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // ********************************************************\n //\n if (!subscriber) {\n //\n // Create subscriber with status 'pending',\n // and an invisible unknowable password,\n // and send a verify email\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable\n await createSubscriber({\n email,\n optIns,\n password: tokenHash2,\n status: 'pending',\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n\n //\n // Send email\n const emailResult = await sendVerifyEmail({\n email,\n linkText: '<b>Verify</b>',\n message: data.message || `<p>Click here to verify your subscription:</p>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeURL,\n verifyData,\n verifyURL,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n //\n }\n //\n // ********************************************************\n //\n if (!req.user && subscriber) {\n //\n // Send magic link to log the user in\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n // Update subscriber with token for pending email\n const updateResults = await updateSubscriber({\n id: subscriber.id,\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n if (!updateResults) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Send email\n const emailResult = await sendVerifyEmail({\n email,\n linkText: 'Verify',\n message: data.message || `<h1>Click here to verify your subscription:</h1>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeURL,\n verifyData,\n verifyURL,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n }\n //\n // ********************************************************\n //\n if (req.user && subscriber && subscriber.status == 'pending') {\n //\n // Send magic link to verify the email and log the user in\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n // Create subscriber with token for pending email\n const updateResults = await updateSubscriber({\n id: subscriber.id,\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n if (!updateResults) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n const emailResult = await sendVerifyEmail({\n email,\n linkText: 'Verify',\n message: data.message || `<h1>Click here to verify your email:</h1>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeURL,\n verifyData,\n verifyURL,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n }\n\n //\n // ********************************************************\n //\n if (req.user && subscriber && subscriber.status != 'pending') {\n //\n // Update subscriber with status 'subscribed',\n // an invisible unknowable password,\n // and if any optIns input exists, set subscriber optIns\n // to EXACTLY verifiedOptInIDs (potentially unsubscribing from any not in verifiedOptInIDs)\n //\n const { tokenHash } = getTokenAndHash() // Use for magic link\n // Update subscriber with optIns\n const updateResults = (await updateSubscriber({\n id: subscriber.id,\n optIns: verifiedOptInIDs,\n password: tokenHash,\n status: 'subscribed',\n verificationToken: '',\n verificationTokenExpires: null,\n })) as Subscriber\n\n // Return results, including the verified optIns\n return Response.json({\n email: updateResults.email,\n now: new Date().toISOString(),\n optIns: updateResults.optIns,\n } as SubscribeResponse)\n }\n //\n // Uncaught case\n //\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n /** Endpoint config for subscription and opt-in updates. Mount as POST /subscribe. */\n const subscribeEndpoint: Endpoint = {\n handler: subscribeHandler,\n method: 'post',\n path: '/subscribe',\n }\n\n return subscribeEndpoint\n}\n\nexport default createEndpointSubscribe\n"],"names":["defaultCollectionSlug","getHmacHash","getTokenAndHash","verifyOptIns","createEndpointSubscribe","subscribersCollectionSlug","unsubscribeURL","verifyURL","subscribeHandler","req","data","json","email","optIns","verifyData","createSubscriber","password","status","verificationToken","verificationTokenExpires","payload","create","collection","toISOString","draft","updateSubscriber","id","updateResults","update","depth","sendVerifyEmail","linkText","message","subject","token","unsubscribeHash","magicLink","href","search","encodeURIComponent","unsubscribeLink","undefined","html","emailResult","sendEmail","to","logger","info","error","JSON","stringify","now","Date","Response","invalidOptInsInput","verifiedOptInIDs","userResults","find","where","equals","subscriber","docs","hashToken","user","expiresAt","tokenHash","tokenHash2","subscribeEndpoint","handler","method","path"],"mappings":"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"}
|
|
@@ -10,10 +10,10 @@ import { getHmacHash } from '../helpers/token.js';
|
|
|
10
10
|
* @returns Payload Endpoint config for POST /unsubscribe
|
|
11
11
|
*/ function createEndpointUnsubscribe({ subscribersCollectionSlug = defaultCollectionSlug }) {
|
|
12
12
|
/**
|
|
13
|
-
* Handler for POST /unsubscribe. Accepts email
|
|
14
|
-
*
|
|
13
|
+
* Handler for POST /unsubscribe. Accepts email and unsubscribeToken. Updates subscriber's status
|
|
14
|
+
* to 'unsubscribed'.
|
|
15
15
|
*
|
|
16
|
-
* @param req - Payload request
|
|
16
|
+
* @param req - Payload request. Expects body to be a json object { email, unsubscribeToken }
|
|
17
17
|
* @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure
|
|
18
18
|
*/ const unsubscribeHandler = async (req)=>{
|
|
19
19
|
const data = req?.json ? await req.json() : {};
|
|
@@ -23,25 +23,27 @@ import { getHmacHash } from '../helpers/token.js';
|
|
|
23
23
|
//
|
|
24
24
|
// VALIDATE INPUT
|
|
25
25
|
//
|
|
26
|
-
// Require
|
|
27
|
-
if (!unsubscribeToken) {
|
|
26
|
+
// Require token and email OR authed user
|
|
27
|
+
if (!unsubscribeToken && (!req.user || req.user?.email != email)) {
|
|
28
28
|
const result = {
|
|
29
29
|
error: 'Bad data',
|
|
30
30
|
now: new Date().toISOString()
|
|
31
31
|
};
|
|
32
|
-
req.payload.logger.error(`unsubscribe: No unsubscribeToken — ${JSON.stringify(result, undefined, 2)}`);
|
|
32
|
+
req.payload.logger.error(`unsubscribe: No unsubscribeToken or authenticated user - ${req.user?.email} — ${JSON.stringify(result, undefined, 2)}`);
|
|
33
33
|
return Response.json(result);
|
|
34
34
|
}
|
|
35
35
|
//
|
|
36
36
|
// Verify unsubscribeToken
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
if (unsubscribeToken) {
|
|
38
|
+
const { hashToken: verifyUnsubscribeToken } = getHmacHash(email);
|
|
39
|
+
if (unsubscribeToken != verifyUnsubscribeToken) {
|
|
40
|
+
const result = {
|
|
41
|
+
error: 'Bad data',
|
|
42
|
+
now: new Date().toISOString()
|
|
43
|
+
};
|
|
44
|
+
req.payload.logger.error(`unsubscribe: unsubscribeToken not verified — ${JSON.stringify(result, undefined, 2)}`);
|
|
45
|
+
return Response.json(result);
|
|
46
|
+
}
|
|
45
47
|
}
|
|
46
48
|
//
|
|
47
49
|
// Require subscriber exists
|
|
@@ -103,7 +105,7 @@ import { getHmacHash } from '../helpers/token.js';
|
|
|
103
105
|
message: 'Unsubscribed',
|
|
104
106
|
now: new Date().toISOString()
|
|
105
107
|
};
|
|
106
|
-
req.payload.logger.
|
|
108
|
+
req.payload.logger.info(`unsubscribe: successful — ${JSON.stringify(result, undefined, 2)}`);
|
|
107
109
|
return Response.json(result);
|
|
108
110
|
};
|
|
109
111
|
/** Endpoint config for subscription and opt-in updates. Mount as POST /subscribe. */ const unsubscribeEndpoint = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/unsubscribe.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\nimport type { Subscriber } from 'src/copied/payload-types.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash } from '../helpers/token.js'\n\nexport type UnsubscribeResponse =\n // When unsubscriber status is set to 'unsubscribed'...\n | {\n error: string\n now: string\n }\n // When any error occurs...\n | {\n message: string\n now: string\n }\n\n/**\n * Factory that creates the unsubscribe endpoint config and handler.\n * Handles completely unsubscribing a subscriber by marking their\n * status as 'unsubscribed'.\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)\n * @returns Payload Endpoint config for POST /unsubscribe\n */\nfunction createEndpointUnsubscribe({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n /**\n * Handler for POST /unsubscribe. Accepts email
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/unsubscribe.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\nimport type { Subscriber } from 'src/copied/payload-types.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash } from '../helpers/token.js'\n\nexport type UnsubscribeResponse =\n // When unsubscriber status is set to 'unsubscribed'...\n | {\n error: string\n now: string\n }\n // When any error occurs...\n | {\n message: string\n now: string\n }\n\n/**\n * Factory that creates the unsubscribe endpoint config and handler.\n * Handles completely unsubscribing a subscriber by marking their\n * status as 'unsubscribed'.\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)\n * @returns Payload Endpoint config for POST /unsubscribe\n */\nfunction createEndpointUnsubscribe({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n /**\n * Handler for POST /unsubscribe. Accepts email and unsubscribeToken. Updates subscriber's status\n * to 'unsubscribed'.\n *\n * @param req - Payload request. Expects body to be a json object { email, unsubscribeToken }\n * @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure\n */\n const unsubscribeHandler: PayloadHandler = async (req) => {\n const data = req?.json ? await req.json() : {}\n const { email, unsubscribeToken }: { email: string; unsubscribeToken: string } = await data // if by POST data\n // const { email } = req.routeParams // if by path\n\n //\n // VALIDATE INPUT\n //\n // Require token and email OR authed user\n if (!unsubscribeToken && (!req.user || req.user?.email != email)) {\n const result = { error: 'Bad data', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: No unsubscribeToken or authenticated user - ${req.user?.email} — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n //\n // Verify unsubscribeToken\n if (unsubscribeToken) {\n const { hashToken: verifyUnsubscribeToken } = getHmacHash(email)\n if (unsubscribeToken != verifyUnsubscribeToken) {\n const result = { error: 'Bad data', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: unsubscribeToken not verified — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n }\n\n //\n // Require subscriber exists\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const subscriber = userResults.docs[0] as Subscriber\n\n if (!subscriber) {\n const result = { error: 'Bad data', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: No subscriber — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n //\n // Require authed user to match incoming email\n if (req.user && req.user.email != subscriber.email) {\n //\n // Error: Auth-ed user doesn't match subscriber email\n //\n const result = {\n error: 'Unauthorized: ' + subscriber.email,\n now: new Date().toISOString(),\n } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: Unauthorized — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n //\n // Now we have a validated subscriber and unsubscribeToken\n // Mark as unsubscribed\n\n const updateResults = await req.payload.update({\n id: subscriber.id,\n collection: subscribersCollectionSlug,\n data: {\n status: 'unsubscribed',\n },\n depth: 0,\n })\n if (!updateResults) {\n const result = {\n error: 'Unable to unsubscribe. Please try again.',\n now: new Date().toISOString(),\n } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: Unknown updateResults — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result, { status: 400 })\n }\n\n //\n // Success\n //\n const result = { message: 'Unsubscribed', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.info(`unsubscribe: successful — ${JSON.stringify(result, undefined, 2)}`)\n return Response.json(result)\n }\n\n /** Endpoint config for subscription and opt-in updates. Mount as POST /subscribe. */\n const unsubscribeEndpoint: Endpoint = {\n handler: unsubscribeHandler,\n method: 'post',\n path: '/unsubscribe',\n }\n\n return unsubscribeEndpoint\n}\n\nexport default createEndpointUnsubscribe\n"],"names":["defaultCollectionSlug","getHmacHash","createEndpointUnsubscribe","subscribersCollectionSlug","unsubscribeHandler","req","data","json","email","unsubscribeToken","user","result","error","now","Date","toISOString","payload","logger","JSON","stringify","undefined","Response","hashToken","verifyUnsubscribeToken","userResults","find","collection","where","equals","subscriber","docs","updateResults","update","id","status","depth","message","info","unsubscribeEndpoint","handler","method","path"],"mappings":"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"}
|
package/dist/exports/ui.d.ts
CHANGED
|
@@ -10,4 +10,8 @@ export type { VerifyMagicLinkResponse } from '../components/app/VerifyMagicLink.
|
|
|
10
10
|
export { VerifyMagicLink } from '../components/app/VerifyMagicLink.js';
|
|
11
11
|
export type { SubscriberContextType } from '../contexts/SubscriberProvider.js';
|
|
12
12
|
export { SubscriberProvider, useSubscriber } from '../contexts/SubscriberProvider.js';
|
|
13
|
+
export { useRequestMagicLink } from '../hooks/useRequestMagicLink.js';
|
|
14
|
+
export { useSubscribe } from '../hooks/useSubscribe.js';
|
|
15
|
+
export { useUnsubscribe } from '../hooks/useUnsubscribe.js';
|
|
16
|
+
export { useVerifyMagicLink } from '../hooks/useVerifyMagicLink.js';
|
|
13
17
|
export { getServerUrl } from '../server-functions/serverUrl.js';
|
package/dist/exports/ui.js
CHANGED
|
@@ -5,6 +5,10 @@ export { SubscriberMenu } from '../components/app/SubscriberMenu.js';
|
|
|
5
5
|
export { Unsubscribe } from '../components/app/Unsubscribe.js';
|
|
6
6
|
export { VerifyMagicLink } from '../components/app/VerifyMagicLink.js';
|
|
7
7
|
export { SubscriberProvider, useSubscriber } from '../contexts/SubscriberProvider.js';
|
|
8
|
+
export { useRequestMagicLink } from '../hooks/useRequestMagicLink.js';
|
|
9
|
+
export { useSubscribe } from '../hooks/useSubscribe.js';
|
|
10
|
+
export { useUnsubscribe } from '../hooks/useUnsubscribe.js';
|
|
11
|
+
export { useVerifyMagicLink } from '../hooks/useVerifyMagicLink.js';
|
|
8
12
|
export { getServerUrl } from '../server-functions/serverUrl.js';
|
|
9
13
|
|
|
10
14
|
//# sourceMappingURL=ui.js.map
|
package/dist/exports/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exports/ui.ts"],"sourcesContent":["export type { RequestMagicLinkResponse } from '../components/app/RequestMagicLink.js'\nexport { RequestMagicLink } from '../components/app/RequestMagicLink.js'\n\nexport { RequestOrSubscribe } from '../components/app/RequestOrSubscribe.js'\n\nexport type { SubscribeResponse } from '../components/app/Subscribe.js'\nexport { Subscribe } from '../components/app/Subscribe.js'\n\nexport { SubscriberMenu } from '../components/app/SubscriberMenu.js'\n\nexport type { UnsubscribeResponse } from '../components/app/Unsubscribe.js'\nexport { Unsubscribe } from '../components/app/Unsubscribe.js'\n\nexport type { VerifyMagicLinkResponse } from '../components/app/VerifyMagicLink.js'\nexport { VerifyMagicLink } from '../components/app/VerifyMagicLink.js'\n\nexport type { SubscriberContextType } from '../contexts/SubscriberProvider.js'\nexport { SubscriberProvider, useSubscriber } from '../contexts/SubscriberProvider.js'\n\nexport { getServerUrl } from '../server-functions/serverUrl.js'\n"],"names":["RequestMagicLink","RequestOrSubscribe","Subscribe","SubscriberMenu","Unsubscribe","VerifyMagicLink","SubscriberProvider","useSubscriber","getServerUrl"],"mappings":"AACA,SAASA,gBAAgB,QAAQ,wCAAuC;AAExE,SAASC,kBAAkB,QAAQ,0CAAyC;AAG5E,SAASC,SAAS,QAAQ,iCAAgC;AAE1D,SAASC,cAAc,QAAQ,sCAAqC;AAGpE,SAASC,WAAW,QAAQ,mCAAkC;AAG9D,SAASC,eAAe,QAAQ,uCAAsC;AAGtE,SAASC,kBAAkB,EAAEC,aAAa,QAAQ,oCAAmC;AAErF,SAASC,YAAY,QAAQ,mCAAkC"}
|
|
1
|
+
{"version":3,"sources":["../../src/exports/ui.ts"],"sourcesContent":["export type { RequestMagicLinkResponse } from '../components/app/RequestMagicLink.js'\nexport { RequestMagicLink } from '../components/app/RequestMagicLink.js'\n\nexport { RequestOrSubscribe } from '../components/app/RequestOrSubscribe.js'\n\nexport type { SubscribeResponse } from '../components/app/Subscribe.js'\nexport { Subscribe } from '../components/app/Subscribe.js'\n\nexport { SubscriberMenu } from '../components/app/SubscriberMenu.js'\n\nexport type { UnsubscribeResponse } from '../components/app/Unsubscribe.js'\nexport { Unsubscribe } from '../components/app/Unsubscribe.js'\n\nexport type { VerifyMagicLinkResponse } from '../components/app/VerifyMagicLink.js'\nexport { VerifyMagicLink } from '../components/app/VerifyMagicLink.js'\n\nexport type { SubscriberContextType } from '../contexts/SubscriberProvider.js'\nexport { SubscriberProvider, useSubscriber } from '../contexts/SubscriberProvider.js'\n\nexport { useRequestMagicLink } from '../hooks/useRequestMagicLink.js'\nexport { useSubscribe } from '../hooks/useSubscribe.js'\nexport { useUnsubscribe } from '../hooks/useUnsubscribe.js'\nexport { useVerifyMagicLink } from '../hooks/useVerifyMagicLink.js'\n\nexport { getServerUrl } from '../server-functions/serverUrl.js'\n"],"names":["RequestMagicLink","RequestOrSubscribe","Subscribe","SubscriberMenu","Unsubscribe","VerifyMagicLink","SubscriberProvider","useSubscriber","useRequestMagicLink","useSubscribe","useUnsubscribe","useVerifyMagicLink","getServerUrl"],"mappings":"AACA,SAASA,gBAAgB,QAAQ,wCAAuC;AAExE,SAASC,kBAAkB,QAAQ,0CAAyC;AAG5E,SAASC,SAAS,QAAQ,iCAAgC;AAE1D,SAASC,cAAc,QAAQ,sCAAqC;AAGpE,SAASC,WAAW,QAAQ,mCAAkC;AAG9D,SAASC,eAAe,QAAQ,uCAAsC;AAGtE,SAASC,kBAAkB,EAAEC,aAAa,QAAQ,oCAAmC;AAErF,SAASC,mBAAmB,QAAQ,kCAAiC;AACrE,SAASC,YAAY,QAAQ,2BAA0B;AACvD,SAASC,cAAc,QAAQ,6BAA4B;AAC3D,SAASC,kBAAkB,QAAQ,iCAAgC;AAEnE,SAASC,YAAY,QAAQ,mCAAkC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isAbsoluteURL(url: string): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/utilities.ts"],"sourcesContent":["export function isAbsoluteURL(url: string): boolean {\n // Checks if it starts with \"//\" or contains \"://\" after the first character\n return url.indexOf('://') > 0 || url.indexOf('//') === 0\n}\n"],"names":["isAbsoluteURL","url","indexOf"],"mappings":"AAAA,OAAO,SAASA,cAAcC,GAAW;IACvC,4EAA4E;IAC5E,OAAOA,IAAIC,OAAO,CAAC,SAAS,KAAKD,IAAIC,OAAO,CAAC,UAAU;AACzD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { RequestMagicLinkResponse } from '../endpoints/requestMagicLink.js';
|
|
2
|
+
export { RequestMagicLinkResponse };
|
|
3
|
+
/**
|
|
4
|
+
* Options for the useRequestMagicLink hook.
|
|
5
|
+
*
|
|
6
|
+
* @property handleMagicLinkRequested - Callback when a magic link is successfully requested
|
|
7
|
+
* @property verifyData - Optional data sent with the request (e.g. for verification redirect)
|
|
8
|
+
*/
|
|
9
|
+
export interface IUseRequestMagicLinkOptions {
|
|
10
|
+
handleMagicLinkRequested?: (result: RequestMagicLinkResponse) => void;
|
|
11
|
+
verifyData?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Return value of useRequestMagicLink.
|
|
15
|
+
*
|
|
16
|
+
* @property result - Success or error message from the last request
|
|
17
|
+
* @property sendMagicLink - Sends a magic link email for the given address
|
|
18
|
+
* @property status - Current status: 'default' | 'sending' | 'sent' | 'error'
|
|
19
|
+
*/
|
|
20
|
+
export interface IUseRequestMagicLink {
|
|
21
|
+
result?: string;
|
|
22
|
+
sendMagicLink: (email: string) => Promise<void>;
|
|
23
|
+
status?: RequestMagicLinkStatusValue;
|
|
24
|
+
}
|
|
25
|
+
export type RequestMagicLinkStatusValue = 'default' | 'error' | 'sending' | 'sent';
|
|
26
|
+
/**
|
|
27
|
+
* Hook to request a magic-login link by email. Calls POST /api/emailToken and exposes
|
|
28
|
+
* sendMagicLink, plus result message and status for UI.
|
|
29
|
+
*
|
|
30
|
+
* @param options - Hook options (see IUseRequestMagicLinkOptions)
|
|
31
|
+
* @param options.handleMagicLinkRequested - Callback when a magic link is successfully requested
|
|
32
|
+
* @param options.verifyData - Optional data sent with the request (e.g. for verification redirect)
|
|
33
|
+
* @returns sendMagicLink function, result message, and status (see IUseRequestMagicLink)
|
|
34
|
+
*/
|
|
35
|
+
export declare const useRequestMagicLink: ({ handleMagicLinkRequested, verifyData, }: IUseRequestMagicLinkOptions) => IUseRequestMagicLink;
|