@apptimate/core-lib 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -7
- package/package.json +16 -16
- package/src/client.ts +10 -0
- package/src/common/interfaces/ICommon.ts +6 -6
- package/src/constants/iconRegistry.ts +189 -0
- package/src/constants/menus.ts +576 -0
- package/src/constants/storageKeys.ts +14 -9
- package/src/index.ts +3 -8
- package/src/server.ts +12 -0
- package/src/utils/apiProxy.ts +155 -0
- package/src/utils/bem.ts +13 -13
- package/src/utils/bootstrapConfig.ts +80 -0
- package/src/utils/cn.ts +9 -9
- package/src/utils/commonService.ts +46 -26
- package/src/utils/cookiesHandler.ts +64 -64
- package/src/utils/httpClient.ts +192 -161
- package/src/utils/localStorageHandler.ts +59 -49
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { cookie } from "../constants/storageKeys";
|
|
2
|
+
import { decode } from "./commonService";
|
|
3
|
+
|
|
4
|
+
const HOP_BY_HOP_HEADERS = new Set([
|
|
5
|
+
"connection",
|
|
6
|
+
"content-length",
|
|
7
|
+
"content-encoding",
|
|
8
|
+
"keep-alive",
|
|
9
|
+
"proxy-authenticate",
|
|
10
|
+
"proxy-authorization",
|
|
11
|
+
"te",
|
|
12
|
+
"trailer",
|
|
13
|
+
"transfer-encoding",
|
|
14
|
+
"upgrade",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function parseCookieHeader(header: string | null): Record<string, string> {
|
|
18
|
+
if (!header) {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return header.split(";").reduce<Record<string, string>>((acc, part) => {
|
|
23
|
+
const [rawName, ...valueParts] = part.trim().split("=");
|
|
24
|
+
if (!rawName) {
|
|
25
|
+
return acc;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
acc[rawName] = decodeURIComponent(valueParts.join("=") || "");
|
|
29
|
+
return acc;
|
|
30
|
+
}, {});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getCookieValue(cookies: Record<string, string>, name: string): string | null {
|
|
34
|
+
return cookies[name] ?? null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getAccessToken(cookies: Record<string, string>): string | null {
|
|
38
|
+
const cookieNames = [
|
|
39
|
+
cookie.access_token.encrypted ? cookie.access_token.secretName : cookie.access_token.name,
|
|
40
|
+
cookie.access_token.name,
|
|
41
|
+
cookie.access_token.secretName,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
for (const name of cookieNames) {
|
|
45
|
+
const value = getCookieValue(cookies, name);
|
|
46
|
+
if (!value) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return cookie.access_token.encrypted ? decode(value) : value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildCookieHeader(name: string, options?: { httpOnly?: boolean }): string {
|
|
57
|
+
const parts = [
|
|
58
|
+
`${name}=`,
|
|
59
|
+
"Path=/",
|
|
60
|
+
"Max-Age=0",
|
|
61
|
+
"Expires=Thu, 01 Jan 1970 00:00:00 GMT",
|
|
62
|
+
"SameSite=Lax",
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
if (options?.httpOnly) {
|
|
66
|
+
parts.push("HttpOnly");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (process.env.NODE_ENV === "production") {
|
|
70
|
+
parts.push("Secure");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return parts.join("; ");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function createSessionLogoutResponse(): Response {
|
|
77
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
78
|
+
headers.append("set-cookie", buildCookieHeader(cookie.access_token.name, { httpOnly: true }));
|
|
79
|
+
headers.append("set-cookie", buildCookieHeader(cookie.access_token.secretName, { httpOnly: true }));
|
|
80
|
+
headers.append("set-cookie", buildCookieHeader("selected_organization_id"));
|
|
81
|
+
|
|
82
|
+
return new Response(JSON.stringify({ cleared: true }), {
|
|
83
|
+
status: 200,
|
|
84
|
+
headers,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function forwardApiProxyRequest(request: Request, pathSegments: string[]): Promise<Response> {
|
|
89
|
+
const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL;
|
|
90
|
+
|
|
91
|
+
if (!apiBaseUrl) {
|
|
92
|
+
return new Response(JSON.stringify({
|
|
93
|
+
is_success: false,
|
|
94
|
+
message: "NEXT_PUBLIC_API_URL is not configured.",
|
|
95
|
+
result: null,
|
|
96
|
+
system_code: "api_proxy_not_configured",
|
|
97
|
+
}), {
|
|
98
|
+
status: 500,
|
|
99
|
+
headers: { "content-type": "application/json" },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const incomingUrl = new URL(request.url);
|
|
104
|
+
const upstreamPath = pathSegments.join("/");
|
|
105
|
+
const upstreamUrl = new URL(upstreamPath, `${apiBaseUrl.replace(/\/$/, "")}/`);
|
|
106
|
+
upstreamUrl.search = incomingUrl.search;
|
|
107
|
+
|
|
108
|
+
const cookies = parseCookieHeader(request.headers.get("cookie"));
|
|
109
|
+
const token = getAccessToken(cookies);
|
|
110
|
+
const selectedOrganizationId = getCookieValue(cookies, "selected_organization_id");
|
|
111
|
+
|
|
112
|
+
const headers = new Headers();
|
|
113
|
+
const accept = request.headers.get("accept");
|
|
114
|
+
const contentType = request.headers.get("content-type");
|
|
115
|
+
const organizationId = request.headers.get("x-organization-id") ?? selectedOrganizationId;
|
|
116
|
+
|
|
117
|
+
if (accept) {
|
|
118
|
+
headers.set("accept", accept);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (contentType) {
|
|
122
|
+
headers.set("content-type", contentType);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (organizationId) {
|
|
126
|
+
headers.set("x-organization-id", organizationId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (token) {
|
|
130
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const hasBody = !["GET", "HEAD"].includes(request.method.toUpperCase());
|
|
134
|
+
const body = hasBody ? await request.arrayBuffer() : undefined;
|
|
135
|
+
|
|
136
|
+
const upstreamResponse = await fetch(upstreamUrl.toString(), {
|
|
137
|
+
method: request.method,
|
|
138
|
+
headers,
|
|
139
|
+
body,
|
|
140
|
+
cache: "no-store",
|
|
141
|
+
redirect: "manual",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const responseHeaders = new Headers();
|
|
145
|
+
upstreamResponse.headers.forEach((value, key) => {
|
|
146
|
+
if (!HOP_BY_HOP_HEADERS.has(key.toLowerCase())) {
|
|
147
|
+
responseHeaders.set(key, value);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return new Response(upstreamResponse.body, {
|
|
152
|
+
status: upstreamResponse.status,
|
|
153
|
+
headers: responseHeaders,
|
|
154
|
+
});
|
|
155
|
+
}
|
package/src/utils/bem.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export function block(prefix: string, name: string): string {
|
|
2
|
-
return `${prefix}-${name}`;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function element(prefix: string, blockName: string, elementName: string): string {
|
|
6
|
-
return `${prefix}-${blockName}__${elementName}`;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function modifier(prefix: string, name: string, modifier: string | undefined | null | boolean): string {
|
|
10
|
-
if (!modifier) return "";
|
|
11
|
-
if (typeof modifier === "boolean") return modifier ? `${prefix}-${name}--active` : "";
|
|
12
|
-
return `${prefix}-${name}--${modifier}`;
|
|
13
|
-
}
|
|
1
|
+
export function block(prefix: string, name: string): string {
|
|
2
|
+
return `${prefix}-${name}`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function element(prefix: string, blockName: string, elementName: string): string {
|
|
6
|
+
return `${prefix}-${blockName}__${elementName}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function modifier(prefix: string, name: string, modifier: string | undefined | null | boolean): string {
|
|
10
|
+
if (!modifier) return "";
|
|
11
|
+
if (typeof modifier === "boolean") return modifier ? `${prefix}-${name}--active` : "";
|
|
12
|
+
return `${prefix}-${name}--${modifier}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared bootstrap configuration loader for module layouts.
|
|
3
|
+
* Fetches the public app_config from the API during SSR.
|
|
4
|
+
*
|
|
5
|
+
* This centralizes the config fetch logic so all module layouts
|
|
6
|
+
* share the same implementation instead of duplicating it.
|
|
7
|
+
*/
|
|
8
|
+
export async function fetchAppBootstrapConfig(): Promise<any | null> {
|
|
9
|
+
try {
|
|
10
|
+
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
|
|
11
|
+
if (!apiUrl) return null;
|
|
12
|
+
|
|
13
|
+
const response = await fetch(`${apiUrl}/api/core/config/app_config`, { cache: 'no-store' });
|
|
14
|
+
if (response.ok) {
|
|
15
|
+
const data = await response.json();
|
|
16
|
+
if (data.is_success) {
|
|
17
|
+
return data.result;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error("Failed to fetch app_config:", error);
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Server-side menu config loader.
|
|
28
|
+
*
|
|
29
|
+
* Reads the auth token and selected organization from cookies,
|
|
30
|
+
* then fetches the active menu configuration from the API.
|
|
31
|
+
* Returns the raw config object or null if not available.
|
|
32
|
+
*
|
|
33
|
+
* Usage in server layouts:
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { fetchMenuConfig } from '@apptimate/core-lib/src/utils/bootstrapConfig';
|
|
36
|
+
* const menuConfig = await fetchMenuConfig();
|
|
37
|
+
* <DashboardLayoutClient initialMenuConfig={menuConfig} />
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export async function fetchMenuConfig(): Promise<any | null> {
|
|
41
|
+
try {
|
|
42
|
+
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
|
|
43
|
+
if (!apiUrl) return null;
|
|
44
|
+
|
|
45
|
+
// Dynamic import to avoid bundling next/headers in client code
|
|
46
|
+
const { cookies } = await import('next/headers');
|
|
47
|
+
const cookieStore = await cookies();
|
|
48
|
+
|
|
49
|
+
// Read auth token — cookie name matches storageKeys.ts (encrypted: false → use .name)
|
|
50
|
+
const token = cookieStore.get('access_token')?.value;
|
|
51
|
+
if (!token) return null;
|
|
52
|
+
|
|
53
|
+
// Read selected organization from cookie (set by the client layout)
|
|
54
|
+
const orgCookie = cookieStore.get('selected_organization_id')?.value;
|
|
55
|
+
|
|
56
|
+
const headers: Record<string, string> = {
|
|
57
|
+
'Accept': 'application/json',
|
|
58
|
+
'Authorization': `Bearer ${token}`,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (orgCookie) {
|
|
62
|
+
headers['X-Organization-Id'] = orgCookie;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const response = await fetch(`${apiUrl}/api/menu-config/active`, {
|
|
66
|
+
cache: 'no-store',
|
|
67
|
+
headers,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (response.ok) {
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
if (data.is_success && data.result?.config) {
|
|
73
|
+
return data.result.config;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error("Failed to fetch menu config:", error);
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
package/src/utils/cn.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type ClassValue, clsx } from "clsx";
|
|
2
|
-
import { twMerge } from "tailwind-merge";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Utility function to merge tailwind classes safely.
|
|
6
|
-
*/
|
|
7
|
-
export function cn(...inputs: ClassValue[]) {
|
|
8
|
-
return twMerge(clsx(inputs));
|
|
9
|
-
}
|
|
1
|
+
import { type ClassValue, clsx } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Utility function to merge tailwind classes safely.
|
|
6
|
+
*/
|
|
7
|
+
export function cn(...inputs: ClassValue[]) {
|
|
8
|
+
return twMerge(clsx(inputs));
|
|
9
|
+
}
|
|
@@ -1,26 +1,46 @@
|
|
|
1
|
-
export function replacePlaceholders(template: string, replacements: Record<string, string | number>): string {
|
|
2
|
-
return template.replace(/\${(\w+)}/g, (_, key) => {
|
|
3
|
-
return (replacements[key] ?? `\${${key}}`).toString();
|
|
4
|
-
});
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
export function replacePlaceholders(template: string, replacements: Record<string, string | number>): string {
|
|
2
|
+
return template.replace(/\${(\w+)}/g, (_, key) => {
|
|
3
|
+
return (replacements[key] ?? `\${${key}}`).toString();
|
|
4
|
+
});
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base64 encode a value.
|
|
9
|
+
*
|
|
10
|
+
* WARNING: This is NOT encryption. It is a reversible encoding meant only for
|
|
11
|
+
* transport convenience. Do NOT rely on this for security.
|
|
12
|
+
* Auth tokens should be stored in HttpOnly, Secure, SameSite cookies managed
|
|
13
|
+
* by the server in a production deployment.
|
|
14
|
+
*/
|
|
15
|
+
export function encode(value: any): string {
|
|
16
|
+
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
17
|
+
return btoa(unescape(encodeURIComponent(stringValue)));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Base64 decode a value.
|
|
22
|
+
*
|
|
23
|
+
* WARNING: This is NOT decryption. See the note on `encode()`.
|
|
24
|
+
*/
|
|
25
|
+
export function decode(encodedValue: string): any {
|
|
26
|
+
try {
|
|
27
|
+
const decoded = decodeURIComponent(escape(atob(encodedValue)));
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(decoded);
|
|
30
|
+
} catch {
|
|
31
|
+
return decoded;
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Use `encode()` instead. This function name is misleading — it performs base64 encoding, not encryption.
|
|
40
|
+
*/
|
|
41
|
+
export const encrypt = encode;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @deprecated Use `decode()` instead. This function name is misleading — it performs base64 decoding, not decryption.
|
|
45
|
+
*/
|
|
46
|
+
export const decrypt = decode;
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
'use server';
|
|
2
|
-
|
|
3
|
-
import { cookies } from 'next/headers';
|
|
4
|
-
import { IStorageOptions } from '../constants/storageKeys';
|
|
5
|
-
import { decrypt, encrypt, replacePlaceholders } from './commonService';
|
|
6
|
-
|
|
7
|
-
export async function getCookies<T = any>(storageKey: IStorageOptions, options?: { replacements?: Record<string, string | number> }): Promise<T | null> {
|
|
8
|
-
const cookieStore = await cookies();
|
|
9
|
-
const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
|
|
10
|
-
|
|
11
|
-
const value = cookieStore.get(name)?.value;
|
|
12
|
-
if (!value) return null;
|
|
13
|
-
|
|
14
|
-
if (storageKey.encrypted) {
|
|
15
|
-
return decrypt(value) as T;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
return JSON.parse(value) as T;
|
|
20
|
-
} catch {
|
|
21
|
-
return value as unknown as T;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function setCookies(storageKey: IStorageOptions, value: any, options?: {
|
|
26
|
-
replacements?: Record<string, string | number>;
|
|
27
|
-
expires?: number | Date;
|
|
28
|
-
maxAge?: number;
|
|
29
|
-
domain?: string;
|
|
30
|
-
path?: string;
|
|
31
|
-
secure?: boolean;
|
|
32
|
-
httpOnly?: boolean;
|
|
33
|
-
sameSite?: 'lax' | 'strict' | 'none';
|
|
34
|
-
}): Promise<void> {
|
|
35
|
-
const cookieStore = await cookies();
|
|
36
|
-
const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
|
|
37
|
-
|
|
38
|
-
let finalValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
39
|
-
if (storageKey.encrypted) {
|
|
40
|
-
finalValue = encrypt(value);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
cookieStore.set(name, finalValue, {
|
|
44
|
-
expires: options?.expires,
|
|
45
|
-
maxAge: options?.maxAge,
|
|
46
|
-
domain: options?.domain,
|
|
47
|
-
path: options?.path || '/',
|
|
48
|
-
secure: options?.secure ?? process.env.NODE_ENV === 'production',
|
|
49
|
-
httpOnly: options?.httpOnly ?? true,
|
|
50
|
-
sameSite: options?.sameSite || 'lax',
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function clearCookie(storageKey: IStorageOptions, options?: { replacements?: Record<string, string | number> }): Promise<void> {
|
|
55
|
-
const cookieStore = await cookies();
|
|
56
|
-
const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
|
|
57
|
-
cookieStore.delete(name);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function clearAllCookies(): Promise<void> {
|
|
61
|
-
const cookieStore = await cookies();
|
|
62
|
-
const all = cookieStore.getAll();
|
|
63
|
-
all.forEach(c => cookieStore.delete(c.name));
|
|
64
|
-
}
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { cookies } from 'next/headers';
|
|
4
|
+
import { IStorageOptions } from '../constants/storageKeys';
|
|
5
|
+
import { decrypt, encrypt, replacePlaceholders } from './commonService';
|
|
6
|
+
|
|
7
|
+
export async function getCookies<T = any>(storageKey: IStorageOptions, options?: { replacements?: Record<string, string | number> }): Promise<T | null> {
|
|
8
|
+
const cookieStore = await cookies();
|
|
9
|
+
const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
|
|
10
|
+
|
|
11
|
+
const value = cookieStore.get(name)?.value;
|
|
12
|
+
if (!value) return null;
|
|
13
|
+
|
|
14
|
+
if (storageKey.encrypted) {
|
|
15
|
+
return decrypt(value) as T;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(value) as T;
|
|
20
|
+
} catch {
|
|
21
|
+
return value as unknown as T;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function setCookies(storageKey: IStorageOptions, value: any, options?: {
|
|
26
|
+
replacements?: Record<string, string | number>;
|
|
27
|
+
expires?: number | Date;
|
|
28
|
+
maxAge?: number;
|
|
29
|
+
domain?: string;
|
|
30
|
+
path?: string;
|
|
31
|
+
secure?: boolean;
|
|
32
|
+
httpOnly?: boolean;
|
|
33
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
34
|
+
}): Promise<void> {
|
|
35
|
+
const cookieStore = await cookies();
|
|
36
|
+
const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
|
|
37
|
+
|
|
38
|
+
let finalValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
39
|
+
if (storageKey.encrypted) {
|
|
40
|
+
finalValue = encrypt(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
cookieStore.set(name, finalValue, {
|
|
44
|
+
expires: options?.expires,
|
|
45
|
+
maxAge: options?.maxAge,
|
|
46
|
+
domain: options?.domain,
|
|
47
|
+
path: options?.path || '/',
|
|
48
|
+
secure: options?.secure ?? process.env.NODE_ENV === 'production',
|
|
49
|
+
httpOnly: options?.httpOnly ?? true,
|
|
50
|
+
sameSite: options?.sameSite || 'lax',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function clearCookie(storageKey: IStorageOptions, options?: { replacements?: Record<string, string | number> }): Promise<void> {
|
|
55
|
+
const cookieStore = await cookies();
|
|
56
|
+
const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
|
|
57
|
+
cookieStore.delete(name);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function clearAllCookies(): Promise<void> {
|
|
61
|
+
const cookieStore = await cookies();
|
|
62
|
+
const all = cookieStore.getAll();
|
|
63
|
+
all.forEach(c => cookieStore.delete(c.name));
|
|
64
|
+
}
|