next-session
Advanced tools
Comparing version 3.4.3 to 4.0.0
{ | ||
"type": "module", | ||
"name": "next-session", | ||
"version": "3.4.3", | ||
"description": "Simple promise-based session middleware for Next.js", | ||
"version": "4.0.0", | ||
"description": "Simple promise-based session for Next.js", | ||
"keywords": [ | ||
"javascript", | ||
"nextjs", | ||
"session", | ||
"middleware", | ||
"promise" | ||
"promise", | ||
"express-session" | ||
], | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"main": "./lib/session.cjs", | ||
"exports": { | ||
".": { | ||
"import": "./lib/session.js", | ||
"require": "./lib/session.cjs" | ||
}, | ||
"./lib/compat": { | ||
"import": "./lib/compat.js", | ||
"require": "./lib/compat.cjs" | ||
} | ||
}, | ||
"types": "lib/session.d.ts", | ||
"files": [ | ||
"dist/" | ||
"lib/" | ||
], | ||
"sideEffects": false, | ||
"scripts": { | ||
"prepublish": "yarn build", | ||
"build": "tsc --outDir dist", | ||
"build:commonjs": "BUILD_MODULE=commonjs babel src -d lib --extensions .ts --out-file-extension .cjs", | ||
"build:module": "babel src -d lib --extensions .ts", | ||
"build:typescript": "tsc --outDir lib --emitDeclarationOnly", | ||
"build": "rm -rf lib && yarn build:typescript && yarn build:commonjs && yarn build:module", | ||
"lint": "eslint src --ext ts --ignore-path .gitignore", | ||
"test": "yarn build && jest --coverageReporters=text-lcov > coverage.lcov" | ||
"test:import": "cd test/import-test && rm -rf node_modules && yarn && node index.js && node index.cjs && cd ../../", | ||
"test:unit": "jest --coverageReporters=text-lcov > coverage.lcov", | ||
"test": "yarn test:unit && yarn test:import" | ||
}, | ||
@@ -35,19 +49,15 @@ "repository": { | ||
"devDependencies": { | ||
"@babel/cli": "^7.15.7", | ||
"@babel/core": "^7.15.5", | ||
"@babel/plugin-transform-modules-commonjs": "^7.15.4", | ||
"@babel/preset-typescript": "^7.15.0", | ||
"@types/cookie": "^0.4.1", | ||
"@types/cookie-signature": "^1.0.3", | ||
"@types/express-session": "^1.17.4", | ||
"@types/jest": "^27.0.1", | ||
"@types/node": "^16.9.4", | ||
"@types/react": "^17.0.22", | ||
"@types/supertest": "^2.0.11", | ||
"@typescript-eslint/eslint-plugin": "^4.31.1", | ||
"@typescript-eslint/parser": "^4.31.1", | ||
"cookie-signature": "^1.1.0", | ||
"babel-plugin-add-import-extension": "^1.6.0", | ||
"eslint": "^7.32.0", | ||
"jest": "^27.2.0", | ||
"next": "11.1.2", | ||
"react": "^17.0.2", | ||
"react-dom": "^17.0.2", | ||
"supertest": "^6.1.6", | ||
"tree-kill": "^1.2.2", | ||
"light-my-request": "^4.4.4", | ||
"ts-jest": "^27.0.5", | ||
@@ -61,4 +71,4 @@ "typescript": "^4.4.3" | ||
"engines": { | ||
"node": ">=10.0.0" | ||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0" | ||
} | ||
} |
311
README.md
@@ -9,6 +9,5 @@ # next-session | ||
Simple _promise-based_ session middleware for [Next.js](https://github.com/zeit/next.js). Also works in [micro](https://github.com/zeit/micro) or [Node.js HTTP Server](https://nodejs.org/api/http.html), [Express](https://github.com/expressjs/express), and more. | ||
Simple _promise-based_ session for [Next.js](https://github.com/zeit/next.js). Also works in [micro](https://github.com/zeit/micro) or [Node.js HTTP Server](https://nodejs.org/api/http.html), [Express](https://github.com/expressjs/express), and more. | ||
> Also check out alternatives like [express-session](https://github.com/expressjs/session)+[next-connect](https://github.com/hoangvvo/next-connect) or [next-iron-session](https://github.com/vvo/next-iron-session) instead. | ||
> Update: It is observed that express-session sometimes does not work properly with Next.js 11.x | ||
> Check out alternatives like [next-iron-session](https://github.com/vvo/next-iron-session) instead. Also check out [nextjs-mongodb-app](https://github.com/hoangvvo/nextjs-mongodb-app) to see this module in use. | ||
@@ -30,17 +29,39 @@ ## Installation | ||
`next-session` has several named exports: | ||
:point_right: **Upgrading from v3.x to v4.x?** Please read the release notes [here](https://github.com/hoangvvo/next-session/releases/tag/v4.0.0)! | ||
- `session` to be used as a Connect/Express middleware. (Use [next-connect](https://github.com/hoangvvo/next-connect) if used in Next.js) | ||
- `withSession` to be used as HOC in Page Components or API Routes wrapper (and several others). | ||
- `applySession`, to manually initialize `next-session` by providing `req` and `res`. | ||
**Warning** The default session store (if `options?.store` is `undefined`), `MemoryStore`, **DOES NOT** work in production or serverless environment. You must use a [Session Store](#session-store). | ||
Use **one of them** to work with `next-session`. Can also be used in other frameworks in the same manner as long as they have `(req, res)` handler signature. | ||
```js | ||
// ./lib/get-session.js | ||
import nextSession from "next-session"; | ||
export const getSession = nextSession(options); | ||
``` | ||
**Warning** The default session store, `MemoryStore`, should not be used in production since it does not persist nor work in Serverless. | ||
### API Routes | ||
```js | ||
import { getSession } from "./lib/get-session.js"; | ||
export default function handler(req, res) { | ||
const session = await getSession(req, res); | ||
session.views = session.views ? session.views + 1 : 1; | ||
// Also available under req.session: | ||
// req.session.views = req.session.views ? req.session.views + 1 : 1; | ||
res.send( | ||
`In this session, you have visited this website ${session.views} time(s).` | ||
); | ||
} | ||
``` | ||
Usage in API Routes may result in `API resolved without sending a response`. This can be solved by either adding: | ||
```js | ||
import nextSession from "next-session"; | ||
const getSession = nextSession(); | ||
export default function handler(req, res) { | ||
const session = await getSession(req, res); | ||
/* ... */ | ||
} | ||
export const config = { | ||
@@ -53,45 +74,51 @@ api: { | ||
...or setting `options.autoCommit` to `false` and do `await session.commit()` (See [this](https://github.com/hoangvvo/next-session#reqsessioncommit)). | ||
...or setting `options.autoCommit` to `false` and do `await session.commit()`. | ||
#### `{ session }` | ||
```js | ||
import nextSession from "next-session"; | ||
const getSession = nextSession({ autoCommit: false }); | ||
```javascript | ||
import { session } from 'next-session'; | ||
import nextConnect from 'next-connect'; | ||
const mySession = session(options); | ||
const handler = nextConnect() | ||
.use(mySession) | ||
.all(() => { | ||
req.session.views = req.session.views ? req.session.views + 1 : 1; | ||
res.send( | ||
`In this session, you have visited this website ${req.session.views} time(s).` | ||
); | ||
}); | ||
export default handler; | ||
export default function handler(req, res) { | ||
const session = await getSession(req, res); | ||
/* ... */ | ||
await session.commit(); | ||
} | ||
``` | ||
#### `{ withSession }` | ||
### getServerSideProps | ||
```javascript | ||
import { withSession } from 'next-session'; | ||
```js | ||
import { getSession } from "./lib/get-session.js"; | ||
function handler(req, res) { | ||
req.session.views = req.session.views ? req.session.views + 1 : 1; | ||
res.send( | ||
`In this session, you have visited this website ${req.session.views} time(s).` | ||
export default function Page({ views }) { | ||
return ( | ||
<div>In this session, you have visited this website {views} time(s).</div> | ||
); | ||
} | ||
export default withSession(handler, options); | ||
export async function getServerSideProps({ req, res }) { | ||
const session = await getSession(req, res); | ||
session.views = session.views ? session.views + 1 : 1; | ||
// Also available under req.session: | ||
// req.session.views = req.session.views ? req.session.views + 1 : 1; | ||
return { | ||
props: { | ||
views: session.views, | ||
}, | ||
}; | ||
} | ||
``` | ||
#### `{ applySession }` | ||
### Others | ||
```javascript | ||
import { applySession } from 'next-session'; | ||
[express](https://github.com/expressjs/express), [next-connect](https://github.com/hoangvvo/next-connect) | ||
export default async function handler(req, res) { | ||
await applySession(req, res, options); | ||
```js | ||
const express = require("express"); | ||
const app = express(); | ||
app.use(async (req, res, next) => { | ||
await getSession(req, res); // session is set to req.session | ||
next(); | ||
}); | ||
app.get("/", (req, res) => { | ||
req.session.views = req.session.views ? req.session.views + 1 : 1; | ||
@@ -101,59 +128,26 @@ res.send( | ||
); | ||
} | ||
}); | ||
``` | ||
### Pages | ||
[micro](https://github.com/vercel/micro), [Vercel Serverless Functions](https://vercel.com/docs/functions/introduction) | ||
`next-session` does not work in [Custom App](https://nextjs.org/docs/advanced-features/custom-app) since it leads to deoptimization. | ||
#### ~~`{ withSession }` ([`getInitialProps`](https://nextjs.org/docs/api-reference/data-fetching/getInitialProps))~~ | ||
**This will be deprecated in the next major release!** | ||
> `next@>9.3.0` recommends using `getServerSideProps` instead of `getInitialProps`. | ||
> Also, it is not reliable since `req` or `req.session` is only available on [server only](https://nextjs.org/docs/api-reference/data-fetching/getInitialProps#context-object) | ||
```javascript | ||
import { withSession } from 'next-session'; | ||
function Page({ views }) { | ||
return ( | ||
<div>In this session, you have visited this website {views} time(s).</div> | ||
```js | ||
module.exports = (req, res) => { | ||
const session = await getSession(req, res); | ||
res.end( | ||
`In this session, you have visited this website ${session.views} time(s).` | ||
); | ||
} | ||
Page.getInitialProps = ({ req }) => { | ||
let views; | ||
if (typeof window === 'undefined') { | ||
// req.session is only available on server-side. | ||
req.session.views = req.session.views ? req.session.views + 1 : 1; | ||
views = req.session.views; | ||
} | ||
// WARNING: On client-side routing, neither req nor req.session is available. | ||
return { views }; | ||
}; | ||
export default withSession(Page, options); | ||
``` | ||
#### `{ applySession }` ([`getServerSideProps`](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering)) | ||
[Node.js HTTP Server](https://nodejs.org/api/http.html) | ||
```javascript | ||
import { applySession } from 'next-session'; | ||
```js | ||
const http = require("http"); | ||
export default function Page({ views }) { | ||
return ( | ||
<div>In this session, you have visited this website {views} time(s).</div> | ||
); | ||
} | ||
export async function getServerSideProps({ req, res }) { | ||
await applySession(req, res, options); | ||
req.session.views = req.session.views ? req.session.views + 1 : 1; | ||
return { | ||
props: { | ||
views: req.session.views, | ||
}, | ||
}; | ||
} | ||
const server = http.createServer(async (req, res) => { | ||
const session = await getSession(req, res); | ||
res.end(`In this session, you have visited this website ${session.views} time(s).`; | ||
}); | ||
server.listen(8080); | ||
``` | ||
@@ -163,37 +157,19 @@ | ||
Regardless of the above approaches, to avoid bugs, you want to reuse the same `options` to in every route. For example: | ||
```javascript | ||
// Define the option only once | ||
// foo/bar/session.js | ||
export const options = { ...someOptions }; | ||
// Always import it at other places | ||
// pages/index.js | ||
import { options } from 'foo/bar/session'; | ||
/* ... */ | ||
export default withSession(Page, options); | ||
// pages/api/index.js | ||
import { options } from 'foo/bar/session'; | ||
/* ... */ | ||
await applySession(req, res, options); | ||
``` | ||
`next-session` accepts the properties below. | ||
| options | description | default | | ||
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | | ||
| name | The name of the cookie to be read from the request and set to the response. | `sid` | | ||
| store | The session store instance to be used. | `MemoryStore` | | ||
| genid | The function that generates a string for a new session ID. | [`nanoid`](https://github.com/ai/nanoid) | | ||
| encode | Transforms session ID before setting cookie. It takes the raw session ID and returns the decoded/decrypted session ID. | undefined | | ||
| decode | Transforms session ID back while getting from cookie. It should return the encoded/encrypted session ID | undefined | | ||
| touchAfter | Only touch after an amount of time **(in miliseconds)** since last access. Disabled by default or if set to `-1`. See [touchAfter](#touchAfter). | `-1` (Disabled) | | ||
| autoCommit | Automatically commit session. Disable this if you want to manually `session.commit()` | `true` | | ||
| cookie.secure | Specifies the boolean value for the **Secure** `Set-Cookie` attribute. | `false` | | ||
| cookie.httpOnly | Specifies the boolean value for the **httpOnly** `Set-Cookie` attribute. | `true` | | ||
| cookie.path | Specifies the value for the **Path** `Set-Cookie` attribute. | `/` | | ||
| cookie.domain | Specifies the value for the **Domain** `Set-Cookie` attribute. | unset | | ||
| cookie.sameSite | Specifies the value for the **SameSite** `Set-Cookie` attribute. | unset | | ||
| cookie.maxAge | **(in seconds)** Specifies the value for the **Max-Age** `Set-Cookie` attribute. | unset (Browser session) | | ||
| options | description | default | | ||
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | | ||
| name | The name of the cookie to be read from the request and set to the response. | `sid` | | ||
| store | The session store instance to be used. **Required** to work in production! | `MemoryStore` | | ||
| genid | The function that generates a string for a new session ID. | [`nanoid`](https://github.com/ai/nanoid) | | ||
| encode | Transforms session ID before setting cookie. It takes the raw session ID and returns the decoded/decrypted session ID. | undefined | | ||
| decode | Transforms session ID back while getting from cookie. It should return the encoded/encrypted session ID | undefined | | ||
| touchAfter | Only touch after an amount of time **(in seconds)** since last access. Disabled by default or if set to `-1`. See [touchAfter](#touchAfter). | `-1` (Disabled) | | ||
| autoCommit | Automatically commit session. Disable this if you want to manually `session.commit()` | `true` | | ||
| cookie.secure | Specifies the boolean value for the **Secure** `Set-Cookie` attribute. | `false` | | ||
| cookie.httpOnly | Specifies the boolean value for the **httpOnly** `Set-Cookie` attribute. | `true` | | ||
| cookie.path | Specifies the value for the **Path** `Set-Cookie` attribute. | `/` | | ||
| cookie.domain | Specifies the value for the **Domain** `Set-Cookie` attribute. | unset | | ||
| cookie.sameSite | Specifies the value for the **SameSite** `Set-Cookie` attribute. | unset | | ||
| cookie.maxAge | **(in seconds)** Specifies the value for the **Max-Age** `Set-Cookie` attribute. | unset (Browser session) | | ||
@@ -212,7 +188,7 @@ ### touchAfter | ||
// `express-session` signing strategy | ||
const signature = require('cookie-signature'); | ||
const secret = 'keyboard cat'; | ||
const signature = require("cookie-signature"); | ||
const secret = "keyboard cat"; | ||
session({ | ||
decode: (raw) => signature.unsign(raw.slice(2), secret), | ||
encode: (sid) => (sid ? 's:' + signature.sign(sid, secret) : null), | ||
encode: (sid) => (sid ? "s:" + signature.sign(sid, secret) : null), | ||
}); | ||
@@ -223,3 +199,3 @@ ``` | ||
### req.session | ||
### session object | ||
@@ -230,16 +206,26 @@ This allows you to **set** or **get** a specific value that associates to the current session. | ||
// Set a value | ||
if (loggedIn) req.session.user = 'John Doe'; | ||
if (loggedIn) session.user = "John Doe"; | ||
// Get a value | ||
const currentUser = req.session.user; // "John Doe" | ||
const currentUser = session.user; // "John Doe" | ||
``` | ||
### req.session.destroy() | ||
### session.touch() | ||
Manually extends the session expiry by maxAge. **Note:** You must still call session.commit() if `autoCommit = false`. | ||
```js | ||
session.touch(); | ||
``` | ||
If `touchAfter` is set with a non-negative value, this will be automatically called accordingly. | ||
### session.destroy() | ||
Destroy to current session and remove it from session store. | ||
```javascript | ||
if (loggedOut) await req.session.destroy(); | ||
if (loggedOut) await session.destroy(); | ||
``` | ||
### req.session.commit() | ||
### session.commit() | ||
@@ -251,15 +237,11 @@ Save the session and set neccessary headers. Return Promise. It must be called before _sending the headers (`res.writeHead`) or response (`res.send`, `res.end`, etc.)_. | ||
```javascript | ||
req.session.hello = 'world'; | ||
await req.session.commit(); | ||
session.hello = "world"; | ||
await session.commit(); | ||
// always calling res.end or res.writeHead after the above | ||
``` | ||
### req.session.id | ||
### session.id | ||
The unique id that associates to the current session. | ||
### req.session.isNew | ||
Return _true_ if the session is new. | ||
## Session Store | ||
@@ -269,35 +251,50 @@ | ||
### Compatibility with Express/Connect stores | ||
### Implementation | ||
To use [Express/Connect stores](https://github.com/expressjs/session#compatible-session-stores), you may need to use `expressSession` from `next-session` if the store has the following pattern. | ||
A compatible session store must include three functions: `set(sid, session)`, `get(sid)`, and `destroy(sid)`. The function `touch(sid, session)` is recommended. All functions must return **Promises**. | ||
```javascript | ||
const session = require('express-session'); | ||
const MongoStore = require('connect-mongo')(session); | ||
Refer to [MemoryStore](https://github.com/hoangvvo/next-session/blob/master/src/memory-store.ts). | ||
// Use `expressSession` as the replacement | ||
_TypeScript:_ the `SessionStore` type can be used to aid implementation: | ||
import { expressSession } from 'next-session'; | ||
const MongoStore = require('connect-mongo')(expressSession); | ||
```ts | ||
import type { SessionStore } from "next-session"; | ||
class CustomStore implements SessionStore {} | ||
``` | ||
### Implementation | ||
### Compatibility with Express/Connect stores | ||
A compatible session store must include three functions: `set(sid, session)`, `get(sid)`, and `destroy(sid)`. The function `touch(sid, session)` is recommended. All functions can either return **Promises** or allowing **callback** in the last argument. | ||
#### Promisify functions | ||
To use [Express/Connect stores](https://github.com/expressjs/session#compatible-session-stores), you must promisify `get`, `set`, `destroy`, and (if exists) `touch` methods, possibly using [`util.promisify`](https://nodejs.org/dist/latest/docs/api/util.html#util_util_promisify_original). | ||
We include the util [`promisifyStore`](./src/compat.ts#L29) in `next-session/lib/compat` to do just that: | ||
```js | ||
// Both of the below work! | ||
import nextSession from "next-session"; | ||
import { promisifyStore } from "next-session/lib/compat"; | ||
import SomeConnectStore from "connect-xyz"; | ||
function get(sid) { | ||
return promiseGetFn(sid); | ||
} | ||
const connectStore = new SomeConnectStore(); | ||
function get(sid, done) { | ||
cbGetFn(sid, done); | ||
} | ||
const getSession = nextSession({ | ||
store: promisifyStore(connectStore), | ||
}); | ||
``` | ||
Refer to [MemoryStore](https://github.com/hoangvvo/next-session/blob/master/src/store/memory.ts) | ||
or the type of [SessionStore](https://github.com/hoangvvo/next-session/blob/master/src/types.ts#L23-L29). | ||
You can use `expressSession` from `next-session/lib/compat` if the connect store has the following pattern. | ||
```javascript | ||
const session = require("express-session"); | ||
const RedisStore = require("connect-redis")(session); | ||
// Use `expressSession` from `next-session/lib/compat` as the replacement | ||
import { expressSession } from "next-session/lib/compat"; | ||
import RedisStoreFactory from "connect-redis"; | ||
const RedisStore = RedisStoreFactory(expressSession); | ||
``` | ||
## Contributing | ||
@@ -304,0 +301,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
33774
15
21
514
Yes
297
1