@cloudwerk/security 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Squirrelsoft Dev Tools
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,207 @@
1
+ import { S as SecureFetchOptions } from './types-DtbmXdeT.js';
2
+
3
+ /**
4
+ * @cloudwerk/security - Client-Side CSRF Helpers
5
+ *
6
+ * Utilities for working with CSRF tokens in browser code.
7
+ */
8
+ /**
9
+ * Get the CSRF token from cookies.
10
+ *
11
+ * Reads the CSRF token cookie set by the server.
12
+ *
13
+ * @param cookieName - Name of the CSRF cookie
14
+ * @returns The CSRF token or null if not found
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { getCsrfToken } from '@cloudwerk/security/client'
19
+ *
20
+ * const token = getCsrfToken()
21
+ * if (token) {
22
+ * // Include token in your request
23
+ * fetch('/api/users', {
24
+ * method: 'POST',
25
+ * headers: {
26
+ * 'X-CSRF-Token': token,
27
+ * },
28
+ * body: JSON.stringify(data),
29
+ * })
30
+ * }
31
+ * ```
32
+ */
33
+ declare function getCsrfToken(cookieName?: string): string | null;
34
+ /**
35
+ * Add CSRF token to a headers object.
36
+ *
37
+ * Creates a new Headers object with the CSRF token added.
38
+ *
39
+ * @param headers - Existing headers to extend (optional)
40
+ * @param options - Configuration options
41
+ * @returns Headers object with CSRF token added
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * import { withCsrfToken } from '@cloudwerk/security/client'
46
+ *
47
+ * const headers = withCsrfToken({
48
+ * 'Content-Type': 'application/json',
49
+ * })
50
+ *
51
+ * fetch('/api/users', {
52
+ * method: 'POST',
53
+ * headers,
54
+ * body: JSON.stringify(data),
55
+ * })
56
+ * ```
57
+ */
58
+ declare function withCsrfToken(headers?: HeadersInit, options?: {
59
+ cookieName?: string;
60
+ headerName?: string;
61
+ }): Headers;
62
+ /**
63
+ * Create a hidden input element with the CSRF token.
64
+ *
65
+ * Useful for including CSRF tokens in traditional form submissions.
66
+ *
67
+ * @param fieldName - Name of the form field
68
+ * @param cookieName - Name of the CSRF cookie
69
+ * @returns HTML input element string or empty string if no token
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * import { csrfInput } from '@cloudwerk/security/client'
74
+ *
75
+ * // In your form template
76
+ * const formHtml = `
77
+ * <form method="POST" action="/submit">
78
+ * ${csrfInput()}
79
+ * <input type="text" name="email" />
80
+ * <button type="submit">Submit</button>
81
+ * </form>
82
+ * `
83
+ * ```
84
+ */
85
+ declare function csrfInput(fieldName?: string, cookieName?: string): string;
86
+
87
+ /**
88
+ * @cloudwerk/security - Secure Fetch Wrapper
89
+ *
90
+ * A fetch wrapper that automatically includes CSRF tokens and
91
+ * X-Requested-With headers for secure AJAX requests.
92
+ */
93
+
94
+ /**
95
+ * Configure secure fetch defaults.
96
+ *
97
+ * Set global configuration options that apply to all secureFetch calls.
98
+ *
99
+ * @param options - Configuration options
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * import { configureSecureFetch } from '@cloudwerk/security/client'
104
+ *
105
+ * // Set up once at app initialization
106
+ * configureSecureFetch({
107
+ * baseUrl: '/api',
108
+ * credentials: 'include',
109
+ * })
110
+ * ```
111
+ */
112
+ declare function configureSecureFetch(options: SecureFetchOptions): void;
113
+ /**
114
+ * Reset secure fetch configuration to defaults.
115
+ */
116
+ declare function resetSecureFetch(): void;
117
+ /**
118
+ * A secure fetch wrapper that automatically adds CSRF tokens and
119
+ * X-Requested-With headers.
120
+ *
121
+ * This wrapper:
122
+ * - Adds X-CSRF-Token header from the CSRF cookie
123
+ * - Adds X-Requested-With: XMLHttpRequest header (forces CORS preflight)
124
+ * - Sets credentials to 'same-origin' by default
125
+ *
126
+ * @param input - URL or Request object
127
+ * @param init - Fetch options
128
+ * @returns Promise resolving to Response
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * import { secureFetch } from '@cloudwerk/security/client'
133
+ *
134
+ * // POST request with automatic security headers
135
+ * const response = await secureFetch('/api/users', {
136
+ * method: 'POST',
137
+ * headers: { 'Content-Type': 'application/json' },
138
+ * body: JSON.stringify({ name: 'Alice' }),
139
+ * })
140
+ * ```
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * // Works with FormData too
145
+ * const formData = new FormData()
146
+ * formData.append('file', fileInput.files[0])
147
+ *
148
+ * const response = await secureFetch('/api/upload', {
149
+ * method: 'POST',
150
+ * body: formData,
151
+ * })
152
+ * ```
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * // DELETE request
157
+ * const response = await secureFetch(`/api/users/${userId}`, {
158
+ * method: 'DELETE',
159
+ * })
160
+ * ```
161
+ */
162
+ declare function secureFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
163
+ /**
164
+ * Convenience method for GET requests.
165
+ *
166
+ * @param url - URL to fetch
167
+ * @param init - Additional fetch options
168
+ * @returns Promise resolving to Response
169
+ */
170
+ declare function secureGet(url: string, init?: Omit<RequestInit, 'method'>): Promise<Response>;
171
+ /**
172
+ * Convenience method for POST requests.
173
+ *
174
+ * @param url - URL to fetch
175
+ * @param body - Request body
176
+ * @param init - Additional fetch options
177
+ * @returns Promise resolving to Response
178
+ */
179
+ declare function securePost(url: string, body?: BodyInit | Record<string, unknown> | null, init?: Omit<RequestInit, 'method' | 'body'>): Promise<Response>;
180
+ /**
181
+ * Convenience method for PUT requests.
182
+ *
183
+ * @param url - URL to fetch
184
+ * @param body - Request body
185
+ * @param init - Additional fetch options
186
+ * @returns Promise resolving to Response
187
+ */
188
+ declare function securePut(url: string, body?: BodyInit | Record<string, unknown> | null, init?: Omit<RequestInit, 'method' | 'body'>): Promise<Response>;
189
+ /**
190
+ * Convenience method for PATCH requests.
191
+ *
192
+ * @param url - URL to fetch
193
+ * @param body - Request body
194
+ * @param init - Additional fetch options
195
+ * @returns Promise resolving to Response
196
+ */
197
+ declare function securePatch(url: string, body?: BodyInit | Record<string, unknown> | null, init?: Omit<RequestInit, 'method' | 'body'>): Promise<Response>;
198
+ /**
199
+ * Convenience method for DELETE requests.
200
+ *
201
+ * @param url - URL to fetch
202
+ * @param init - Additional fetch options
203
+ * @returns Promise resolving to Response
204
+ */
205
+ declare function secureDelete(url: string, init?: Omit<RequestInit, 'method'>): Promise<Response>;
206
+
207
+ export { SecureFetchOptions, configureSecureFetch, csrfInput, getCsrfToken, resetSecureFetch, secureDelete, secureFetch, secureGet, securePatch, securePost, securePut, withCsrfToken };
package/dist/client.js ADDED
@@ -0,0 +1,145 @@
1
+ // src/client/csrf.ts
2
+ var DEFAULT_CSRF_COOKIE_NAME = "cloudwerk.csrf-token";
3
+ var DEFAULT_CSRF_HEADER_NAME = "X-CSRF-Token";
4
+ function getCsrfToken(cookieName = DEFAULT_CSRF_COOKIE_NAME) {
5
+ if (typeof document === "undefined") {
6
+ return null;
7
+ }
8
+ const cookies = document.cookie.split(";");
9
+ for (const cookie of cookies) {
10
+ const [name, ...valueParts] = cookie.split("=");
11
+ if (name?.trim() === cookieName) {
12
+ const value = valueParts.join("=").trim();
13
+ try {
14
+ return decodeURIComponent(value);
15
+ } catch {
16
+ return value;
17
+ }
18
+ }
19
+ }
20
+ return null;
21
+ }
22
+ function withCsrfToken(headers, options = {}) {
23
+ const {
24
+ cookieName = DEFAULT_CSRF_COOKIE_NAME,
25
+ headerName = DEFAULT_CSRF_HEADER_NAME
26
+ } = options;
27
+ const newHeaders = new Headers(headers);
28
+ const token = getCsrfToken(cookieName);
29
+ if (token) {
30
+ newHeaders.set(headerName, token);
31
+ }
32
+ return newHeaders;
33
+ }
34
+ function csrfInput(fieldName = "csrf_token", cookieName = DEFAULT_CSRF_COOKIE_NAME) {
35
+ const token = getCsrfToken(cookieName);
36
+ if (!token) {
37
+ return "";
38
+ }
39
+ const escapedToken = token.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
40
+ return `<input type="hidden" name="${fieldName}" value="${escapedToken}" />`;
41
+ }
42
+
43
+ // src/client/fetch.ts
44
+ var globalConfig = {};
45
+ function configureSecureFetch(options) {
46
+ globalConfig = { ...globalConfig, ...options };
47
+ }
48
+ function resetSecureFetch() {
49
+ globalConfig = {};
50
+ }
51
+ var MUTATION_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
52
+ async function secureFetch(input, init) {
53
+ const config = globalConfig;
54
+ let url;
55
+ if (input instanceof URL) {
56
+ url = input.toString();
57
+ } else if (typeof input === "string") {
58
+ if (config.baseUrl && !input.startsWith("http://") && !input.startsWith("https://")) {
59
+ url = `${config.baseUrl.replace(/\/$/, "")}/${input.replace(/^\//, "")}`;
60
+ } else {
61
+ url = input;
62
+ }
63
+ } else {
64
+ url = input.url;
65
+ }
66
+ const method = (init?.method ?? "GET").toUpperCase();
67
+ const headers = new Headers(init?.headers);
68
+ if (MUTATION_METHODS.includes(method)) {
69
+ const csrfCookieName = config.csrfCookieName ?? "cloudwerk.csrf-token";
70
+ const csrfHeaderName = config.csrfHeaderName ?? "X-CSRF-Token";
71
+ const csrfToken = getCsrfToken(csrfCookieName);
72
+ if (csrfToken && !headers.has(csrfHeaderName)) {
73
+ headers.set(csrfHeaderName, csrfToken);
74
+ }
75
+ const requestedWithValue = config.requestedWithValue ?? "XMLHttpRequest";
76
+ if (!headers.has("X-Requested-With")) {
77
+ headers.set("X-Requested-With", requestedWithValue);
78
+ }
79
+ }
80
+ const fetchOptions = {
81
+ ...init,
82
+ headers,
83
+ credentials: init?.credentials ?? config.credentials ?? "same-origin"
84
+ };
85
+ return fetch(url, fetchOptions);
86
+ }
87
+ function secureGet(url, init) {
88
+ return secureFetch(url, { ...init, method: "GET" });
89
+ }
90
+ function securePost(url, body, init) {
91
+ let finalBody;
92
+ const headers = new Headers(init?.headers);
93
+ if (body && typeof body === "object" && !(body instanceof FormData) && !(body instanceof URLSearchParams) && !(body instanceof Blob) && !(body instanceof ArrayBuffer) && !(body instanceof ReadableStream)) {
94
+ finalBody = JSON.stringify(body);
95
+ if (!headers.has("Content-Type")) {
96
+ headers.set("Content-Type", "application/json");
97
+ }
98
+ } else {
99
+ finalBody = body;
100
+ }
101
+ return secureFetch(url, { ...init, method: "POST", body: finalBody, headers });
102
+ }
103
+ function securePut(url, body, init) {
104
+ let finalBody;
105
+ const headers = new Headers(init?.headers);
106
+ if (body && typeof body === "object" && !(body instanceof FormData) && !(body instanceof URLSearchParams) && !(body instanceof Blob) && !(body instanceof ArrayBuffer) && !(body instanceof ReadableStream)) {
107
+ finalBody = JSON.stringify(body);
108
+ if (!headers.has("Content-Type")) {
109
+ headers.set("Content-Type", "application/json");
110
+ }
111
+ } else {
112
+ finalBody = body;
113
+ }
114
+ return secureFetch(url, { ...init, method: "PUT", body: finalBody, headers });
115
+ }
116
+ function securePatch(url, body, init) {
117
+ let finalBody;
118
+ const headers = new Headers(init?.headers);
119
+ if (body && typeof body === "object" && !(body instanceof FormData) && !(body instanceof URLSearchParams) && !(body instanceof Blob) && !(body instanceof ArrayBuffer) && !(body instanceof ReadableStream)) {
120
+ finalBody = JSON.stringify(body);
121
+ if (!headers.has("Content-Type")) {
122
+ headers.set("Content-Type", "application/json");
123
+ }
124
+ } else {
125
+ finalBody = body;
126
+ }
127
+ return secureFetch(url, { ...init, method: "PATCH", body: finalBody, headers });
128
+ }
129
+ function secureDelete(url, init) {
130
+ return secureFetch(url, { ...init, method: "DELETE" });
131
+ }
132
+ export {
133
+ configureSecureFetch,
134
+ csrfInput,
135
+ getCsrfToken,
136
+ resetSecureFetch,
137
+ secureDelete,
138
+ secureFetch,
139
+ secureGet,
140
+ securePatch,
141
+ securePost,
142
+ securePut,
143
+ withCsrfToken
144
+ };
145
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/csrf.ts","../src/client/fetch.ts"],"sourcesContent":["/**\n * @cloudwerk/security - Client-Side CSRF Helpers\n *\n * Utilities for working with CSRF tokens in browser code.\n */\n\n/** Default CSRF cookie name */\nconst DEFAULT_CSRF_COOKIE_NAME = 'cloudwerk.csrf-token'\n\n/** Default CSRF header name */\nconst DEFAULT_CSRF_HEADER_NAME = 'X-CSRF-Token'\n\n/**\n * Get the CSRF token from cookies.\n *\n * Reads the CSRF token cookie set by the server.\n *\n * @param cookieName - Name of the CSRF cookie\n * @returns The CSRF token or null if not found\n *\n * @example\n * ```typescript\n * import { getCsrfToken } from '@cloudwerk/security/client'\n *\n * const token = getCsrfToken()\n * if (token) {\n * // Include token in your request\n * fetch('/api/users', {\n * method: 'POST',\n * headers: {\n * 'X-CSRF-Token': token,\n * },\n * body: JSON.stringify(data),\n * })\n * }\n * ```\n */\nexport function getCsrfToken(cookieName: string = DEFAULT_CSRF_COOKIE_NAME): string | null {\n if (typeof document === 'undefined') {\n return null\n }\n\n const cookies = document.cookie.split(';')\n for (const cookie of cookies) {\n const [name, ...valueParts] = cookie.split('=')\n if (name?.trim() === cookieName) {\n const value = valueParts.join('=').trim()\n // Handle URL-encoded cookie values\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n }\n }\n\n return null\n}\n\n/**\n * Add CSRF token to a headers object.\n *\n * Creates a new Headers object with the CSRF token added.\n *\n * @param headers - Existing headers to extend (optional)\n * @param options - Configuration options\n * @returns Headers object with CSRF token added\n *\n * @example\n * ```typescript\n * import { withCsrfToken } from '@cloudwerk/security/client'\n *\n * const headers = withCsrfToken({\n * 'Content-Type': 'application/json',\n * })\n *\n * fetch('/api/users', {\n * method: 'POST',\n * headers,\n * body: JSON.stringify(data),\n * })\n * ```\n */\nexport function withCsrfToken(\n headers?: HeadersInit,\n options: {\n cookieName?: string\n headerName?: string\n } = {}\n): Headers {\n const {\n cookieName = DEFAULT_CSRF_COOKIE_NAME,\n headerName = DEFAULT_CSRF_HEADER_NAME,\n } = options\n\n const newHeaders = new Headers(headers)\n\n const token = getCsrfToken(cookieName)\n if (token) {\n newHeaders.set(headerName, token)\n }\n\n return newHeaders\n}\n\n/**\n * Create a hidden input element with the CSRF token.\n *\n * Useful for including CSRF tokens in traditional form submissions.\n *\n * @param fieldName - Name of the form field\n * @param cookieName - Name of the CSRF cookie\n * @returns HTML input element string or empty string if no token\n *\n * @example\n * ```typescript\n * import { csrfInput } from '@cloudwerk/security/client'\n *\n * // In your form template\n * const formHtml = `\n * <form method=\"POST\" action=\"/submit\">\n * ${csrfInput()}\n * <input type=\"text\" name=\"email\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * `\n * ```\n */\nexport function csrfInput(\n fieldName: string = 'csrf_token',\n cookieName: string = DEFAULT_CSRF_COOKIE_NAME\n): string {\n const token = getCsrfToken(cookieName)\n if (!token) {\n return ''\n }\n\n // Escape HTML entities in token\n const escapedToken = token\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n\n return `<input type=\"hidden\" name=\"${fieldName}\" value=\"${escapedToken}\" />`\n}\n","/**\n * @cloudwerk/security - Secure Fetch Wrapper\n *\n * A fetch wrapper that automatically includes CSRF tokens and\n * X-Requested-With headers for secure AJAX requests.\n */\n\nimport type { SecureFetchOptions } from '../types.js'\nimport { getCsrfToken } from './csrf.js'\n\n/** Global configuration for secure fetch */\nlet globalConfig: SecureFetchOptions = {}\n\n/**\n * Configure secure fetch defaults.\n *\n * Set global configuration options that apply to all secureFetch calls.\n *\n * @param options - Configuration options\n *\n * @example\n * ```typescript\n * import { configureSecureFetch } from '@cloudwerk/security/client'\n *\n * // Set up once at app initialization\n * configureSecureFetch({\n * baseUrl: '/api',\n * credentials: 'include',\n * })\n * ```\n */\nexport function configureSecureFetch(options: SecureFetchOptions): void {\n globalConfig = { ...globalConfig, ...options }\n}\n\n/**\n * Reset secure fetch configuration to defaults.\n */\nexport function resetSecureFetch(): void {\n globalConfig = {}\n}\n\n/**\n * HTTP methods that require CSRF protection.\n */\nconst MUTATION_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/**\n * A secure fetch wrapper that automatically adds CSRF tokens and\n * X-Requested-With headers.\n *\n * This wrapper:\n * - Adds X-CSRF-Token header from the CSRF cookie\n * - Adds X-Requested-With: XMLHttpRequest header (forces CORS preflight)\n * - Sets credentials to 'same-origin' by default\n *\n * @param input - URL or Request object\n * @param init - Fetch options\n * @returns Promise resolving to Response\n *\n * @example\n * ```typescript\n * import { secureFetch } from '@cloudwerk/security/client'\n *\n * // POST request with automatic security headers\n * const response = await secureFetch('/api/users', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ name: 'Alice' }),\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Works with FormData too\n * const formData = new FormData()\n * formData.append('file', fileInput.files[0])\n *\n * const response = await secureFetch('/api/upload', {\n * method: 'POST',\n * body: formData,\n * })\n * ```\n *\n * @example\n * ```typescript\n * // DELETE request\n * const response = await secureFetch(`/api/users/${userId}`, {\n * method: 'DELETE',\n * })\n * ```\n */\nexport async function secureFetch(\n input: RequestInfo | URL,\n init?: RequestInit\n): Promise<Response> {\n const config = globalConfig\n\n // Resolve URL\n let url: string\n if (input instanceof URL) {\n url = input.toString()\n } else if (typeof input === 'string') {\n // Prepend base URL if configured and URL is relative\n if (config.baseUrl && !input.startsWith('http://') && !input.startsWith('https://')) {\n url = `${config.baseUrl.replace(/\\/$/, '')}/${input.replace(/^\\//, '')}`\n } else {\n url = input\n }\n } else {\n url = input.url\n }\n\n // Determine method\n const method = (init?.method ?? 'GET').toUpperCase()\n\n // Build headers\n const headers = new Headers(init?.headers)\n\n // Add CSRF token for mutation methods\n if (MUTATION_METHODS.includes(method)) {\n const csrfCookieName = config.csrfCookieName ?? 'cloudwerk.csrf-token'\n const csrfHeaderName = config.csrfHeaderName ?? 'X-CSRF-Token'\n\n const csrfToken = getCsrfToken(csrfCookieName)\n if (csrfToken && !headers.has(csrfHeaderName)) {\n headers.set(csrfHeaderName, csrfToken)\n }\n\n // Add X-Requested-With header\n const requestedWithValue = config.requestedWithValue ?? 'XMLHttpRequest'\n if (!headers.has('X-Requested-With')) {\n headers.set('X-Requested-With', requestedWithValue)\n }\n }\n\n // Build fetch options\n const fetchOptions: RequestInit = {\n ...init,\n headers,\n credentials: init?.credentials ?? config.credentials ?? 'same-origin',\n }\n\n return fetch(url, fetchOptions)\n}\n\n/**\n * Convenience method for GET requests.\n *\n * @param url - URL to fetch\n * @param init - Additional fetch options\n * @returns Promise resolving to Response\n */\nexport function secureGet(\n url: string,\n init?: Omit<RequestInit, 'method'>\n): Promise<Response> {\n return secureFetch(url, { ...init, method: 'GET' })\n}\n\n/**\n * Convenience method for POST requests.\n *\n * @param url - URL to fetch\n * @param body - Request body\n * @param init - Additional fetch options\n * @returns Promise resolving to Response\n */\nexport function securePost(\n url: string,\n body?: BodyInit | Record<string, unknown> | null,\n init?: Omit<RequestInit, 'method' | 'body'>\n): Promise<Response> {\n let finalBody: BodyInit | null | undefined\n const headers = new Headers(init?.headers)\n\n // Auto-stringify objects and set Content-Type\n if (body && typeof body === 'object' && !(body instanceof FormData) &&\n !(body instanceof URLSearchParams) && !(body instanceof Blob) &&\n !(body instanceof ArrayBuffer) && !(body instanceof ReadableStream)) {\n finalBody = JSON.stringify(body)\n if (!headers.has('Content-Type')) {\n headers.set('Content-Type', 'application/json')\n }\n } else {\n finalBody = body as BodyInit | null | undefined\n }\n\n return secureFetch(url, { ...init, method: 'POST', body: finalBody, headers })\n}\n\n/**\n * Convenience method for PUT requests.\n *\n * @param url - URL to fetch\n * @param body - Request body\n * @param init - Additional fetch options\n * @returns Promise resolving to Response\n */\nexport function securePut(\n url: string,\n body?: BodyInit | Record<string, unknown> | null,\n init?: Omit<RequestInit, 'method' | 'body'>\n): Promise<Response> {\n let finalBody: BodyInit | null | undefined\n const headers = new Headers(init?.headers)\n\n if (body && typeof body === 'object' && !(body instanceof FormData) &&\n !(body instanceof URLSearchParams) && !(body instanceof Blob) &&\n !(body instanceof ArrayBuffer) && !(body instanceof ReadableStream)) {\n finalBody = JSON.stringify(body)\n if (!headers.has('Content-Type')) {\n headers.set('Content-Type', 'application/json')\n }\n } else {\n finalBody = body as BodyInit | null | undefined\n }\n\n return secureFetch(url, { ...init, method: 'PUT', body: finalBody, headers })\n}\n\n/**\n * Convenience method for PATCH requests.\n *\n * @param url - URL to fetch\n * @param body - Request body\n * @param init - Additional fetch options\n * @returns Promise resolving to Response\n */\nexport function securePatch(\n url: string,\n body?: BodyInit | Record<string, unknown> | null,\n init?: Omit<RequestInit, 'method' | 'body'>\n): Promise<Response> {\n let finalBody: BodyInit | null | undefined\n const headers = new Headers(init?.headers)\n\n if (body && typeof body === 'object' && !(body instanceof FormData) &&\n !(body instanceof URLSearchParams) && !(body instanceof Blob) &&\n !(body instanceof ArrayBuffer) && !(body instanceof ReadableStream)) {\n finalBody = JSON.stringify(body)\n if (!headers.has('Content-Type')) {\n headers.set('Content-Type', 'application/json')\n }\n } else {\n finalBody = body as BodyInit | null | undefined\n }\n\n return secureFetch(url, { ...init, method: 'PATCH', body: finalBody, headers })\n}\n\n/**\n * Convenience method for DELETE requests.\n *\n * @param url - URL to fetch\n * @param init - Additional fetch options\n * @returns Promise resolving to Response\n */\nexport function secureDelete(\n url: string,\n init?: Omit<RequestInit, 'method'>\n): Promise<Response> {\n return secureFetch(url, { ...init, method: 'DELETE' })\n}\n"],"mappings":"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","names":[]}