payload-subscribers-plugin 0.0.16 → 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/dist/endpoints/logout.d.ts +3 -1
- package/dist/endpoints/logout.js +13 -30
- package/dist/endpoints/logout.js.map +1 -1
- package/dist/endpoints/verifyMagicLink.js +53 -15
- package/dist/endpoints/verifyMagicLink.js.map +1 -1
- package/dist/hooks/useVerifyMagicLink.d.ts +3 -0
- package/dist/hooks/useVerifyMagicLink.js +11 -4
- package/dist/hooks/useVerifyMagicLink.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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)
|
package/dist/endpoints/logout.js
CHANGED
|
@@ -1,49 +1,32 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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();
|
|
13
15
|
const collectionLogoutEndpoint = `${req.payload.config.serverURL}/api/${subscribersCollectionSlug}/logout`;
|
|
14
16
|
try {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const cookies = await nextCookies();
|
|
18
|
+
cookies.set('payload-token', '', {
|
|
19
|
+
expires: new Date(0)
|
|
18
20
|
});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return Response.json({
|
|
22
|
-
message: logoutResultData.message,
|
|
23
|
-
now: new Date().toISOString()
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
if (logoutResult.status == 400 && logoutResultData.errors?.map((e)=>e.message).includes('No User')) {
|
|
27
|
-
return Response.json({
|
|
28
|
-
error: `Logout failed: 'No User'`,
|
|
29
|
-
now: new Date().toISOString()
|
|
30
|
-
}, {
|
|
31
|
-
status: 400
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
return Response.json({
|
|
35
|
-
error: `Logout failed: ${logoutResultData.errors ? logoutResultData.errors?.map((e)=>e.message).join(' // ') : JSON.stringify(logoutResultData)}`,
|
|
21
|
+
return NextResponse.json({
|
|
22
|
+
message: 'Logged out',
|
|
36
23
|
now: new Date().toISOString()
|
|
37
|
-
}, {
|
|
38
|
-
status: 400
|
|
39
24
|
});
|
|
40
25
|
} catch (error) {
|
|
26
|
+
req.payload.logger.error(`logoutHandler error: ${JSON.stringify(error, undefined, 2)}`);
|
|
41
27
|
// throw new Error(`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
now: new Date().toISOString()
|
|
45
|
-
}, {
|
|
46
|
-
status: 400
|
|
28
|
+
throw new Error(`Logout failed: ${collectionLogoutEndpoint} : ${JSON.stringify(error, undefined, 2)}`, {
|
|
29
|
+
cause: error
|
|
47
30
|
});
|
|
48
31
|
}
|
|
49
32
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/logout.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\n\nimport {
|
|
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
|
-
//
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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":"AAGA,SAASA,qBAAqB,QAAQ,gCAA+B;AACrE,SAASC,OAAO,EAAEC,eAAe,QAAQ,sBAAqB;AAY9D;;;;;;;CAOC,GACD,SAASC,8BAA8B,EACrCC,4BAA4BJ,qBAAqB,EAGlD;IACC;;;;;;GAMC,GACD,MAAMK,yBAAyC,OAAOC;QACpD,MAAMC,UAAUD,KAAKE,OAAO,MAAMF,IAAIE,IAAI,KAAK,CAAC;QAChD,MAAM,EAAEC,KAAK,EAAEC,KAAK,EAAE,GAAqCH,QAAQ,qBAAqB;;QACxF,yDAAyD;QAEzD,IAAI,CAACE,SAAS,CAACC,OAAO;YACpB,OAAOC,SAASH,IAAI,CAClB;gBAAEI,OAAO;gBAAYC,KAAK,IAAIC,OAAOC,WAAW;YAAG,GACnD;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAMC,cAAc,MAAMX,IAAIY,OAAO,CAACC,IAAI,CAAC;YACzCC,YAAYhB;YACZiB,OAAO;gBACLZ,OAAO;oBAAEa,QAAQb;gBAAM;YACzB;QACF;QAOA,MAAMc,OAAON,YAAYO,IAAI,CAAC,EAAE;QAEhC,IAAI,CAACD,MAAM;YACT,OAAOZ,SAASH,IAAI,CAClB;gBAAEI,OAAO;gBAAYC,KAAK,IAAIC,OAAOC,WAAW;YAAG,GACnD;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAM,EAAES,SAAS,EAAE,GAAGxB,QAAQS;QAE9B,2BAA2B;QAC3B,wHAAwH;QACxH,IAAI;QACJ,IAAI,CAACa,KAAKG,wBAAwB,IAAID,aAAaF,KAAKI,iBAAiB,EAAE;YACzErB,IAAIY,OAAO,CAACU,MAAM,CAACC,IAAI,CAAC,CAAC,oBAAoB,EAAEJ,UAAU,IAAI,EAAEF,KAAKI,iBAAiB,EAAE;YACvF,OAAOhB,SAASH,IAAI,CAClB;gBAAEI,OAAO;gBAAsBC,KAAK,IAAIC,OAAOC,WAAW;YAAG,GAC7D;gBAAEC,QAAQ;YAAI;QAElB;QAEA,IAAI,IAAIF,KAAKA,KAAKD,GAAG,MAAM,IAAIC,KAAKS,KAAKG,wBAAwB,GAAG;YAClE,OAAOf,SAASH,IAAI,CAClB;gBAAEI,OAAO;gBAAiBC,KAAK,IAAIC,OAAOC,WAAW;YAAG,GACxD;gBAAEC,QAAQ;YAAI;QAElB;QAEA,cAAc;QACd,MAAMV,IAAIY,OAAO,CAACY,MAAM,CAAC;YACvBV,YAAYhB;YACZ2B,MAAM;gBACJC,UAAUP;YACZ;YACAJ,OAAO;gBACLZ,OAAO;oBAAEa,QAAQC,KAAKd,KAAK;gBAAC;YAC9B;QACF;QAEA,sCAAsC;QACtC,IAAIwB;QACJ,IAAI;YACF,MAAMC,WAAW,MAAMC,MACrB,GAAG7B,IAAIY,OAAO,CAACkB,MAAM,CAACC,SAAS,CAAC,KAAK,EAAEjC,0BAA0B,MAAM,CAAC,EACxE;gBACEkC,MAAMC,KAAKC,SAAS,CAAC;oBACnB/B;oBACAuB,UAAUP;gBACZ;gBACAgB,aAAa;gBACbR,SAAS;oBACP,gBAAgB;gBAClB;gBACAS,QAAQ;YACV;YAEF,IAAIR,YAAYA,SAASS,EAAE,EAAE;gBAC3BV,UAAUC,SAASD,OAAO;YAC5B;QACF,EAAE,OAAOrB,OAAO;YACd,qBAAqB;YACrB,OAAOD,SAASH,IAAI,CAAC;gBAAEI;YAAM,GAA8B;gBAAEI,QAAQ;YAAI;QAC3E;QACA,gCAAgC;QAEhC,MAAMA,SACJO,MAAMP,UAAU,YAAY,eAAeO,MAAMP;QAEnD,MAAM,EAAES,WAAWmB,UAAU,EAAE,GAAG1C,kBAAkB,aAAa;;QACjE,MAAM6B,OAAO;YACXC,UAAUY;YACV5B;YACAW,mBAAmB;YACnBD,0BAA0B;QAC5B;QACA,cAAc;QACd,MAAMpB,IAAIY,OAAO,CAACY,MAAM,CAAC;YACvBV,YAAYhB;YACZ2B;YACAV,OAAO;gBACLZ,OAAO;oBAAEa,QAAQC,KAAKd,KAAK;gBAAC;YAC9B;QACF;QAEA,OAAOE,SAASH,IAAI,CAClB;YACEqC,SAAS;YACThC,KAAK,IAAIC,OAAOC,WAAW;QAC7B,GACA;YAAEkB;QAAQ;IAEd;IAEA,yFAAyF,GACzF,MAAMa,0BAAoC;QACxCC,SAAS1C;QACTqC,QAAQ;QACRM,MAAM;IACR;IAEA,OAAOF;AACT;AAEA,eAAe3C,8BAA6B"}
|
|
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
|
|
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"}
|
package/package.json
CHANGED
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
"registry": "https://registry.npmjs.org/",
|
|
71
71
|
"dependencies": {},
|
|
72
|
-
"version": "0.0.
|
|
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",
|