ソースを参照

浏览器端cookie管理工具函数修改

fogwind 1 週間 前
コミット
c784a0e29b

+ 1 - 1
src/components/cart/OrderDetail.tsx

@@ -1,7 +1,7 @@
 "use client";
 
 import { ORDER_ID } from "@/utils/constants";
-import { getCookie } from "@utils/getCartToken";
+import { getCookie } from "@utils/cookie-tools";
 import { useEffect, useState } from "react";
 
 export default function OrderDetail() {

+ 8 - 3
src/components/customer/LoginForm.tsx

@@ -11,8 +11,8 @@ import { EMAIL_REGEX, SIGNIN_IMG } from "@/utils/constants";
 import InputText from "@components/common/form/Input";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import { useMergeCart } from "@utils/hooks/useMergeCart";
-import { getCookie } from "@utils/getCartToken";
-import { setCookie } from "@utils/helper";
+
+import { getCookie, setCookie } from "@/utils/cookie-tools";
 import { setLocalStorage } from "@/store/local-storage";
 import { useAppDispatch } from "@/store/hooks";
 import { setUser } from "@/store/slices/user-slice";
@@ -46,11 +46,16 @@ export default function LoginForm() {
       const guestCartId = getCookie(GUEST_CART_ID);
       const guestCartToken = getCookie(GUEST_CART_TOKEN);
 
+      /**
+       * @todo 使用 signInAuth 重写
+       * result: {error: null,ok: true,status: 200,url: "http://localhost:3001/"}
+       */
       const result = await signIn("credentials", {
         redirect: false,
         ...data,
-        callbackUrl: "/",
+        callbackUrl: "/", // The callbackUrl specifies to which URL the user will be redirected after signing in. Defaults to the page URL the sign-in is initiated from.
       });
+      console.log('result --- ',result);
 
       if (!result?.ok) {
         showToast(result?.error || "Invalid login credentials.", "warning");

+ 131 - 0
src/utils/cookie-tools.ts

@@ -0,0 +1,131 @@
+/*const COOKIE_EXPIRES_DAYS = 7;
+
+export const getNativeCookie = (name: string): string | null => {
+  if (typeof document === "undefined") return null;
+
+  const cookies = document.cookie.split("; ").map((c) => c.trim());
+  const found = cookies.find((c) => c.startsWith(name + "="));
+  return found ? decodeURIComponent(found.split("=")[1]) : null;
+};
+
+export const getCookie = (name: string): string | null => {
+  if (typeof document === "undefined") return null;
+
+  const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));
+  return match ? decodeURIComponent(match[2]) : null;
+};
+
+export const setCookie = (
+    name: string, 
+    value: string | number, 
+    { days = COOKIE_EXPIRES_DAYS, isEncode = false }: { days?: number; isEncode?: boolean } = {}
+) => {
+  if (typeof document === "undefined") return;
+  const d = new Date();
+  d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
+  // const expires = new Date(Date.now() + days * 864e5).toUTCString();
+  const expires = d.toUTCString();
+  const val = isEncode ? encodeURIComponent(String(value)) : value;
+  document.cookie = `${name}=${val}; expires=${expires}; path=/`;
+};
+
+export const deleteCookie = (name: string) => {
+  if (typeof document === "undefined") return;
+  document.cookie = `${name}=; Max-Age=0; path=/`;
+};
+*/
+// cookie.ts
+
+const DEFAULT_EXPIRES_DAYS = 7;
+
+export interface CookieOptions {
+  /** 过期天数(从当前时间起算),默认 7 天 */
+  days?: number;
+  /** 是否需要对值进行编码(encodeURIComponent),默认 false */
+  encode?: boolean;
+  /** Cookie 路径,默认 '/' */
+  path?: string;
+  /** Cookie 域名 */
+  domain?: string;
+  /** 是否仅 HTTPS 传输 */
+  secure?: boolean;
+  /** SameSite 属性 */
+  sameSite?: 'Strict' | 'Lax' | 'None';
+}
+
+// 对正则特殊字符进行转义,避免名称中的特殊字符导致匹配错误
+function escapeRegExp(string: string): string {
+  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
+
+/**
+ * 获取指定名称的 cookie 值
+ * 解决了值中包含等号的问题,并对名称中的正则特殊字符做了转义
+ */
+export function getCookie(name: string): string | null {
+  if (typeof document === 'undefined') return null;
+
+  const escapedName = escapeRegExp(name);
+  // 在 document.cookie 开头或分号后的空格之后匹配名称,值部分匹配到下一个分号或结束
+  const regex = new RegExp(`(?:^|;\\s*)${escapedName}=([^;]*)`);
+  const match = document.cookie.match(regex);
+  
+  return match ? decodeURIComponent(match[1]) : null;
+}
+
+/**
+ * 设置 cookie
+ * 默认对值进行编码以保证安全存储,可通过 options.encode = false 关闭
+ */
+export function setCookie(
+  name: string,
+  value: string | number | boolean,
+  options: CookieOptions = {}
+): void {
+  if (typeof document === 'undefined') return;
+
+  const {
+    days = DEFAULT_EXPIRES_DAYS,
+    encode = true,          // 默认编码,更加安全
+    path = '/',
+    domain,
+    secure,
+    sameSite,
+  } = options;
+
+  // 处理过期时间
+  const expires = new Date(Date.now() + days * 864e5).toUTCString();
+
+  // 处理值:编码或直接转换
+  const val = encode
+    ? encodeURIComponent(String(value))
+    : String(value);
+
+  // 构建 cookie 字符串
+  let cookie = `${name}=${val}; expires=${expires}; path=${path}`;
+  if (domain) cookie += `; domain=${domain}`;
+  if (secure) cookie += '; secure';
+  if (sameSite) cookie += `; samesite=${sameSite}`;
+
+  document.cookie = cookie;
+}
+
+/**
+ * 删除指定名称的 cookie
+ * 通过设置 Max-Age=0 并匹配 path/domain 等属性来确保删除
+ */
+export function deleteCookie(
+  name: string,
+  options: Pick<CookieOptions, 'path' | 'domain' | 'secure'> = {}
+): void {
+  if (typeof document === 'undefined') return;
+
+  const { path = '/', domain, secure } = options;
+
+  // 若删除时指定了 domain 或 secure,需要与设置时一致才能删除成功
+  let cookie = `${name}=; Max-Age=0; path=${path}`;
+  if (domain) cookie += `; domain=${domain}`;
+  if (secure) cookie += '; secure';
+
+  document.cookie = cookie;
+}

+ 4 - 30
src/utils/getCartToken.ts

@@ -1,39 +1,13 @@
 import { GUEST_CART_TOKEN, IS_GUEST } from "@/utils/constants";
 import { decodeJWT } from "@/utils/jwt-cookie";
-
-export const getNativeCookie = (name: string): string | null => {
-  if (typeof document === "undefined") return null;
-
-  const cookies = document.cookie.split("; ").map((c) => c.trim());
-  const found = cookies.find((c) => c.startsWith(name + "="));
-  return found ? decodeURIComponent(found.split("=")[1]) : null;
-};
-
+import { getCookie } from "@/utils/cookie-tools";
+// 管理游客的token
 export const getCartToken = (): string | null => {
-  const raw = getNativeCookie(GUEST_CART_TOKEN);
+  const raw = getCookie(GUEST_CART_TOKEN);
   if (!raw) return null;
 
-  const isGuest = getNativeCookie(IS_GUEST) !== "false";
+  const isGuest = getCookie(IS_GUEST) !== "false";
 
   const decoded = decodeJWT<{ sessionToken: string }>(raw, isGuest);
   return decoded?.sessionToken ?? null;
 };
-
-//  fetch any cookie data
-export const getCookie = (name: string): string | null => {
-  if (typeof document === "undefined") return null;
-
-  const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));
-  return match ? decodeURIComponent(match[2]) : null;
-};
-
-export const setCookie = (name: string, value: string, days = 7) => {
-  if (typeof document === "undefined") return;
-  const expires = new Date(Date.now() + days * 864e5).toUTCString();
-  document.cookie = `${name}=${value}; expires=${expires}; path=/`;
-};
-
-export const deleteCookie = (name: string) => {
-  if (typeof document === "undefined") return;
-  document.cookie = `${name}=; Max-Age=0; path=/`;
-};

+ 0 - 9
src/utils/helper.ts

@@ -303,15 +303,6 @@ export default function safeArray<T = any>(value: T[] | null | undefined): T[] {
   return Array.isArray(value) ? value : [];
 }
 
-export const setCookie = (name: string, value: string | number, days = 30) => {
-  if (typeof window === "undefined" || !name) return;
-  const d = new Date();
-  d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
-  const expires = "expires=" + d.toUTCString();
-  document.cookie = `${name}=${encodeURIComponent(
-    String(value),
-  )};${expires};path=/`;
-};
 
 export const getValidTitle = (text: string) => {
   return text?.toLowerCase()?.replaceAll("_", " ") ?? "";

+ 3 - 2
src/utils/hooks/useCheckout.ts

@@ -5,8 +5,9 @@ import { useDispatch } from "react-redux";
 import {
   clearCart,
 } from "@/store/slices/cart-slice";
-import { getCartToken, getCookie } from "@utils/getCartToken";
-import { setCookie } from "@utils/helper";
+import { getCartToken } from "@utils/getCartToken";
+
+import { getCookie, setCookie } from "@/utils/cookie-tools";
 import { useGuestCartToken } from "./useGuestCartToken";
 import { ORDER_ID, IS_GUEST } from "@utils/constants";
 import { useCartDetail } from "./useCartDetail";

+ 6 - 6
src/utils/hooks/useGuestCartToken.ts

@@ -4,7 +4,7 @@ import { useEffect, useState, useRef } from "react";
 import { fetchHandler } from "../fetch-handler";
 import { GUEST_CART_ID, GUEST_CART_TOKEN, IS_GUEST } from "@/utils/constants";
 import { encodeJWT, decodeJWT } from "@/utils/jwt-cookie";
-import { setCookie, deleteCookie, getNativeCookie } from "../getCartToken";
+import { setCookie, deleteCookie, getCookie } from "@/utils/cookie-tools";
 
 
 
@@ -26,9 +26,9 @@ export const useGuestCartToken = () => {
     tokenPromiseRef.current = (async () => {
       if (tokenCreatedRef.current) {
         // Return existing raw token from cookie
-        const cookieVal = getNativeCookie(GUEST_CART_TOKEN);
+        const cookieVal = getCookie(GUEST_CART_TOKEN);
         if (cookieVal) {
-          const isGuest = getNativeCookie(IS_GUEST) !== "false";
+          const isGuest = getCookie(IS_GUEST) !== "false";
           const decoded = decodeJWT<{ sessionToken: string }>(cookieVal, isGuest);
           return decoded?.sessionToken ?? null;
         }
@@ -57,7 +57,7 @@ export const useGuestCartToken = () => {
         });
         const newCartId = Number(cart.id);
 
-        setCookie(GUEST_CART_TOKEN, newToken);
+        setCookie(GUEST_CART_TOKEN, newToken,{encode: false});
         setCookie(GUEST_CART_ID, String(newCartId));
         setCookie(IS_GUEST, String(cart?.isGuest));
 
@@ -95,10 +95,10 @@ export const useGuestCartToken = () => {
   };
 
   useEffect(() => {
-    const cookieToken = getNativeCookie(GUEST_CART_TOKEN);
+    const cookieToken = getCookie(GUEST_CART_TOKEN);
 
     if (cookieToken) {
-      const isGuest = getNativeCookie(IS_GUEST) !== "false";
+      const isGuest = getCookie(IS_GUEST) !== "false";
       const decoded = decodeJWT<{
         sessionToken: string;
         cartId: number;

+ 3 - 10
src/utils/hooks/useMergeCart.ts

@@ -4,7 +4,8 @@ import { useMutation } from "@apollo/client";
 import { useAppDispatch } from "@/store/hooks";
 import { addItem } from "@/store/slices/cart-slice";
 import { CREATE_MERGE_CART } from "@/graphql";
-
+import { GUEST_CART_ID } from "@/utils/constants";
+import { setCookie } from "@utils/cookie-tools";
 
 export function useMergeCart() {
   const dispatch = useAppDispatch();
@@ -17,16 +18,8 @@ export function useMergeCart() {
       }
        const cartId = responseData?.id ?? null;
 
-      const setCookie = (name: string, value: string | number, days = 30) => {
-        if (typeof window === "undefined" || !name) return;
-        const d = new Date();
-        d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
-        const expires = "expires=" + d.toUTCString();
-        document.cookie = `${name}=${encodeURIComponent(String(value))};${expires};path=/`;
-      };
-
       if (cartId !== null && typeof cartId !== "undefined") {
-        setCookie("guest_cart_id", String(cartId));
+        setCookie(GUEST_CART_ID, String(cartId));
       }
 
       dispatch(addItem(responseData));