diff --git a/README.md b/README.md index ea8fe40..62f59fa 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Are you overwhelmed by countless options and dependencies? Embrace the simplicit ## Alpha Software Please use at your own risk, this is alpha software and is still very much in the early stages and the APIs are **guranteed** to change. +This project is + ## Key Highlights - **Zero Dependencies**: Our goal is to build this toolkit with no extra dependencies so your apps stay lean and fast, relying solely on Bun itself. diff --git a/index.ts b/index.ts index c35c12b..0dc22e4 100644 --- a/index.ts +++ b/index.ts @@ -1,17 +1,29 @@ export { createCliFactory } from "./modules/cli-factory"; +export { + createClientCookieFactory, + createServerCookieFactory +} from "./modules/cookie-factory"; export { createErrorHandlerFactory } from "./modules/error-handler-factory/create-error-handler-factory"; export { defaultErrorHandler } from "./modules/error-handler-factory/default-error-handler"; export * from "./modules/fetch-factory/create-fetch-factory"; export { createFileFactory } from "./modules/files-factory"; +export { + createNonSecureHashFactory, + createSecureHashFactory +} from "./modules/hash-factory"; +export { createHIDKeyboardFactory } from "./modules/hid-emulators/keyboard-hid-factory"; +export { + jwtClientSideFactory, + jwtServerSideFactory +} from "./modules/jwt-factory"; export * from "./modules/logger-factory"; export { createLoggerFactory } from "./modules/logger-factory/create-logger-factory"; export { createServerFactory } from "./modules/server-factory"; export { createSqliteFactory, - createTableQuery, + createTableQuery } from "./modules/sqlite-factory"; export { createHTMLProcessor } from "./modules/text-processing/html-factory"; export { createMarkdownProcessor } from "./modules/text-processing/markdown-factory"; export { createValidatorFactory } from "./modules/validation-factory"; -export { createHIDKeyboardFactory } from "./modules/hid-emulators/keyboard-hid-factory"; -export {jwtClientSideFactory, jwtServerSideFactory} from './modules/jwt-factory' \ No newline at end of file + diff --git a/modules/cookie-factory/cookie-factory.md b/modules/cookie-factory/cookie-factory.md new file mode 100644 index 0000000..f6a93c7 --- /dev/null +++ b/modules/cookie-factory/cookie-factory.md @@ -0,0 +1,65 @@ +## Client-Side: + +1. **Set Cookie:** A method that accepts a cookie name, value, and optional settings (like max-age, path, domain, secure, and http-only) as parameters and sets the cookie on the client. + +2. **Get Cookie:** A method that accepts a cookie name as a parameter and retrieves its value from the client. + +3. **Delete Cookie:** A method that accepts a cookie name as a parameter and removes it from the client. + +4. **Check Cookie:** A method that accepts a cookie name as a parameter and checks if the cookie exists on the client. + +## Server-Side: + +1. **Set Cookie:** A similar method to the client-side's set cookie method but sets the cookie on the server response. This will require access to the response object from the server. + +2. **Get Cookie:** A method that accepts a cookie name as a parameter and retrieves its value from the server request. This will require access to the request object from the server. + +3. **Delete Cookie:** A similar method to the client-side's delete cookie method but deletes the cookie on the server. This will require access to the response object from the server. + +4. **Check Cookie:** A method that accepts a cookie name as a parameter and checks if the cookie exists on the server request. This will require access to the request object from the server. + +Here's a brief overview of how the code for these might look: + +```typescript +export const cookieFactory = () => { + // Client-side methods. + const setCookieClient = (name: string, value: string, options?: CookieOptions) => { ... }; + const getCookieClient = (name: string) => { ... }; + const deleteCookieClient = (name: string) => { ... }; + const checkCookieClient = (name: string) => { ... }; + + // Server-side methods. + const setCookieServer = (res: Response, name: string, value: string, options?: CookieOptions) => { ... }; + const getCookieServer = (req: Request, name: string) => { ... }; + const deleteCookieServer = (res: Response, name: string) => { ... }; + const checkCookieServer = (req: Request, name: string) => { ... }; + + return { + setCookieClient, + getCookieClient, + deleteCookieClient, + checkCookieClient, + setCookieServer, + getCookieServer, + deleteCookieServer, + checkCookieServer, + }; +}; + +// Cookie options type for TypeScript. +type CookieOptions = { + maxAge?: number; + path?: string; + domain?: string; + secure?: boolean; + httpOnly?: boolean; +}; +``` + +Remember to replace the `{ ... }` with the actual logic of each function. For client-side methods, you'll be manipulating `document.cookie`. For server-side methods, you'll be manipulating the `req.cookies` and `res.cookie` objects (assuming Express.js or similar server framework). + +Please note that on the server side, you need to ensure you have middleware in place to handle cookies. In Express.js, for example, this would be the `cookie-parser` middleware. + +For the server-side `setCookieServer` and `deleteCookieServer` methods, you will need to provide the `res` (response) object as an argument to the function. This is because these methods need to modify the HTTP response headers to set or delete cookies. For `getCookieServer` and `checkCookieServer`, you will need to provide the `req` (request) object to access the cookies sent by the client in the HTTP request headers. + +Keep in mind, while storing JWTs in cookies, it's good practice to set the `httpOnly` flag to prevent client-side JavaScript from accessing the cookie. This helps mitigate certain types of cross-site scripting (XSS) attacks. \ No newline at end of file diff --git a/modules/cookie-factory/create-client-side-cookie-factory.ts b/modules/cookie-factory/create-client-side-cookie-factory.ts new file mode 100644 index 0000000..42da4c2 --- /dev/null +++ b/modules/cookie-factory/create-client-side-cookie-factory.ts @@ -0,0 +1,69 @@ +export function createClientCookieFactory() { + const setCookie = ( + name: string, + value: string, + options: CookieOptions = {} + ) => { + let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent( + value + )}`; + + if (options.maxAge) { + cookieString += `; max-age=${options.maxAge}`; + } + + if (options.path) { + cookieString += `; path=${options.path}`; + } + + if (options.domain) { + cookieString += `; domain=${options.domain}`; + } + + if (options.secure) { + cookieString += `; secure`; + } + + if (options.httpOnly) { + cookieString += `; httpOnly`; + } + + document.cookie = cookieString; + }; + + const getCookie = (name: string) => { + const cookieArr = document.cookie.split("; "); + + for (let i = 0; i < cookieArr.length; i++) { + const cookiePair = cookieArr[i].split("="); + if (name == decodeURIComponent(cookiePair[0])) { + return decodeURIComponent(cookiePair[1]); + } + } + + return null; + }; + + const deleteCookie = (name: string) => { + setCookie(name, "", { maxAge: -1 }); + }; + + const checkCookie = (name: string) => { + return getCookie(name) !== null; + }; + + return { + setCookie, + getCookie, + deleteCookie, + checkCookie, + }; +} + +type CookieOptions = { + maxAge?: number; + path?: string; + domain?: string; + secure?: boolean; + httpOnly?: boolean; +}; diff --git a/modules/cookie-factory/index.ts b/modules/cookie-factory/index.ts new file mode 100644 index 0000000..e4e75e6 --- /dev/null +++ b/modules/cookie-factory/index.ts @@ -0,0 +1,2 @@ +export { createClientCookieFactory } from "./create-client-side-cookie-factory"; +export { createServerCookieFactory } from "./server-side-cookie-factory"; diff --git a/modules/cookie-factory/server-side-cookie-factory.ts b/modules/cookie-factory/server-side-cookie-factory.ts new file mode 100644 index 0000000..30548fe --- /dev/null +++ b/modules/cookie-factory/server-side-cookie-factory.ts @@ -0,0 +1,80 @@ +export function createServerCookieFactory() { + const setCookie = ( + res: Response, + name: string, + value: string, + options: CookieOptions = {} + ) => { + let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent( + value + )}`; + + if (options.maxAge) { + cookieString += `; Max-Age=${options.maxAge}`; + } + + if (options.path) { + cookieString += `; Path=${options.path}`; + } + + if (options.domain) { + cookieString += `; Domain=${options.domain}`; + } + + if (options.secure) { + cookieString += `; Secure`; + } + + if (options.httpOnly) { + cookieString += `; HttpOnly`; + } + + if (options.sameSite) { + cookieString += `; SameSite=${options.sameSite}`; + } + + // For HTTP/2, multiple Set-Cookie headers are allowed + res.headers.append("Set-Cookie", cookieString); + }; + + const getCookie = (req: Request, name: string) => { + const cookies = parseCookies(req.headers.get("Cookie") || ""); + return cookies[name]; + }; + + const deleteCookie = (res: Response, name: string) => { + setCookie(res, name, "", { maxAge: -1 }); + }; + + const checkCookie = (req: Request, name: string) => { + return getCookie(req, name) !== undefined; + }; + + return { + setCookie, + getCookie, + deleteCookie, + checkCookie, + }; +} + +type CookieOptions = { + maxAge?: number; + path?: string; + domain?: string; + secure?: boolean; + httpOnly?: boolean; + sameSite?: "Strict" | "Lax" | "None"; +}; + +function parseCookies(cookiesString: string) { + const cookies: { [name: string]: string } = {}; + const pairs = cookiesString.split(";"); + + pairs.forEach((pair) => { + const [name, ...rest] = pair.split("="); + cookies[name.trim()] = rest.join("=").trim(); + }); + + return cookies; +} diff --git a/modules/files-factory/files-folder.ts b/modules/files-factory/files-folder.ts index 70534b8..c219d8b 100644 --- a/modules/files-factory/files-folder.ts +++ b/modules/files-factory/files-folder.ts @@ -66,7 +66,7 @@ export function findAppRoot(startingPath: string): string | null | undefined { export const saveResultToFile = async ( filePath: string, content: string -): Promise | undefined => { +): Promise => { try { await fs.promises.mkdir(path.dirname(filePath), { recursive: true }); await fs.promises.writeFile(filePath, content); @@ -89,13 +89,17 @@ export const readFilesContents = ( } }; - type FileFactoryOptions = { baseDirectory: string; - errorHandler: ReturnType>>; + errorHandler: ReturnType< + typeof createErrorHandlerFactory> + >; }; -export function createFileFactory({ baseDirectory, errorHandler }: FileFactoryOptions) { +export function createFileFactory({ + baseDirectory, + errorHandler, +}: FileFactoryOptions) { const getFullPath = (filePath: string) => path.join(baseDirectory, filePath); const updateFiles = async (filePaths: string[], data: string) => { diff --git a/modules/hash-factory/has-factory.md b/modules/hash-factory/hash-factory.md similarity index 100% rename from modules/hash-factory/has-factory.md rename to modules/hash-factory/hash-factory.md diff --git a/modules/hash-factory/index.ts b/modules/hash-factory/index.ts index e69de29..cc4b6ea 100644 --- a/modules/hash-factory/index.ts +++ b/modules/hash-factory/index.ts @@ -0,0 +1,3 @@ +export { createNonSecureHashFactory } from "./non-secure-hash-factory"; +export { createSecureHashFactory } from "./secure-hash-factory"; + diff --git a/modules/hash-factory/non-secure-hash-factory.ts b/modules/hash-factory/non-secure-hash-factory.ts new file mode 100644 index 0000000..5af8a39 --- /dev/null +++ b/modules/hash-factory/non-secure-hash-factory.ts @@ -0,0 +1,30 @@ +type NonSecureHashAlgorithm = + | "wyhash" + | "crc32" + | "adler32" + | "cityHash32" + | "cityHash64" + | "murmur32v3" + | "murmur64v2"; + +export function createNonSecureHashFactory() { + const hash = ( + data: string | ArrayBuffer | SharedArrayBuffer, + seed?: number + ) => { + return Bun.hash(data, seed); + }; + + const hashWithAlgorithm = ( + algorithm: NonSecureHashAlgorithm, + data: string | ArrayBuffer | SharedArrayBuffer, + seed?: number + ) => { + return Bun.hash[algorithm](data, seed); + }; + + return { + hash, + hashWithAlgorithm, + }; +} diff --git a/modules/hash-factory/secure-hash-factory.ts b/modules/hash-factory/secure-hash-factory.ts index caf5cca..e05ba82 100644 --- a/modules/hash-factory/secure-hash-factory.ts +++ b/modules/hash-factory/secure-hash-factory.ts @@ -5,7 +5,7 @@ type SecureHashConfig = { cost?: number; }; -function createSecureHasher() { +export function createSecureHashFactory() { const hashPassword = async ( password: string, config: SecureHashConfig = { diff --git a/tsconfig.json b/tsconfig.json index 6dfe9f8..06ef36c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "lib": ["ESNext"], + "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "esnext", "target": "esnext", "moduleResolution": "bundler",