-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #87 from greg2012201/master
feat: add Next.js 15+ support (continuation)
- Loading branch information
Showing
11 changed files
with
733 additions
and
491 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module.exports = { | ||
preset: 'ts-jest', | ||
testEnvironment: 'jest-environment-jsdom', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { getCookie, getCookies, setCookie, deleteCookie, hasCookie } from '../index'; | ||
|
||
describe('Client-side cookie operations', () => { | ||
test('getCookies should return all cookies', () => { | ||
setCookie('key1', 'value1'); | ||
setCookie('key2', 'value2'); | ||
const cookies = getCookies(); | ||
expect(cookies).toEqual({ key1: 'value1', key2: 'value2' }); | ||
}); | ||
|
||
test('setCookie should set a cookie', () => { | ||
setCookie('testKey', 'testValue'); | ||
expect(document.cookie).toContain('testKey=testValue'); | ||
}); | ||
|
||
test('getCookie should retrieve a set cookie', () => { | ||
document.cookie = 'testKey2=testValue2'; | ||
const value = getCookie('testKey2'); | ||
expect(value).toBe('testValue2'); | ||
}); | ||
|
||
test('deleteCookie should remove a cookie', () => { | ||
document.cookie = 'testKey3=testValue3'; | ||
deleteCookie('testKey3'); | ||
expect(document.cookie).not.toContain('testKey3=testValue3'); | ||
}); | ||
|
||
test('hasCookie should return true for existing cookie', () => { | ||
document.cookie = 'testKey4=testValue4'; | ||
const exists = hasCookie('testKey4'); | ||
expect(exists).toBe(true); | ||
}); | ||
|
||
test('hasCookie should return false for non-existing cookie', () => { | ||
const exists = hasCookie('nonExistentKey5'); | ||
expect(exists).toBe(false); | ||
}); | ||
|
||
test('getCookie should return undefined for non-existing cookie', () => { | ||
const value = getCookie('nonExistentKey'); | ||
expect(value).toBeUndefined(); | ||
}); | ||
|
||
test('setCookie should handle complex values', () => { | ||
const complexValue = { key: 'value', nested: { array: [1, 2, 3] } }; | ||
setCookie('complexKey', complexValue); | ||
const retrievedValue = getCookie('complexKey'); | ||
expect(typeof retrievedValue === 'string' ? JSON.parse(retrievedValue) : {}).toEqual(complexValue); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { serialize } from 'cookie'; | ||
import type { OptionsType, TmpCookiesObj, CookieValueTypes } from '../common/types'; | ||
import { stringify, decode, isClientSide, getRenderPhase } from '../common/utils'; | ||
|
||
const ensureClientSide = (options?: OptionsType) => { | ||
if (!isClientSide(options)) { | ||
throw new Error( | ||
'You are trying to access cookies on the server side. Please, use the server-side import with `cookies-next/server` instead.', | ||
); | ||
} | ||
}; | ||
|
||
const getCookies = (_options?: OptionsType): TmpCookiesObj | undefined => { | ||
ensureClientSide(_options); | ||
if (getRenderPhase() === 'server') { | ||
return; | ||
} | ||
const cookies: TmpCookiesObj = {}; | ||
const documentCookies = document.cookie ? document.cookie.split('; ') : []; | ||
|
||
for (let i = 0, len = documentCookies.length; i < len; i++) { | ||
const cookieParts = documentCookies[i].split('='); | ||
const cookie = cookieParts.slice(1).join('='); | ||
const name = cookieParts[0]; | ||
cookies[name] = cookie; | ||
} | ||
|
||
return cookies; | ||
}; | ||
|
||
const getCookie = (key: string, options?: OptionsType): CookieValueTypes => { | ||
ensureClientSide(options); | ||
const _cookies = getCookies(options); | ||
const value = _cookies?.[key]; | ||
if (value === undefined) return undefined; | ||
return decode(value); | ||
}; | ||
|
||
const setCookie = (key: string, data: any, options?: OptionsType): void => { | ||
ensureClientSide(options); | ||
if (getRenderPhase() === 'server') { | ||
return; | ||
} | ||
const _cookieOptions = options || {}; | ||
const cookieStr = serialize(key, stringify(data), { path: '/', ..._cookieOptions }); | ||
document.cookie = cookieStr; | ||
}; | ||
|
||
const deleteCookie = (key: string, options?: OptionsType): void => { | ||
ensureClientSide(options); | ||
setCookie(key, '', { ...options, maxAge: -1 }); | ||
}; | ||
|
||
const hasCookie = (key: string, options?: OptionsType): boolean => { | ||
ensureClientSide(options); | ||
if (!key) return false; | ||
const cookies = getCookies(options); | ||
if (!cookies) { | ||
return false; | ||
} | ||
return Object.prototype.hasOwnProperty.call(cookies, key); | ||
}; | ||
|
||
export * from '../common/types'; | ||
export { getCookies, getCookie, setCookie, deleteCookie, hasCookie }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { SerializeOptions } from 'cookie'; | ||
import { IncomingMessage, ServerResponse } from 'http'; | ||
import type { cookies } from 'next/headers'; | ||
import type { RequestCookies, ResponseCookies } from 'next/dist/compiled/@edge-runtime/cookies'; | ||
|
||
/* | ||
We need to declare our own extensions of Request and Response | ||
because NextResponse and NextRequest have an [INTERNALS] property, | ||
which is a symbol that conflicts with the types provided by the user to our exported function. | ||
The TypeScript error that occurred before this re-declaration was as follows: | ||
Property '[INTERNALS]' is missing in type 'import("node_modules/next/dist/server/web/spec-extension/response").NextResponse<unknown>' | ||
but required in type 'import("cookies-next/node_modules/next/dist/server/web/spec-extension/response").NextResponse<unknown>'.ts(2741) | ||
*/ | ||
interface NextCookiesRequest extends Request { | ||
get cookies(): RequestCookies; | ||
} | ||
|
||
interface NextCookiesResponse extends Response { | ||
get cookies(): ResponseCookies; | ||
} | ||
|
||
export interface HttpContext extends SerializeOptions { | ||
req?: IncomingMessage & { | ||
// Might be set by third-party libraries such as `cookie-parser` | ||
cookies?: TmpCookiesObj; | ||
}; | ||
res?: ServerResponse; | ||
} | ||
export type NextContext = { | ||
req?: NextCookiesRequest; | ||
res?: NextCookiesResponse; | ||
cookies?: CookiesFn; | ||
}; | ||
export type OptionsType = HttpContext | NextContext; | ||
|
||
export type CookiesFn = typeof cookies; | ||
export type NextCookies = NextCookiesResponse['cookies'] | NextCookiesRequest['cookies']; | ||
export type TmpCookiesObj = { [key: string]: string } | Partial<{ [key: string]: string }>; | ||
export type CookieValueTypes = string | undefined; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import type { CookiesFn, OptionsType } from './types'; | ||
|
||
export const stringify = (value: any) => { | ||
try { | ||
if (typeof value === 'string') { | ||
return value; | ||
} | ||
const stringifiedValue = JSON.stringify(value); | ||
return stringifiedValue; | ||
} catch (e) { | ||
return value; | ||
} | ||
}; | ||
|
||
export const decode = (str: string): string => { | ||
if (!str) return str; | ||
return str.replace(/(%[0-9A-Z]{2})+/g, decodeURIComponent); | ||
}; | ||
|
||
export const isClientSide = (options?: OptionsType) => { | ||
return !options?.req && !options?.res && !(options && 'cookies' in options && (options?.cookies as CookiesFn)); | ||
}; | ||
|
||
export const getRenderPhase = () => (typeof window === 'undefined' ? 'server' : 'client'); |
Oops, something went wrong.