payload-subscribers-plugin 0.0.15 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,7 +49,7 @@ export default buildConfig({
49
49
  subscribersCollectionSlug?: CollectionSlug
50
50
 
51
51
  // Provide a custom expiration for magic link tokens. The default is 30 minutes.
52
- tokenExpiration: 60 * 60,
52
+ tokenExpiration: 30 * 60,
53
53
 
54
54
  // Provide your unsubscribe route. This route should include the Unsubscribe component, or implement your own with the useUnsubscribe hook. If not provided, your payload config must have serverURL defined, and the default will be serverURL+'/unsubscribe'
55
55
  unsubscribeURL?: string,
@@ -18,36 +18,46 @@ const SubscriberContext = /*#__PURE__*/ createContext(undefined);
18
18
  // Keep track of if the selection content is loaded yet
19
19
  const [isLoaded, setIsLoaded] = useState(false);
20
20
  const [permissions, setPermissions] = useState();
21
- const initSubscriber = async ()=>{
22
- console.log('initSubscriber');
21
+ const initSubscriber = useCallback(async ()=>{
22
+ console.log('initSubscriber', serverURL);
23
23
  setIsLoaded(false);
24
- try {
25
- const authResponse = await fetch('/api/subscriberAuth', {
26
- // body: JSON.stringify({}),
27
- method: 'POST'
28
- });
29
- if (authResponse.ok) {
30
- // Call the server function to get the user data
31
- const authResponseJson = await authResponse.json();
32
- // console.log('authResponseJson', JSON.stringify(authResponseJson, undefined, 2))
33
- const { permissions, subscriber } = authResponseJson;
34
- // console.log(`subscriber = `, subscriber)
35
- // console.log(`permissions = `, permissions)
36
- setPermissions(permissions);
37
- setSubscriber(subscriber);
38
- } else {
39
- setPermissions(null);
40
- setSubscriber(null);
24
+ if (serverURL) {
25
+ try {
26
+ const authResponse = await fetch(`${serverURL}/api/subscriberAuth`, {
27
+ // body: JSON.stringify({}),
28
+ method: 'POST'
29
+ });
30
+ if (authResponse.ok) {
31
+ try {
32
+ const authResponseJson = await authResponse.json();
33
+ // console.log('authResponseJson', JSON.stringify(authResponseJson, undefined, 2))
34
+ const { permissions, subscriber } = authResponseJson;
35
+ console.log(`subscriber = `, subscriber);
36
+ // console.log(`permissions = `, permissions)
37
+ setPermissions(permissions);
38
+ setSubscriber(subscriber);
39
+ } catch (error) {
40
+ console.log('authResponse error, with json:', error);
41
+ const authResponseText = await authResponse.text();
42
+ console.log('authResponse error, text:', authResponseText);
43
+ }
44
+ } else {
45
+ console.log('authResponse not ok', authResponse);
46
+ setPermissions(null);
47
+ setSubscriber(null);
48
+ }
49
+ setIsLoaded(true);
50
+ } catch (error) {
51
+ console.log(`authResponse error`, error);
41
52
  }
42
- } catch (error) {
43
- console.log(`authResponse error`, error);
44
53
  }
45
- setIsLoaded(true);
46
- };
54
+ }, [
55
+ serverURL
56
+ ]);
47
57
  const refreshSubscriber = useCallback(async ()=>{
48
58
  await initSubscriber();
49
59
  }, [
50
- serverURL
60
+ initSubscriber
51
61
  ]);
52
62
  const logOut = useCallback(async ()=>{
53
63
  setIsLoaded(false);
@@ -76,7 +86,9 @@ const SubscriberContext = /*#__PURE__*/ createContext(undefined);
76
86
  }, []);
77
87
  useEffect(()=>{
78
88
  void initSubscriber();
79
- }, []);
89
+ }, [
90
+ initSubscriber
91
+ ]);
80
92
  // Memoize the value to prevent unnecessary re-renders in consumers
81
93
  const contextValue = useMemo(()=>({
82
94
  isLoaded,
@@ -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, OptInChannel, 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: ({ optIns?: null | OptInChannel[] } & Omit<Subscriber, 'optIns'>) | null\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: OptInChannel[] })>(\n null,\n )\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 initSubscriber = async () => {\n console.log('initSubscriber')\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 authResponseJson = await authResponse.json()\n // console.log('authResponseJson', JSON.stringify(authResponseJson, undefined, 2))\n const { permissions, subscriber } = authResponseJson\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\n const refreshSubscriber = useCallback(async () => {\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 initSubscriber()\n }, [])\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","initSubscriber","console","log","authResponse","fetch","method","ok","authResponseJson","json","error","refreshSubscriber","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,SAClC;IAGF,MAAM,EAAEQ,SAAS,EAAE,GAAGP;IAEtB,uDAAuD;IACvD,MAAM,CAACQ,UAAUC,YAAY,GAAGV,SAAS;IAEzC,MAAM,CAACW,aAAaC,eAAe,GAAGZ;IAEtC,MAAMa,iBAAiB;QACrBC,QAAQC,GAAG,CAAC;QACZL,YAAY;QACZ,IAAI;YACF,MAAMM,eAAe,MAAMC,MAAM,uBAAuB;gBACtD,4BAA4B;gBAC5BC,QAAQ;YACV;YAEA,IAAIF,aAAaG,EAAE,EAAE;gBACnB,gDAAgD;gBAChD,MAAMC,mBAAmB,MAAMJ,aAAaK,IAAI;gBAChD,kFAAkF;gBAClF,MAAM,EAAEV,WAAW,EAAEL,UAAU,EAAE,GAAGc;gBACpC,2CAA2C;gBAC3C,6CAA6C;gBAC7CR,eAAeD;gBACfJ,cAAcD;YAChB,OAAO;gBACLM,eAAe;gBACfL,cAAc;YAChB;QACF,EAAE,OAAOe,OAAgB;YACvBR,QAAQC,GAAG,CAAC,CAAC,kBAAkB,CAAC,EAAEO;QACpC;QACAZ,YAAY;IACd;IAEA,MAAMa,oBAAoB5B,YAAY;QACpC,MAAMkB;IACR,GAAG;QAACL;KAAU;IAEd,MAAMgB,SAAS7B,YAAY;QACzBe,YAAY;QACZ,IAAI;YACF,uCAAuC;YACvC,8BAA8B;YAC9B,KAAK;YACL,6CAA6C;YAC7C,cAAc;YACd,oBAAoB;YACpB,yBAAyB;YACzB,KAAK;YACL,oCAAoC;YACpC,MAAMe,iBAAiB,MAAMR,MAAM,eAAe;gBAChDC,QAAQ;YACV;YAEA,gDAAgD;YAEhD,IAAIO,eAAeN,EAAE,EAAE;gBACrBZ,cAAc;gBACdK,eAAe;YACjB;QACF,EAAE,OAAOU,OAAgB;YACvBR,QAAQC,GAAG,CAAC,CAAC,oBAAoB,CAAC,EAAEO;QACtC;QACAZ,YAAY;IACd,GAAG,EAAE;IAELd,UAAU;QACR,KAAKiB;IACP,GAAG,EAAE;IAEL,mEAAmE;IACnE,MAAMa,eAAsC3B,QAC1C,IAAO,CAAA;YACLU;YACAe;YACAb;YACAY;YACAjB;QACF,CAAA,GACA;QAACG;QAAUe;QAAQb;QAAaY;QAAmBjB;KAAW;IAGhE,qBAAO,KAACJ,kBAAkByB,QAAQ;QAACC,OAAOF;kBAAerB;;AAC3D;AAEA;;;;;CAKC,GACD,OAAO,SAASwB;IACd,MAAMC,UAAUhC,WAAWI;IAC3B,IAAI4B,YAAY3B,WAAW;QACzB,MAAM,IAAI4B,MAAM;IAClB;IACA,OAAOD;AACT"}
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, OptInChannel, 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: ({ optIns?: null | OptInChannel[] } & Omit<Subscriber, 'optIns'>) | null\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: OptInChannel[] })>(\n null,\n )\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 initSubscriber = useCallback(async () => {\n console.log('initSubscriber', serverURL)\n setIsLoaded(false)\n if (serverURL) {\n try {\n const authResponse = await fetch(`${serverURL}/api/subscriberAuth`, {\n // body: JSON.stringify({}),\n method: 'POST',\n })\n\n if (authResponse.ok) {\n try {\n const authResponseJson = await authResponse.json()\n // console.log('authResponseJson', JSON.stringify(authResponseJson, undefined, 2))\n const { permissions, subscriber } = authResponseJson\n console.log(`subscriber = `, subscriber)\n // console.log(`permissions = `, permissions)\n setPermissions(permissions)\n setSubscriber(subscriber)\n } catch (error) {\n console.log('authResponse error, with json:', error)\n const authResponseText = await authResponse.text()\n console.log('authResponse error, text:', authResponseText)\n }\n } else {\n console.log('authResponse not ok', authResponse)\n setPermissions(null)\n setSubscriber(null)\n }\n setIsLoaded(true)\n } catch (error: unknown) {\n console.log(`authResponse error`, error)\n }\n }\n }, [serverURL])\n\n const refreshSubscriber = useCallback(async () => {\n await initSubscriber()\n }, [initSubscriber])\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 initSubscriber()\n }, [initSubscriber])\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","initSubscriber","console","log","authResponse","fetch","method","ok","authResponseJson","json","error","authResponseText","text","refreshSubscriber","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,SAClC;IAGF,MAAM,EAAEQ,SAAS,EAAE,GAAGP;IAEtB,uDAAuD;IACvD,MAAM,CAACQ,UAAUC,YAAY,GAAGV,SAAS;IAEzC,MAAM,CAACW,aAAaC,eAAe,GAAGZ;IAEtC,MAAMa,iBAAiBlB,YAAY;QACjCmB,QAAQC,GAAG,CAAC,kBAAkBP;QAC9BE,YAAY;QACZ,IAAIF,WAAW;YACb,IAAI;gBACF,MAAMQ,eAAe,MAAMC,MAAM,GAAGT,UAAU,mBAAmB,CAAC,EAAE;oBAClE,4BAA4B;oBAC5BU,QAAQ;gBACV;gBAEA,IAAIF,aAAaG,EAAE,EAAE;oBACnB,IAAI;wBACF,MAAMC,mBAAmB,MAAMJ,aAAaK,IAAI;wBAChD,kFAAkF;wBAClF,MAAM,EAAEV,WAAW,EAAEL,UAAU,EAAE,GAAGc;wBACpCN,QAAQC,GAAG,CAAC,CAAC,aAAa,CAAC,EAAET;wBAC7B,6CAA6C;wBAC7CM,eAAeD;wBACfJ,cAAcD;oBAChB,EAAE,OAAOgB,OAAO;wBACdR,QAAQC,GAAG,CAAC,kCAAkCO;wBAC9C,MAAMC,mBAAmB,MAAMP,aAAaQ,IAAI;wBAChDV,QAAQC,GAAG,CAAC,6BAA6BQ;oBAC3C;gBACF,OAAO;oBACLT,QAAQC,GAAG,CAAC,uBAAuBC;oBACnCJ,eAAe;oBACfL,cAAc;gBAChB;gBACAG,YAAY;YACd,EAAE,OAAOY,OAAgB;gBACvBR,QAAQC,GAAG,CAAC,CAAC,kBAAkB,CAAC,EAAEO;YACpC;QACF;IACF,GAAG;QAACd;KAAU;IAEd,MAAMiB,oBAAoB9B,YAAY;QACpC,MAAMkB;IACR,GAAG;QAACA;KAAe;IAEnB,MAAMa,SAAS/B,YAAY;QACzBe,YAAY;QACZ,IAAI;YACF,uCAAuC;YACvC,8BAA8B;YAC9B,KAAK;YACL,6CAA6C;YAC7C,cAAc;YACd,oBAAoB;YACpB,yBAAyB;YACzB,KAAK;YACL,oCAAoC;YACpC,MAAMiB,iBAAiB,MAAMV,MAAM,eAAe;gBAChDC,QAAQ;YACV;YAEA,gDAAgD;YAEhD,IAAIS,eAAeR,EAAE,EAAE;gBACrBZ,cAAc;gBACdK,eAAe;YACjB;QACF,EAAE,OAAOU,OAAgB;YACvBR,QAAQC,GAAG,CAAC,CAAC,oBAAoB,CAAC,EAAEO;QACtC;QACAZ,YAAY;IACd,GAAG,EAAE;IAELd,UAAU;QACR,KAAKiB;IACP,GAAG;QAACA;KAAe;IAEnB,mEAAmE;IACnE,MAAMe,eAAsC7B,QAC1C,IAAO,CAAA;YACLU;YACAiB;YACAf;YACAc;YACAnB;QACF,CAAA,GACA;QAACG;QAAUiB;QAAQf;QAAac;QAAmBnB;KAAW;IAGhE,qBAAO,KAACJ,kBAAkB2B,QAAQ;QAACC,OAAOF;kBAAevB;;AAC3D;AAEA;;;;;CAKC,GACD,OAAO,SAAS0B;IACd,MAAMC,UAAUlC,WAAWI;IAC3B,IAAI8B,YAAY7B,WAAW;QACzB,MAAM,IAAI8B,MAAM;IAClB;IACA,OAAOD;AACT"}
@@ -8,7 +8,9 @@ export type LogoutResponse = {
8
8
  };
9
9
  /**
10
10
  * Factory that creates the logout endpoint config and handler.
11
- * Clears the current subscriber session by delegating to Payload's collection logout.
11
+ * Clears the current subscriber session by deleting Payload's cookie directly.
12
+ * (Delegating to Payload's collection logout is causing timing issues with the
13
+ * serverless function to serverless function call.)
12
14
  *
13
15
  * @param options - Config options for the endpoint
14
16
  * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
@@ -1,48 +1,32 @@
1
- import { headers as nextHeaders } from 'next/headers.js';
1
+ import { cookies as nextCookies } from 'next/headers.js';
2
+ import { NextResponse } from 'next/server.js';
2
3
  import { defaultCollectionSlug } from '../collections/Subscribers.js';
3
4
  /**
4
5
  * Factory that creates the logout endpoint config and handler.
5
- * Clears the current subscriber session by delegating to Payload's collection logout.
6
+ * Clears the current subscriber session by deleting Payload's cookie directly.
7
+ * (Delegating to Payload's collection logout is causing timing issues with the
8
+ * serverless function to serverless function call.)
6
9
  *
7
10
  * @param options - Config options for the endpoint
8
11
  * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
9
12
  * @returns Payload Endpoint config for POST /logout
10
13
  */ function createEndpointLogout({ subscribersCollectionSlug = defaultCollectionSlug }) {
11
14
  const logoutHandler = async (req)=>{
12
- const headers = await nextHeaders();
15
+ const collectionLogoutEndpoint = `${req.payload.config.serverURL}/api/${subscribersCollectionSlug}/logout`;
13
16
  try {
14
- const logoutResult = await fetch(`${req.payload.config.serverURL}/api/${subscribersCollectionSlug}/logout`, {
15
- headers,
16
- method: 'POST'
17
+ const cookies = await nextCookies();
18
+ cookies.set('payload-token', '', {
19
+ expires: new Date(0)
17
20
  });
18
- const logoutResultData = await logoutResult.json();
19
- if (logoutResult.ok) {
20
- return Response.json({
21
- message: logoutResultData.message,
22
- now: new Date().toISOString()
23
- });
24
- }
25
- if (logoutResult.status == 400 && logoutResultData.errors?.map((e)=>e.message).includes('No User')) {
26
- return Response.json({
27
- error: `Logout failed: 'No User'`,
28
- now: new Date().toISOString()
29
- }, {
30
- status: 400
31
- });
32
- }
33
- return Response.json({
34
- error: `Logout failed: ${logoutResultData.errors ? logoutResultData.errors?.map((e)=>e.message).join(' // ') : JSON.stringify(logoutResultData)}`,
21
+ return NextResponse.json({
22
+ message: 'Logged out',
35
23
  now: new Date().toISOString()
36
- }, {
37
- status: 400
38
24
  });
39
25
  } catch (error) {
26
+ req.payload.logger.error(`logoutHandler error: ${JSON.stringify(error, undefined, 2)}`);
40
27
  // throw new Error(`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
41
- return Response.json({
42
- error: `Logout failed: ${JSON.stringify(error)}`,
43
- now: new Date().toISOString()
44
- }, {
45
- status: 400
28
+ throw new Error(`Logout failed: ${collectionLogoutEndpoint} : ${JSON.stringify(error, undefined, 2)}`, {
29
+ cause: error
46
30
  });
47
31
  }
48
32
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoints/logout.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\n\nimport { headers as nextHeaders } from 'next/headers.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\n\nexport type LogoutResponse =\n | {\n error: string\n now: string\n }\n | {\n message: string\n now: string\n }\n\n/**\n * Factory that creates the logout endpoint config and handler.\n * Clears the current subscriber session by delegating to Payload's collection logout.\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 /logout\n */\nfunction createEndpointLogout({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n const logoutHandler: PayloadHandler = async (req) => {\n const headers = await nextHeaders()\n\n try {\n const logoutResult = await fetch(\n `${req.payload.config.serverURL}/api/${subscribersCollectionSlug}/logout`,\n {\n headers,\n method: 'POST',\n },\n )\n\n const logoutResultData = await logoutResult.json()\n\n if (logoutResult.ok) {\n return Response.json({\n message: logoutResultData.message,\n now: new Date().toISOString(),\n } as LogoutResponse)\n }\n\n if (\n logoutResult.status == 400 &&\n logoutResultData.errors?.map((e: { message: string }) => e.message).includes('No User')\n ) {\n return Response.json(\n {\n error: `Logout failed: 'No User'`,\n now: new Date().toISOString(),\n } as LogoutResponse,\n {\n status: 400,\n },\n )\n }\n return Response.json(\n {\n error: `Logout failed: ${\n logoutResultData.errors\n ? logoutResultData.errors?.map((e: { message: string }) => e.message).join(' // ')\n : JSON.stringify(logoutResultData)\n }`,\n now: new Date().toISOString(),\n } as LogoutResponse,\n {\n status: 400,\n },\n )\n } catch (error) {\n // throw new Error(`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`)\n return Response.json(\n {\n error: `Logout failed: ${JSON.stringify(error)}`,\n now: new Date().toISOString(),\n } as LogoutResponse,\n {\n status: 400,\n },\n )\n }\n }\n\n /** Endpoint config for subscriber logout. Mount as POST /logout. */\n const logoutEndpoint: Endpoint = {\n handler: logoutHandler,\n method: 'post',\n path: '/logout',\n }\n\n return logoutEndpoint\n}\n\nexport default createEndpointLogout\n"],"names":["headers","nextHeaders","defaultCollectionSlug","createEndpointLogout","subscribersCollectionSlug","logoutHandler","req","logoutResult","fetch","payload","config","serverURL","method","logoutResultData","json","ok","Response","message","now","Date","toISOString","status","errors","map","e","includes","error","join","JSON","stringify","logoutEndpoint","handler","path"],"mappings":"AAEA,SAASA,WAAWC,WAAW,QAAQ,kBAAiB;AAExD,SAASC,qBAAqB,QAAQ,gCAA+B;AAYrE;;;;;;;CAOC,GACD,SAASC,qBAAqB,EAC5BC,4BAA4BF,qBAAqB,EAGlD;IACC,MAAMG,gBAAgC,OAAOC;QAC3C,MAAMN,UAAU,MAAMC;QAEtB,IAAI;YACF,MAAMM,eAAe,MAAMC,MACzB,GAAGF,IAAIG,OAAO,CAACC,MAAM,CAACC,SAAS,CAAC,KAAK,EAAEP,0BAA0B,OAAO,CAAC,EACzE;gBACEJ;gBACAY,QAAQ;YACV;YAGF,MAAMC,mBAAmB,MAAMN,aAAaO,IAAI;YAEhD,IAAIP,aAAaQ,EAAE,EAAE;gBACnB,OAAOC,SAASF,IAAI,CAAC;oBACnBG,SAASJ,iBAAiBI,OAAO;oBACjCC,KAAK,IAAIC,OAAOC,WAAW;gBAC7B;YACF;YAEA,IACEb,aAAac,MAAM,IAAI,OACvBR,iBAAiBS,MAAM,EAAEC,IAAI,CAACC,IAA2BA,EAAEP,OAAO,EAAEQ,SAAS,YAC7E;gBACA,OAAOT,SAASF,IAAI,CAClB;oBACEY,OAAO,CAAC,wBAAwB,CAAC;oBACjCR,KAAK,IAAIC,OAAOC,WAAW;gBAC7B,GACA;oBACEC,QAAQ;gBACV;YAEJ;YACA,OAAOL,SAASF,IAAI,CAClB;gBACEY,OAAO,CAAC,eAAe,EACrBb,iBAAiBS,MAAM,GACnBT,iBAAiBS,MAAM,EAAEC,IAAI,CAACC,IAA2BA,EAAEP,OAAO,EAAEU,KAAK,UACzEC,KAAKC,SAAS,CAAChB,mBACnB;gBACFK,KAAK,IAAIC,OAAOC,WAAW;YAC7B,GACA;gBACEC,QAAQ;YACV;QAEJ,EAAE,OAAOK,OAAO;YACd,gGAAgG;YAChG,OAAOV,SAASF,IAAI,CAClB;gBACEY,OAAO,CAAC,eAAe,EAAEE,KAAKC,SAAS,CAACH,QAAQ;gBAChDR,KAAK,IAAIC,OAAOC,WAAW;YAC7B,GACA;gBACEC,QAAQ;YACV;QAEJ;IACF;IAEA,kEAAkE,GAClE,MAAMS,iBAA2B;QAC/BC,SAAS1B;QACTO,QAAQ;QACRoB,MAAM;IACR;IAEA,OAAOF;AACT;AAEA,eAAe3B,qBAAoB"}
1
+ {"version":3,"sources":["../../src/endpoints/logout.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\n\nimport { cookies as nextCookies } from 'next/headers.js'\nimport { NextResponse } from 'next/server.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\n\nexport type LogoutResponse =\n | {\n error: string\n now: string\n }\n | {\n message: string\n now: string\n }\n\n/**\n * Factory that creates the logout endpoint config and handler.\n * Clears the current subscriber session by deleting Payload's cookie directly.\n * (Delegating to Payload's collection logout is causing timing issues with the\n * serverless function to serverless function call.)\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 /logout\n */\nfunction createEndpointLogout({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n const logoutHandler: PayloadHandler = async (req) => {\n const collectionLogoutEndpoint = `${req.payload.config.serverURL}/api/${subscribersCollectionSlug}/logout`\n try {\n const cookies = await nextCookies()\n cookies.set('payload-token', '', { expires: new Date(0) })\n\n return NextResponse.json({\n message: 'Logged out',\n now: new Date().toISOString(),\n } as LogoutResponse)\n } catch (error) {\n req.payload.logger.error(`logoutHandler error: ${JSON.stringify(error, undefined, 2)}`)\n\n // throw new Error(`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`)\n throw new Error(\n `Logout failed: ${collectionLogoutEndpoint} : ${JSON.stringify(error, undefined, 2)}`,\n { cause: error },\n )\n }\n }\n\n /** Endpoint config for subscriber logout. Mount as POST /logout. */\n const logoutEndpoint: Endpoint = {\n handler: logoutHandler,\n method: 'post',\n path: '/logout',\n }\n\n return logoutEndpoint\n}\n\nexport default createEndpointLogout\n"],"names":["cookies","nextCookies","NextResponse","defaultCollectionSlug","createEndpointLogout","subscribersCollectionSlug","logoutHandler","req","collectionLogoutEndpoint","payload","config","serverURL","set","expires","Date","json","message","now","toISOString","error","logger","JSON","stringify","undefined","Error","cause","logoutEndpoint","handler","method","path"],"mappings":"AAEA,SAASA,WAAWC,WAAW,QAAQ,kBAAiB;AACxD,SAASC,YAAY,QAAQ,iBAAgB;AAE7C,SAASC,qBAAqB,QAAQ,gCAA+B;AAYrE;;;;;;;;;CASC,GACD,SAASC,qBAAqB,EAC5BC,4BAA4BF,qBAAqB,EAGlD;IACC,MAAMG,gBAAgC,OAAOC;QAC3C,MAAMC,2BAA2B,GAAGD,IAAIE,OAAO,CAACC,MAAM,CAACC,SAAS,CAAC,KAAK,EAAEN,0BAA0B,OAAO,CAAC;QAC1G,IAAI;YACF,MAAML,UAAU,MAAMC;YACtBD,QAAQY,GAAG,CAAC,iBAAiB,IAAI;gBAAEC,SAAS,IAAIC,KAAK;YAAG;YAExD,OAAOZ,aAAaa,IAAI,CAAC;gBACvBC,SAAS;gBACTC,KAAK,IAAIH,OAAOI,WAAW;YAC7B;QACF,EAAE,OAAOC,OAAO;YACdZ,IAAIE,OAAO,CAACW,MAAM,CAACD,KAAK,CAAC,CAAC,qBAAqB,EAAEE,KAAKC,SAAS,CAACH,OAAOI,WAAW,IAAI;YAEtF,gGAAgG;YAChG,MAAM,IAAIC,MACR,CAAC,eAAe,EAAEhB,yBAAyB,GAAG,EAAEa,KAAKC,SAAS,CAACH,OAAOI,WAAW,IAAI,EACrF;gBAAEE,OAAON;YAAM;QAEnB;IACF;IAEA,kEAAkE,GAClE,MAAMO,iBAA2B;QAC/BC,SAASrB;QACTsB,QAAQ;QACRC,MAAM;IACR;IAEA,OAAOH;AACT;AAEA,eAAetB,qBAAoB"}
@@ -15,11 +15,13 @@ import { getHash, getTokenAndHash } from '../helpers/token.js';
15
15
  * @param req - Payload request; body must include `email` and `token`
16
16
  * @returns 200 with `message`, `now` and Set-Cookie on success; 400 with `error` and `now` on bad data, invalid token, or expiry
17
17
  */ const verifyMagicLinkHandler = async (req)=>{
18
+ // req.payload.logger.info('verifyMagicLinkHandler')
18
19
  const reqData = req?.json ? await req.json() : {};
19
20
  const { email, token } = reqData // if by POST reqData
20
21
  ;
21
22
  // const { email, token } = req.routeParams // if by path
22
23
  if (!email || !token) {
24
+ req.payload.logger.info('verifyMagicLinkHandler Bad data');
23
25
  return Response.json({
24
26
  error: 'Bad data',
25
27
  now: new Date().toISOString()
@@ -37,6 +39,7 @@ import { getHash, getTokenAndHash } from '../helpers/token.js';
37
39
  });
38
40
  const user = userResults.docs[0];
39
41
  if (!user) {
42
+ req.payload.logger.info('verifyMagicLinkHandler no user');
40
43
  return Response.json({
41
44
  error: 'Bad data',
42
45
  now: new Date().toISOString()
@@ -58,6 +61,7 @@ import { getHash, getTokenAndHash } from '../helpers/token.js';
58
61
  });
59
62
  }
60
63
  if (new Date(Date.now()) > new Date(user.verificationTokenExpires)) {
64
+ req.payload.logger.info('verifyMagicLinkHandler Token expired');
61
65
  return Response.json({
62
66
  error: 'Token expired',
63
67
  now: new Date().toISOString()
@@ -65,18 +69,25 @@ import { getHash, getTokenAndHash } from '../helpers/token.js';
65
69
  status: 400
66
70
  });
67
71
  }
68
- // Update user
72
+ // req.payload.logger.info(
73
+ // `verifyMagicLinkHandler user found and token validated, prepping to authencticate ${user.email}`,
74
+ // )
75
+ // Update user with token password
69
76
  await req.payload.update({
70
77
  collection: subscribersCollectionSlug,
71
78
  data: {
72
79
  password: tokenHash
73
80
  },
81
+ disableTransaction: true,
74
82
  where: {
75
83
  email: {
76
84
  equals: user.email
77
85
  }
78
86
  }
79
87
  });
88
+ // req.payload.logger.info(
89
+ // 'verifyMagicLinkHandler user found and token validated, prepping to authencticate DONE',
90
+ // )
80
91
  // Log the user in via Payload headers
81
92
  let headers;
82
93
  try {
@@ -96,13 +107,12 @@ import { getHash, getTokenAndHash } from '../helpers/token.js';
96
107
  }
97
108
  } catch (error) {
98
109
  // console.log(error)
99
- return Response.json({
100
- error
101
- }, {
102
- status: 400
110
+ req.payload.logger.info(`verifyMagicLinkHandler catch error ${JSON.stringify(error, undefined, 2)}`);
111
+ throw new Error(`verifyMagicLinkHandler catch error: ${JSON.stringify(error, undefined, 2)}`, {
112
+ cause: error
103
113
  });
114
+ // return Response.json({ error } as VerifyMagicLinkResponse, { status: 400 })
104
115
  }
105
- // console.log('login', headers)
106
116
  const status = user?.status == 'pending' ? 'subscribed' : user?.status;
107
117
  const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable
108
118
  ;
@@ -112,21 +122,49 @@ import { getHash, getTokenAndHash } from '../helpers/token.js';
112
122
  verificationToken: '',
113
123
  verificationTokenExpires: null
114
124
  };
115
- // Update user
116
- await req.payload.update({
117
- collection: subscribersCollectionSlug,
118
- data,
119
- where: {
120
- email: {
121
- equals: user.email
125
+ let updateResult;
126
+ try {
127
+ // Update user
128
+ updateResult = await req.payload.update({
129
+ collection: subscribersCollectionSlug,
130
+ data,
131
+ where: {
132
+ email: {
133
+ equals: user.email
134
+ }
122
135
  }
136
+ });
137
+ } catch (error) {
138
+ // console.log(error)
139
+ req.payload.logger.info(`verifyMagicLinkHandler update catch error ${JSON.stringify(error, undefined, 2)}`);
140
+ throw new Error(`verifyMagicLinkHandler update catch error: ${JSON.stringify(error, undefined, 2)}`, {
141
+ cause: error
142
+ });
143
+ // return Response.json({ error } as VerifyMagicLinkResponse, { status: 400 })
144
+ }
145
+ function keepOnlySetCookie(originalHeaders) {
146
+ // Use getSetCookie() to get all values as an array
147
+ const setCookieValues = originalHeaders.getSetCookie();
148
+ // Create a new Headers object
149
+ const newHeaders = new Headers();
150
+ // Append each 'set-cookie' value individually
151
+ for (const cookieValue of setCookieValues){
152
+ newHeaders.append('set-cookie', cookieValue);
123
153
  }
124
- });
154
+ return newHeaders;
155
+ }
156
+ const newHeaders = headers ? keepOnlySetCookie(headers) : undefined;
157
+ // req.payload.logger.info(
158
+ // `verifyMagicLinkHandler headers ${JSON.stringify(headers?.entries(), undefined, 2)}`,
159
+ // )
160
+ // req.payload.logger.info(
161
+ // `verifyMagicLinkHandler newHeaders ${JSON.stringify(newHeaders?.entries(), undefined, 2)}`,
162
+ // )
125
163
  return Response.json({
126
164
  message: 'Token verified',
127
165
  now: new Date().toISOString()
128
166
  }, {
129
- headers
167
+ headers: newHeaders
130
168
  });
131
169
  };
132
170
  /** Endpoint config for verifying magic link and logging in. Mount as POST /verifyToken. */ const verifyMagicLinkEndpoint = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoints/verifyMagicLink.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 { getHash, getTokenAndHash } from '../helpers/token.js'\n\nexport type VerifyMagicLinkResponse =\n | {\n error: string\n now: string\n }\n | {\n message: string\n now: string\n }\n\n/**\n * Factory that creates the verify-magic-link endpoint config and handler.\n * Validates token from the magic link, marks the subscriber as verified, and logs them in.\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 /verifyToken\n */\nfunction createEndpointVerifyMagicLink({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n /**\n * Handler for POST /verifyToken. Validates email + token from magic link, updates subscriber\n * password and status, and performs login to set auth cookies.\n *\n * @param req - Payload request; body must include `email` and `token`\n * @returns 200 with `message`, `now` and Set-Cookie on success; 400 with `error` and `now` on bad data, invalid token, or expiry\n */\n const verifyMagicLinkHandler: PayloadHandler = async (req) => {\n const reqData = req?.json ? await req.json() : {}\n const { email, token }: { email: string; token: string } = reqData // if by POST reqData\n // const { email, token } = req.routeParams // if by path\n\n if (!email || !token) {\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as VerifyMagicLinkResponse,\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\n type SubscriberType = {\n // @ts-expect-error Why is this not correct, isn't it how Payload does it?\n collection: subscribersCollectionSlug\n } & Subscriber\n\n const user = userResults.docs[0] as SubscriberType\n\n if (!user) {\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as VerifyMagicLinkResponse,\n { status: 400 },\n )\n }\n\n const { tokenHash } = getHash(token)\n\n // req.payload.logger.info(\n // `verifyMagicLinkHandler ${email} \\n ${tokenHash} \\n ${user.verificationTokenExpires} \\n ${user.verificationToken}`,\n // )\n if (!user.verificationTokenExpires || tokenHash != user.verificationToken) {\n req.payload.logger.info(`Token not verified: ${tokenHash} != ${user.verificationToken}`)\n return Response.json(\n { error: 'Token not verified', now: new Date().toISOString() } as VerifyMagicLinkResponse,\n { status: 400 },\n )\n }\n\n if (new Date(Date.now()) > new Date(user.verificationTokenExpires)) {\n return Response.json(\n { error: 'Token expired', now: new Date().toISOString() } as VerifyMagicLinkResponse,\n { status: 400 },\n )\n }\n\n // Update user\n await req.payload.update({\n collection: subscribersCollectionSlug,\n data: {\n password: tokenHash,\n },\n where: {\n email: { equals: user.email },\n },\n })\n\n // Log the user in via Payload headers\n let headers\n try {\n const loginReq = await fetch(\n `${req.payload.config.serverURL}/api/${subscribersCollectionSlug}/login`,\n {\n body: JSON.stringify({\n email,\n password: tokenHash,\n }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n },\n )\n if (loginReq && loginReq.ok) {\n headers = loginReq.headers\n }\n } catch (error) {\n // console.log(error)\n return Response.json({ error } as VerifyMagicLinkResponse, { status: 400 })\n }\n // console.log('login', headers)\n\n const status: 'pending' | 'subscribed' | 'unsubscribed' | undefined =\n user?.status == 'pending' ? 'subscribed' : user?.status\n\n const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable\n const data = {\n password: tokenHash2,\n status,\n verificationToken: '',\n verificationTokenExpires: null,\n }\n // Update user\n await req.payload.update({\n collection: subscribersCollectionSlug,\n data,\n where: {\n email: { equals: user.email },\n },\n })\n\n return Response.json(\n {\n message: 'Token verified',\n now: new Date().toISOString(),\n } as VerifyMagicLinkResponse,\n { headers },\n )\n }\n\n /** Endpoint config for verifying magic link and logging in. Mount as POST /verifyToken. */\n const verifyMagicLinkEndpoint: Endpoint = {\n handler: verifyMagicLinkHandler,\n method: 'post',\n path: '/verifyToken',\n }\n\n return verifyMagicLinkEndpoint\n}\n\nexport default createEndpointVerifyMagicLink\n"],"names":["defaultCollectionSlug","getHash","getTokenAndHash","createEndpointVerifyMagicLink","subscribersCollectionSlug","verifyMagicLinkHandler","req","reqData","json","email","token","Response","error","now","Date","toISOString","status","userResults","payload","find","collection","where","equals","user","docs","tokenHash","verificationTokenExpires","verificationToken","logger","info","update","data","password","headers","loginReq","fetch","config","serverURL","body","JSON","stringify","credentials","method","ok","tokenHash2","message","verifyMagicLinkEndpoint","handler","path"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/endpoints/verifyMagicLink.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 { getHash, getTokenAndHash } from '../helpers/token.js'\n\nexport type VerifyMagicLinkResponse =\n | {\n error: string\n now: string\n }\n | {\n message: string\n now: string\n }\n\n/**\n * Factory that creates the verify-magic-link endpoint config and handler.\n * Validates token from the magic link, marks the subscriber as verified, and logs them in.\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 /verifyToken\n */\nfunction createEndpointVerifyMagicLink({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n /**\n * Handler for POST /verifyToken. Validates email + token from magic link, updates subscriber\n * password and status, and performs login to set auth cookies.\n *\n * @param req - Payload request; body must include `email` and `token`\n * @returns 200 with `message`, `now` and Set-Cookie on success; 400 with `error` and `now` on bad data, invalid token, or expiry\n */\n const verifyMagicLinkHandler: PayloadHandler = async (req) => {\n // req.payload.logger.info('verifyMagicLinkHandler')\n const reqData = req?.json ? await req.json() : {}\n const { email, token }: { email: string; token: string } = reqData // if by POST reqData\n // const { email, token } = req.routeParams // if by path\n\n if (!email || !token) {\n req.payload.logger.info('verifyMagicLinkHandler Bad data')\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as VerifyMagicLinkResponse,\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\n type SubscriberType = {\n // @ts-expect-error Why is this not correct, isn't it how Payload does it?\n collection: subscribersCollectionSlug\n } & Subscriber\n\n const user = userResults.docs[0] as SubscriberType\n\n if (!user) {\n req.payload.logger.info('verifyMagicLinkHandler no user')\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as VerifyMagicLinkResponse,\n { status: 400 },\n )\n }\n\n const { tokenHash } = getHash(token)\n\n // req.payload.logger.info(\n // `verifyMagicLinkHandler ${email} \\n ${tokenHash} \\n ${user.verificationTokenExpires} \\n ${user.verificationToken}`,\n // )\n if (!user.verificationTokenExpires || tokenHash != user.verificationToken) {\n req.payload.logger.info(`Token not verified: ${tokenHash} != ${user.verificationToken}`)\n return Response.json(\n { error: 'Token not verified', now: new Date().toISOString() } as VerifyMagicLinkResponse,\n { status: 400 },\n )\n }\n\n if (new Date(Date.now()) > new Date(user.verificationTokenExpires)) {\n req.payload.logger.info('verifyMagicLinkHandler Token expired')\n return Response.json(\n { error: 'Token expired', now: new Date().toISOString() } as VerifyMagicLinkResponse,\n { status: 400 },\n )\n }\n\n // req.payload.logger.info(\n // `verifyMagicLinkHandler user found and token validated, prepping to authencticate ${user.email}`,\n // )\n // Update user with token password\n await req.payload.update({\n collection: subscribersCollectionSlug,\n data: {\n password: tokenHash,\n },\n disableTransaction: true,\n where: {\n email: { equals: user.email },\n },\n })\n // req.payload.logger.info(\n // 'verifyMagicLinkHandler user found and token validated, prepping to authencticate DONE',\n // )\n\n // Log the user in via Payload headers\n let headers\n try {\n const loginReq = await fetch(\n `${req.payload.config.serverURL}/api/${subscribersCollectionSlug}/login`,\n {\n body: JSON.stringify({\n email,\n password: tokenHash,\n }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n },\n )\n if (loginReq && loginReq.ok) {\n headers = loginReq.headers\n }\n } catch (error) {\n // console.log(error)\n req.payload.logger.info(\n `verifyMagicLinkHandler catch error ${JSON.stringify(error, undefined, 2)}`,\n )\n throw new Error(\n `verifyMagicLinkHandler catch error: ${JSON.stringify(error, undefined, 2)}`,\n { cause: error },\n )\n // return Response.json({ error } as VerifyMagicLinkResponse, { status: 400 })\n }\n\n const status: 'pending' | 'subscribed' | 'unsubscribed' | undefined =\n user?.status == 'pending' ? 'subscribed' : user?.status\n\n const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable\n const data = {\n password: tokenHash2,\n status,\n verificationToken: '',\n verificationTokenExpires: null,\n }\n let updateResult\n try {\n // Update user\n updateResult = await req.payload.update({\n collection: subscribersCollectionSlug,\n data,\n where: {\n email: { equals: user.email },\n },\n })\n } catch (error) {\n // console.log(error)\n req.payload.logger.info(\n `verifyMagicLinkHandler update catch error ${JSON.stringify(error, undefined, 2)}`,\n )\n throw new Error(\n `verifyMagicLinkHandler update catch error: ${JSON.stringify(error, undefined, 2)}`,\n { cause: error },\n )\n // return Response.json({ error } as VerifyMagicLinkResponse, { status: 400 })\n }\n\n function keepOnlySetCookie(originalHeaders: Headers): Headers {\n // Use getSetCookie() to get all values as an array\n const setCookieValues = originalHeaders.getSetCookie()\n\n // Create a new Headers object\n const newHeaders = new Headers()\n\n // Append each 'set-cookie' value individually\n for (const cookieValue of setCookieValues) {\n newHeaders.append('set-cookie', cookieValue)\n }\n\n return newHeaders\n }\n\n const newHeaders = headers ? keepOnlySetCookie(headers) : undefined\n // req.payload.logger.info(\n // `verifyMagicLinkHandler headers ${JSON.stringify(headers?.entries(), undefined, 2)}`,\n // )\n // req.payload.logger.info(\n // `verifyMagicLinkHandler newHeaders ${JSON.stringify(newHeaders?.entries(), undefined, 2)}`,\n // )\n\n return Response.json(\n {\n message: 'Token verified',\n now: new Date().toISOString(),\n } as VerifyMagicLinkResponse,\n { headers: newHeaders },\n )\n }\n\n /** Endpoint config for verifying magic link and logging in. Mount as POST /verifyToken. */\n const verifyMagicLinkEndpoint: Endpoint = {\n handler: verifyMagicLinkHandler,\n method: 'post',\n path: '/verifyToken',\n }\n\n return verifyMagicLinkEndpoint\n}\n\nexport default createEndpointVerifyMagicLink\n"],"names":["defaultCollectionSlug","getHash","getTokenAndHash","createEndpointVerifyMagicLink","subscribersCollectionSlug","verifyMagicLinkHandler","req","reqData","json","email","token","payload","logger","info","Response","error","now","Date","toISOString","status","userResults","find","collection","where","equals","user","docs","tokenHash","verificationTokenExpires","verificationToken","update","data","password","disableTransaction","headers","loginReq","fetch","config","serverURL","body","JSON","stringify","credentials","method","ok","undefined","Error","cause","tokenHash2","updateResult","keepOnlySetCookie","originalHeaders","setCookieValues","getSetCookie","newHeaders","Headers","cookieValue","append","message","verifyMagicLinkEndpoint","handler","path"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { VerifyMagicLinkResponse } from '../endpoints/verifyMagicLink.js';
2
2
  export { VerifyMagicLinkResponse };
3
+ type VerifyStatus = 'default' | 'error' | 'verified' | 'verifying';
3
4
  /**
4
5
  * Return value of useVerifyMagicLink.
5
6
  *
@@ -12,6 +13,7 @@ export interface IUseVerifyMagicLink {
12
13
  isError: boolean;
13
14
  isLoading: boolean;
14
15
  result: string;
16
+ status: VerifyStatus;
15
17
  verify: () => void;
16
18
  }
17
19
  /**
@@ -25,6 +27,7 @@ export declare const useVerifyMagicLink: () => {
25
27
  isError: boolean;
26
28
  isLoading: boolean;
27
29
  result: string;
30
+ status: VerifyStatus;
28
31
  verify: () => Promise<{
29
32
  error: string;
30
33
  } | undefined>;
@@ -16,9 +16,11 @@ import { useServerUrl } from '../react-hooks/useServerUrl.js';
16
16
  const email = searchParams.get('email');
17
17
  const token = searchParams.get('token');
18
18
  const [result, setResult] = useState();
19
+ const [status, setStatus] = useState('default');
19
20
  const [isError, setIsError] = useState(false);
20
21
  // const [email, setEmail] = useState('')
21
22
  const verify = useCallback(async ()=>{
23
+ setStatus('verifying');
22
24
  if (!email || !token) {
23
25
  return {
24
26
  error: 'Invalid input'
@@ -38,20 +40,24 @@ import { useServerUrl } from '../react-hooks/useServerUrl.js';
38
40
  });
39
41
  if (verifyEndpointResult && verifyEndpointResult.json) {
40
42
  const resultJson = await verifyEndpointResult.json();
41
- setResult(resultJson.error || resultJson.message);
42
43
  setIsError(!!resultJson.error);
44
+ setResult(resultJson.error || resultJson.message);
45
+ setStatus(resultJson.error ? 'error' : 'verified');
43
46
  // return { error: resultJson.error, message: resultJson.message }
44
47
  } else if (verifyEndpointResult && verifyEndpointResult.text) {
45
48
  const resultText = await verifyEndpointResult.text();
46
- setResult(resultText);
47
49
  setIsError(true);
50
+ setResult(resultText);
51
+ setStatus('error');
48
52
  } else {
49
- setResult(`Error: ${verifyEndpointResult.status}`);
50
53
  setIsError(true);
54
+ setResult(`Error: ${verifyEndpointResult.status}`);
55
+ setStatus('error');
51
56
  }
52
57
  } catch (error) {
53
- setResult(`Error: ${error}`);
54
58
  setIsError(true);
59
+ setResult(`Error: ${error}`);
60
+ setStatus('error');
55
61
  }
56
62
  if (!isError) {
57
63
  refreshSubscriber();
@@ -67,6 +73,7 @@ import { useServerUrl } from '../react-hooks/useServerUrl.js';
67
73
  isError,
68
74
  isLoading: !result,
69
75
  result: result || '',
76
+ status,
70
77
  verify
71
78
  };
72
79
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/useVerifyMagicLink.tsx"],"sourcesContent":["'use client'\n\nimport { useSearchParams } from 'next/navigation.js'\nimport { useCallback, useState } from 'react'\n\nimport type { VerifyMagicLinkResponse } from '../endpoints/verifyMagicLink.js'\n\nexport { VerifyMagicLinkResponse }\nimport { useSubscriber } from '../contexts/SubscriberProvider.js'\nimport { useServerUrl } from '../react-hooks/useServerUrl.js'\n\n/**\n * Return value of useVerifyMagicLink.\n *\n * @property isError - True if the last verify attempt failed\n * @property isLoading - True until verify has been run and has a result\n * @property result - Result message from the last verify attempt\n * @property verify - Calls POST /api/verifyToken with email and token from URL search params\n */\nexport interface IUseVerifyMagicLink {\n isError: boolean\n isLoading: boolean\n result: string\n verify: () => void\n}\n\n/**\n * Hook for the verify step of the magic-link flow. Reads email and token from URL search params,\n * calls POST /api/verifyToken to verify and log in, and refreshes subscriber on success.\n * Takes no parameters.\n *\n * @returns verify function plus isLoading, isError, and result (see IUseVerifyMagicLink)\n */\nexport const useVerifyMagicLink = () => {\n const { serverURL } = useServerUrl()\n\n const { refreshSubscriber } = useSubscriber()\n\n const searchParams = useSearchParams()\n const email = searchParams.get('email')\n const token = searchParams.get('token')\n\n const [result, setResult] = useState<string>()\n const [isError, setIsError] = useState<boolean>(false)\n // const [email, setEmail] = useState('')\n\n const verify = useCallback(async () => {\n if (!email || !token) {\n return { error: 'Invalid input' }\n }\n try {\n // I tried using PayloadSDK.request, but when the endpoint\n // returns a not-okay status, PayloadSDK.request returns its\n // own \"Bad request\" error, and doesn't share the endpoint\n // result data.\n const verifyEndpointResult = await fetch(`${serverURL ? serverURL : ''}/api/verifyToken`, {\n body: JSON.stringify({\n email,\n token,\n }),\n method: 'POST',\n })\n\n if (verifyEndpointResult && verifyEndpointResult.json) {\n const resultJson = await verifyEndpointResult.json()\n setResult(resultJson.error || resultJson.message)\n setIsError(!!resultJson.error)\n // return { error: resultJson.error, message: resultJson.message }\n } else if (verifyEndpointResult && verifyEndpointResult.text) {\n const resultText = await verifyEndpointResult.text()\n setResult(resultText)\n setIsError(true)\n } else {\n setResult(`Error: ${verifyEndpointResult.status}`)\n setIsError(true)\n }\n } catch (error: unknown) {\n setResult(`Error: ${error}`)\n setIsError(true)\n }\n if (!isError) {\n refreshSubscriber()\n }\n }, [email, isError, refreshSubscriber, serverURL, token])\n\n return {\n isError,\n isLoading: !result,\n result: result || '',\n verify,\n }\n}\n"],"names":["useSearchParams","useCallback","useState","useSubscriber","useServerUrl","useVerifyMagicLink","serverURL","refreshSubscriber","searchParams","email","get","token","result","setResult","isError","setIsError","verify","error","verifyEndpointResult","fetch","body","JSON","stringify","method","json","resultJson","message","text","resultText","status","isLoading"],"mappings":"AAAA;AAEA,SAASA,eAAe,QAAQ,qBAAoB;AACpD,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAK7C,SAASC,aAAa,QAAQ,oCAAmC;AACjE,SAASC,YAAY,QAAQ,iCAAgC;AAiB7D;;;;;;CAMC,GACD,OAAO,MAAMC,qBAAqB;IAChC,MAAM,EAAEC,SAAS,EAAE,GAAGF;IAEtB,MAAM,EAAEG,iBAAiB,EAAE,GAAGJ;IAE9B,MAAMK,eAAeR;IACrB,MAAMS,QAAQD,aAAaE,GAAG,CAAC;IAC/B,MAAMC,QAAQH,aAAaE,GAAG,CAAC;IAE/B,MAAM,CAACE,QAAQC,UAAU,GAAGX;IAC5B,MAAM,CAACY,SAASC,WAAW,GAAGb,SAAkB;IAChD,yCAAyC;IAEzC,MAAMc,SAASf,YAAY;QACzB,IAAI,CAACQ,SAAS,CAACE,OAAO;YACpB,OAAO;gBAAEM,OAAO;YAAgB;QAClC;QACA,IAAI;YACF,0DAA0D;YAC1D,4DAA4D;YAC5D,0DAA0D;YAC1D,eAAe;YACf,MAAMC,uBAAuB,MAAMC,MAAM,GAAGb,YAAYA,YAAY,GAAG,gBAAgB,CAAC,EAAE;gBACxFc,MAAMC,KAAKC,SAAS,CAAC;oBACnBb;oBACAE;gBACF;gBACAY,QAAQ;YACV;YAEA,IAAIL,wBAAwBA,qBAAqBM,IAAI,EAAE;gBACrD,MAAMC,aAAa,MAAMP,qBAAqBM,IAAI;gBAClDX,UAAUY,WAAWR,KAAK,IAAIQ,WAAWC,OAAO;gBAChDX,WAAW,CAAC,CAACU,WAAWR,KAAK;YAC7B,kEAAkE;YACpE,OAAO,IAAIC,wBAAwBA,qBAAqBS,IAAI,EAAE;gBAC5D,MAAMC,aAAa,MAAMV,qBAAqBS,IAAI;gBAClDd,UAAUe;gBACVb,WAAW;YACb,OAAO;gBACLF,UAAU,CAAC,OAAO,EAAEK,qBAAqBW,MAAM,EAAE;gBACjDd,WAAW;YACb;QACF,EAAE,OAAOE,OAAgB;YACvBJ,UAAU,CAAC,OAAO,EAAEI,OAAO;YAC3BF,WAAW;QACb;QACA,IAAI,CAACD,SAAS;YACZP;QACF;IACF,GAAG;QAACE;QAAOK;QAASP;QAAmBD;QAAWK;KAAM;IAExD,OAAO;QACLG;QACAgB,WAAW,CAAClB;QACZA,QAAQA,UAAU;QAClBI;IACF;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/hooks/useVerifyMagicLink.tsx"],"sourcesContent":["'use client'\n\nimport { useSearchParams } from 'next/navigation.js'\nimport { useCallback, useState } from 'react'\n\nimport type { VerifyMagicLinkResponse } from '../endpoints/verifyMagicLink.js'\n\nexport { VerifyMagicLinkResponse }\nimport { useSubscriber } from '../contexts/SubscriberProvider.js'\nimport { useServerUrl } from '../react-hooks/useServerUrl.js'\n\ntype VerifyStatus = 'default' | 'error' | 'verified' | 'verifying'\n\n/**\n * Return value of useVerifyMagicLink.\n *\n * @property isError - True if the last verify attempt failed\n * @property isLoading - True until verify has been run and has a result\n * @property result - Result message from the last verify attempt\n * @property verify - Calls POST /api/verifyToken with email and token from URL search params\n */\nexport interface IUseVerifyMagicLink {\n isError: boolean\n isLoading: boolean\n result: string\n status: VerifyStatus\n verify: () => void\n}\n\n/**\n * Hook for the verify step of the magic-link flow. Reads email and token from URL search params,\n * calls POST /api/verifyToken to verify and log in, and refreshes subscriber on success.\n * Takes no parameters.\n *\n * @returns verify function plus isLoading, isError, and result (see IUseVerifyMagicLink)\n */\nexport const useVerifyMagicLink = () => {\n const { serverURL } = useServerUrl()\n\n const { refreshSubscriber } = useSubscriber()\n\n const searchParams = useSearchParams()\n const email = searchParams.get('email')\n const token = searchParams.get('token')\n\n const [result, setResult] = useState<string>()\n const [status, setStatus] = useState<VerifyStatus>('default')\n const [isError, setIsError] = useState<boolean>(false)\n // const [email, setEmail] = useState('')\n\n const verify = useCallback(async () => {\n setStatus('verifying')\n if (!email || !token) {\n return { error: 'Invalid input' }\n }\n try {\n // I tried using PayloadSDK.request, but when the endpoint\n // returns a not-okay status, PayloadSDK.request returns its\n // own \"Bad request\" error, and doesn't share the endpoint\n // result data.\n const verifyEndpointResult = await fetch(`${serverURL ? serverURL : ''}/api/verifyToken`, {\n body: JSON.stringify({\n email,\n token,\n }),\n method: 'POST',\n })\n\n if (verifyEndpointResult && verifyEndpointResult.json) {\n const resultJson = await verifyEndpointResult.json()\n setIsError(!!resultJson.error)\n setResult(resultJson.error || resultJson.message)\n setStatus(resultJson.error ? 'error' : 'verified')\n // return { error: resultJson.error, message: resultJson.message }\n } else if (verifyEndpointResult && verifyEndpointResult.text) {\n const resultText = await verifyEndpointResult.text()\n setIsError(true)\n setResult(resultText)\n setStatus('error')\n } else {\n setIsError(true)\n setResult(`Error: ${verifyEndpointResult.status}`)\n setStatus('error')\n }\n } catch (error: unknown) {\n setIsError(true)\n setResult(`Error: ${error}`)\n setStatus('error')\n }\n if (!isError) {\n refreshSubscriber()\n }\n }, [email, isError, refreshSubscriber, serverURL, token])\n\n return {\n isError,\n isLoading: !result,\n result: result || '',\n status,\n verify,\n }\n}\n"],"names":["useSearchParams","useCallback","useState","useSubscriber","useServerUrl","useVerifyMagicLink","serverURL","refreshSubscriber","searchParams","email","get","token","result","setResult","status","setStatus","isError","setIsError","verify","error","verifyEndpointResult","fetch","body","JSON","stringify","method","json","resultJson","message","text","resultText","isLoading"],"mappings":"AAAA;AAEA,SAASA,eAAe,QAAQ,qBAAoB;AACpD,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAK7C,SAASC,aAAa,QAAQ,oCAAmC;AACjE,SAASC,YAAY,QAAQ,iCAAgC;AAoB7D;;;;;;CAMC,GACD,OAAO,MAAMC,qBAAqB;IAChC,MAAM,EAAEC,SAAS,EAAE,GAAGF;IAEtB,MAAM,EAAEG,iBAAiB,EAAE,GAAGJ;IAE9B,MAAMK,eAAeR;IACrB,MAAMS,QAAQD,aAAaE,GAAG,CAAC;IAC/B,MAAMC,QAAQH,aAAaE,GAAG,CAAC;IAE/B,MAAM,CAACE,QAAQC,UAAU,GAAGX;IAC5B,MAAM,CAACY,QAAQC,UAAU,GAAGb,SAAuB;IACnD,MAAM,CAACc,SAASC,WAAW,GAAGf,SAAkB;IAChD,yCAAyC;IAEzC,MAAMgB,SAASjB,YAAY;QACzBc,UAAU;QACV,IAAI,CAACN,SAAS,CAACE,OAAO;YACpB,OAAO;gBAAEQ,OAAO;YAAgB;QAClC;QACA,IAAI;YACF,0DAA0D;YAC1D,4DAA4D;YAC5D,0DAA0D;YAC1D,eAAe;YACf,MAAMC,uBAAuB,MAAMC,MAAM,GAAGf,YAAYA,YAAY,GAAG,gBAAgB,CAAC,EAAE;gBACxFgB,MAAMC,KAAKC,SAAS,CAAC;oBACnBf;oBACAE;gBACF;gBACAc,QAAQ;YACV;YAEA,IAAIL,wBAAwBA,qBAAqBM,IAAI,EAAE;gBACrD,MAAMC,aAAa,MAAMP,qBAAqBM,IAAI;gBAClDT,WAAW,CAAC,CAACU,WAAWR,KAAK;gBAC7BN,UAAUc,WAAWR,KAAK,IAAIQ,WAAWC,OAAO;gBAChDb,UAAUY,WAAWR,KAAK,GAAG,UAAU;YACvC,kEAAkE;YACpE,OAAO,IAAIC,wBAAwBA,qBAAqBS,IAAI,EAAE;gBAC5D,MAAMC,aAAa,MAAMV,qBAAqBS,IAAI;gBAClDZ,WAAW;gBACXJ,UAAUiB;gBACVf,UAAU;YACZ,OAAO;gBACLE,WAAW;gBACXJ,UAAU,CAAC,OAAO,EAAEO,qBAAqBN,MAAM,EAAE;gBACjDC,UAAU;YACZ;QACF,EAAE,OAAOI,OAAgB;YACvBF,WAAW;YACXJ,UAAU,CAAC,OAAO,EAAEM,OAAO;YAC3BJ,UAAU;QACZ;QACA,IAAI,CAACC,SAAS;YACZT;QACF;IACF,GAAG;QAACE;QAAOO;QAAST;QAAmBD;QAAWK;KAAM;IAExD,OAAO;QACLK;QACAe,WAAW,CAACnB;QACZA,QAAQA,UAAU;QAClBE;QACAI;IACF;AACF,EAAC"}
@@ -3,7 +3,7 @@ const getServerSideURL = ()=>{
3
3
  const serverSideURL = process.env.NEXT_PUBLIC_VERCEL_URL ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}` : process.env.VERCEL_PROJECT_PRODUCTION_URL ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}` : process.env.NEXT_PUBLIC_DEV_URL ? `http://${process.env.NEXT_PUBLIC_DEV_URL}` : 'http://localhost:3000';
4
4
  // console.log(`process.env.NEXT_PUBLIC_DEV_URL: ${process.env.NEXT_PUBLIC_DEV_URL}`)
5
5
  // console.log(`serverSideURL: ${serverSideURL}`)
6
- return serverSideURL;
6
+ return serverSideURL || '';
7
7
  };
8
8
  // const canUseDOM = !!(
9
9
  // typeof window !== 'undefined' &&
@@ -22,6 +22,7 @@ const getServerSideURL = ()=>{
22
22
  // }
23
23
  // return getServerSideURL()
24
24
  // }
25
+ // eslint-disable-next-line @typescript-eslint/require-await
25
26
  export const getServerUrl = async ()=>{
26
27
  return {
27
28
  serverURL: getServerSideURL()
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server-functions/serverUrl.ts"],"sourcesContent":["'use server'\n\nconst getServerSideURL = () => {\n const serverSideURL = process.env.NEXT_PUBLIC_VERCEL_URL\n ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`\n : process.env.VERCEL_PROJECT_PRODUCTION_URL\n ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`\n : process.env.NEXT_PUBLIC_DEV_URL\n ? `http://${process.env.NEXT_PUBLIC_DEV_URL}`\n : 'http://localhost:3000'\n // console.log(`process.env.NEXT_PUBLIC_DEV_URL: ${process.env.NEXT_PUBLIC_DEV_URL}`)\n // console.log(`serverSideURL: ${serverSideURL}`)\n return serverSideURL\n}\n\n// const canUseDOM = !!(\n// typeof window !== 'undefined' &&\n// window.document &&\n// window.document.createElement\n// )\n\n// const getClientSideURL = () => {\n// if (canUseDOM) {\n// const protocol = window.location.protocol\n// const domain = window.location.hostname\n// const port = window.location.port\n// // `${window.location.protocol}//${window.location.host}\n// const clientSideURL = `${protocol}//${domain}${port ? `:${port}` : ''}`\n// // console.log(`clientSideURL: ${clientSideURL}`)\n// return clientSideURL\n// }\n\n// return getServerSideURL()\n// }\n\nexport const getServerUrl = async (): Promise<{ serverURL: string }> => {\n return { serverURL: getServerSideURL() }\n}\n"],"names":["getServerSideURL","serverSideURL","process","env","NEXT_PUBLIC_VERCEL_URL","VERCEL_PROJECT_PRODUCTION_URL","NEXT_PUBLIC_DEV_URL","getServerUrl","serverURL"],"mappings":"AAAA;AAEA,MAAMA,mBAAmB;IACvB,MAAMC,gBAAgBC,QAAQC,GAAG,CAACC,sBAAsB,GACpD,CAAC,QAAQ,EAAEF,QAAQC,GAAG,CAACC,sBAAsB,EAAE,GAC/CF,QAAQC,GAAG,CAACE,6BAA6B,GACvC,CAAC,QAAQ,EAAEH,QAAQC,GAAG,CAACE,6BAA6B,EAAE,GACtDH,QAAQC,GAAG,CAACG,mBAAmB,GAC7B,CAAC,OAAO,EAAEJ,QAAQC,GAAG,CAACG,mBAAmB,EAAE,GAC3C;IACR,qFAAqF;IACrF,iDAAiD;IACjD,OAAOL;AACT;AAEA,wBAAwB;AACxB,qCAAqC;AACrC,uBAAuB;AACvB,kCAAkC;AAClC,IAAI;AAEJ,mCAAmC;AACnC,qBAAqB;AACrB,gDAAgD;AAChD,8CAA8C;AAC9C,wCAAwC;AACxC,+DAA+D;AAC/D,8EAA8E;AAC9E,wDAAwD;AACxD,2BAA2B;AAC3B,MAAM;AAEN,8BAA8B;AAC9B,IAAI;AAEJ,OAAO,MAAMM,eAAe;IAC1B,OAAO;QAAEC,WAAWR;IAAmB;AACzC,EAAC"}
1
+ {"version":3,"sources":["../../src/server-functions/serverUrl.ts"],"sourcesContent":["'use server'\n\nconst getServerSideURL = () => {\n const serverSideURL = process.env.NEXT_PUBLIC_VERCEL_URL\n ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`\n : process.env.VERCEL_PROJECT_PRODUCTION_URL\n ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`\n : process.env.NEXT_PUBLIC_DEV_URL\n ? `http://${process.env.NEXT_PUBLIC_DEV_URL}`\n : 'http://localhost:3000'\n // console.log(`process.env.NEXT_PUBLIC_DEV_URL: ${process.env.NEXT_PUBLIC_DEV_URL}`)\n // console.log(`serverSideURL: ${serverSideURL}`)\n return serverSideURL || ''\n}\n\n// const canUseDOM = !!(\n// typeof window !== 'undefined' &&\n// window.document &&\n// window.document.createElement\n// )\n\n// const getClientSideURL = () => {\n// if (canUseDOM) {\n// const protocol = window.location.protocol\n// const domain = window.location.hostname\n// const port = window.location.port\n// // `${window.location.protocol}//${window.location.host}\n// const clientSideURL = `${protocol}//${domain}${port ? `:${port}` : ''}`\n// // console.log(`clientSideURL: ${clientSideURL}`)\n// return clientSideURL\n// }\n\n// return getServerSideURL()\n// }\n\n// eslint-disable-next-line @typescript-eslint/require-await\nexport const getServerUrl = async (): Promise<{ serverURL: string }> => {\n return { serverURL: getServerSideURL() }\n}\n"],"names":["getServerSideURL","serverSideURL","process","env","NEXT_PUBLIC_VERCEL_URL","VERCEL_PROJECT_PRODUCTION_URL","NEXT_PUBLIC_DEV_URL","getServerUrl","serverURL"],"mappings":"AAAA;AAEA,MAAMA,mBAAmB;IACvB,MAAMC,gBAAgBC,QAAQC,GAAG,CAACC,sBAAsB,GACpD,CAAC,QAAQ,EAAEF,QAAQC,GAAG,CAACC,sBAAsB,EAAE,GAC/CF,QAAQC,GAAG,CAACE,6BAA6B,GACvC,CAAC,QAAQ,EAAEH,QAAQC,GAAG,CAACE,6BAA6B,EAAE,GACtDH,QAAQC,GAAG,CAACG,mBAAmB,GAC7B,CAAC,OAAO,EAAEJ,QAAQC,GAAG,CAACG,mBAAmB,EAAE,GAC3C;IACR,qFAAqF;IACrF,iDAAiD;IACjD,OAAOL,iBAAiB;AAC1B;AAEA,wBAAwB;AACxB,qCAAqC;AACrC,uBAAuB;AACvB,kCAAkC;AAClC,IAAI;AAEJ,mCAAmC;AACnC,qBAAqB;AACrB,gDAAgD;AAChD,8CAA8C;AAC9C,wCAAwC;AACxC,+DAA+D;AAC/D,8EAA8E;AAC9E,wDAAwD;AACxD,2BAA2B;AAC3B,MAAM;AAEN,8BAA8B;AAC9B,IAAI;AAEJ,4DAA4D;AAC5D,OAAO,MAAMM,eAAe;IAC1B,OAAO;QAAEC,WAAWR;IAAmB;AACzC,EAAC"}
package/package.json CHANGED
@@ -69,7 +69,7 @@
69
69
  },
70
70
  "registry": "https://registry.npmjs.org/",
71
71
  "dependencies": {},
72
- "version": "0.0.15",
72
+ "version": "0.0.17",
73
73
  "scripts": {
74
74
  "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
75
75
  "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",