@prairielearn/flash 2.0.11 → 2.0.12

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @prairielearn/flash
2
2
 
3
+ ## 2.0.12
4
+
5
+ ### Patch Changes
6
+
7
+ - cbd4c6c: Fix memory leak caused by holding reference to HTTP request
8
+
3
9
  ## 2.0.11
4
10
 
5
11
  ### Patch Changes
package/dist/index.js CHANGED
@@ -31,26 +31,47 @@ export function flash(type, message) {
31
31
  return messages;
32
32
  }
33
33
  function makeFlashStorage(req) {
34
- const session = req.session;
35
- if (!session) {
36
- throw new Error('@prairielearn/flash requires session support');
34
+ // The "flash storage" object will be stored in `AsyncLocalStorage` for the
35
+ // request. If we start some async I/O during a request (such as opening a
36
+ // database connection or a socket connection to Docker), that would by
37
+ // default keep the async context, and thus the request. This would prevent
38
+ // the request object (and any data associated with it) from being garbage
39
+ // collected. We use a `WeakRef` to fix this.
40
+ //
41
+ // This should always be safe, as the request object won't be garbage
42
+ // collected until the response has been sent, and we should never be reading
43
+ // or writing flash messages after the response has been sent.
44
+ const reqRef = new WeakRef(req);
45
+ function getSession() {
46
+ const req = reqRef.deref();
47
+ if (!req)
48
+ throw new Error('Request has been garbage collected');
49
+ const session = req.session;
50
+ if (!session)
51
+ throw new Error('No session found on request');
52
+ return session;
37
53
  }
38
54
  return {
39
55
  add(type, message) {
56
+ const session = getSession();
40
57
  session.flash ??= [];
41
58
  session.flash.push({ type, message: html `${message}`.toString() });
42
59
  },
43
60
  get(type) {
61
+ const session = getSession();
44
62
  const messages = session.flash ?? [];
45
63
  return messages.filter((message) => message.type === type);
46
64
  },
47
65
  getAll() {
66
+ const session = getSession();
48
67
  return session.flash ?? [];
49
68
  },
50
69
  clear(type) {
70
+ const session = getSession();
51
71
  session.flash = session.flash?.filter((message) => message.type !== type) ?? [];
52
72
  },
53
73
  clearAll() {
74
+ const session = getSession();
54
75
  session.flash = [];
55
76
  },
56
77
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { Request, Response, NextFunction } from 'express';\n\nimport { type HtmlSafeString, html } from '@prairielearn/html';\n\nconst als = new AsyncLocalStorage<FlashStorage>();\n\nexport type FlashMessageType = 'notice' | 'success' | 'warning' | 'error';\n\nexport interface FlashMessage {\n type: FlashMessageType;\n message: string;\n}\n\nexport function flashMiddleware() {\n return (req: Request, _res: Response, next: NextFunction) => {\n const flashStorage = makeFlashStorage(req);\n als.run(flashStorage, () => next());\n };\n}\n\nexport function flash(type?: FlashMessageType | FlashMessageType[]): FlashMessage[];\nexport function flash(type: FlashMessageType, message: string | HtmlSafeString): void;\nexport function flash(\n type?: FlashMessageType | FlashMessageType[],\n message?: string | HtmlSafeString,\n) {\n const flashStorage = als.getStore();\n if (!flashStorage) {\n throw new Error('flash() must be called within a request');\n }\n\n if (Array.isArray(type)) {\n const messages = type.flatMap((type) => flashStorage.get(type));\n type.forEach((t) => flashStorage.clear(t));\n return messages;\n }\n\n if (type != null && message != null) {\n flashStorage.add(type, message);\n return;\n }\n\n if (type != null) {\n const message = flashStorage.get(type);\n flashStorage.clear(type);\n return message;\n }\n\n const messages = flashStorage.getAll();\n flashStorage.clearAll();\n return messages;\n}\n\ninterface FlashStorage {\n add(type: FlashMessageType, message: string | HtmlSafeString): void;\n get(type: FlashMessageType): FlashMessage[] | null;\n getAll(): FlashMessage[];\n clear(type: FlashMessageType): void;\n clearAll(): void;\n}\n\nfunction makeFlashStorage(req: Request): FlashStorage {\n const session = (req as any).session;\n\n if (!session) {\n throw new Error('@prairielearn/flash requires session support');\n }\n\n return {\n add(type: FlashMessageType, message: string | HtmlSafeString) {\n session.flash ??= [];\n session.flash.push({ type, message: html`${message}`.toString() });\n },\n get(type: FlashMessageType) {\n const messages = session.flash ?? [];\n return messages.filter((message: FlashMessage) => message.type === type);\n },\n getAll() {\n return session.flash ?? [];\n },\n clear(type: FlashMessageType) {\n session.flash = session.flash?.filter((message: FlashMessage) => message.type !== type) ?? [];\n },\n clearAll() {\n session.flash = [];\n },\n } satisfies FlashStorage;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { Request, Response, NextFunction } from 'express';\n\nimport { type HtmlSafeString, html } from '@prairielearn/html';\n\nconst als = new AsyncLocalStorage<FlashStorage>();\n\nexport type FlashMessageType = 'notice' | 'success' | 'warning' | 'error';\n\nexport interface FlashMessage {\n type: FlashMessageType;\n message: string;\n}\n\nexport function flashMiddleware() {\n return (req: Request, _res: Response, next: NextFunction) => {\n const flashStorage = makeFlashStorage(req);\n als.run(flashStorage, () => next());\n };\n}\n\nexport function flash(type?: FlashMessageType | FlashMessageType[]): FlashMessage[];\nexport function flash(type: FlashMessageType, message: string | HtmlSafeString): void;\nexport function flash(\n type?: FlashMessageType | FlashMessageType[],\n message?: string | HtmlSafeString,\n) {\n const flashStorage = als.getStore();\n if (!flashStorage) {\n throw new Error('flash() must be called within a request');\n }\n\n if (Array.isArray(type)) {\n const messages = type.flatMap((type) => flashStorage.get(type));\n type.forEach((t) => flashStorage.clear(t));\n return messages;\n }\n\n if (type != null && message != null) {\n flashStorage.add(type, message);\n return;\n }\n\n if (type != null) {\n const message = flashStorage.get(type);\n flashStorage.clear(type);\n return message;\n }\n\n const messages = flashStorage.getAll();\n flashStorage.clearAll();\n return messages;\n}\n\ninterface FlashStorage {\n add(type: FlashMessageType, message: string | HtmlSafeString): void;\n get(type: FlashMessageType): FlashMessage[] | null;\n getAll(): FlashMessage[];\n clear(type: FlashMessageType): void;\n clearAll(): void;\n}\n\nfunction makeFlashStorage(req: Request): FlashStorage {\n // The \"flash storage\" object will be stored in `AsyncLocalStorage` for the\n // request. If we start some async I/O during a request (such as opening a\n // database connection or a socket connection to Docker), that would by\n // default keep the async context, and thus the request. This would prevent\n // the request object (and any data associated with it) from being garbage\n // collected. We use a `WeakRef` to fix this.\n //\n // This should always be safe, as the request object won't be garbage\n // collected until the response has been sent, and we should never be reading\n // or writing flash messages after the response has been sent.\n const reqRef = new WeakRef(req);\n\n function getSession() {\n const req = reqRef.deref();\n if (!req) throw new Error('Request has been garbage collected');\n const session = (req as any).session;\n if (!session) throw new Error('No session found on request');\n return session;\n }\n\n return {\n add(type: FlashMessageType, message: string | HtmlSafeString) {\n const session = getSession();\n session.flash ??= [];\n session.flash.push({ type, message: html`${message}`.toString() });\n },\n get(type: FlashMessageType) {\n const session = getSession();\n const messages = session.flash ?? [];\n return messages.filter((message: FlashMessage) => message.type === type);\n },\n getAll() {\n const session = getSession();\n return session.flash ?? [];\n },\n clear(type: FlashMessageType) {\n const session = getSession();\n session.flash = session.flash?.filter((message: FlashMessage) => message.type !== type) ?? [];\n },\n clearAll() {\n const session = getSession();\n session.flash = [];\n },\n } satisfies FlashStorage;\n}\n"]}
@@ -4,16 +4,18 @@ import { flashMiddleware, flash } from './index.js';
4
4
  describe('flash', () => {
5
5
  it('throws an error if no session present', () => {
6
6
  assert.throw(() => {
7
- flashMiddleware()({}, {}, () => { });
8
- });
7
+ flashMiddleware()({}, {}, () => {
8
+ flash('notice', 'Hello world');
9
+ });
10
+ }, 'No session found on request');
9
11
  });
10
12
  it('throws an error when middleware is not used', () => {
11
13
  assert.throw(() => {
12
14
  flash('notice', 'Hello world');
13
- });
15
+ }, 'flash() must be called within a request');
14
16
  assert.throw(() => {
15
17
  flash('notice', html `<p>hello ${'&'} world</p>`);
16
- });
18
+ }, 'flash() must be called within a request');
17
19
  });
18
20
  it('adds a flash using string message', () => {
19
21
  const req = {
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert } from 'chai';\n\nimport { html } from '@prairielearn/html';\n\nimport { flashMiddleware, flash } from './index.js';\n\ndescribe('flash', () => {\n it('throws an error if no session present', () => {\n assert.throw(() => {\n flashMiddleware()({} as any, {} as any, () => {});\n });\n });\n\n it('throws an error when middleware is not used', () => {\n assert.throw(() => {\n flash('notice', 'Hello world');\n });\n assert.throw(() => {\n flash('notice', html`<p>hello ${'&'} world</p>`);\n });\n });\n\n it('adds a flash using string message', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n\n assert.sameDeepMembers(flash(), [{ type: 'notice', message: 'hello world' }]);\n });\n });\n\n it('adds a flash and escapes unsafe HTML', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', '<b>hello world</b>');\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: '&lt;b&gt;hello world&lt;/b&gt;' },\n ]);\n });\n });\n\n it('adds a flash with HTML-safe message', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [{ type: 'notice', message: '<p>hello &amp; world</p>' }]);\n });\n });\n\n it('stores multiples flashes with the same type', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('notice', '<< goodbye world');\n flash('notice', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: 'hello world' },\n { type: 'notice', message: '&lt;&lt; goodbye world' },\n { type: 'notice', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('returns flash message for a given type', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash('notice'), [{ type: 'notice', message: 'hello world' }]);\n assert.sameDeepMembers(flash('error'), [\n { type: 'error', message: '&lt;&lt; goodbye world' },\n ]);\n assert.sameDeepMembers(flash('success'), [\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('returns all flashes', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: 'hello world' },\n { type: 'error', message: '&lt;&lt; goodbye world' },\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('clears flash after it has been read', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash('notice'), [{ type: 'notice', message: 'hello world' }]);\n assert.sameDeepMembers(flash('error'), [\n { type: 'error', message: '&lt;&lt; goodbye world' },\n ]);\n assert.sameDeepMembers(flash('success'), [\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n\n assert.deepEqual(flash('notice'), []);\n assert.deepEqual(flash('error'), []);\n assert.deepEqual(flash('success'), []);\n assert.isEmpty(flash());\n });\n });\n});\n"]}
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert } from 'chai';\n\nimport { html } from '@prairielearn/html';\n\nimport { flashMiddleware, flash } from './index.js';\n\ndescribe('flash', () => {\n it('throws an error if no session present', () => {\n assert.throw(() => {\n flashMiddleware()({} as any, {} as any, () => {\n flash('notice', 'Hello world');\n });\n }, 'No session found on request');\n });\n\n it('throws an error when middleware is not used', () => {\n assert.throw(() => {\n flash('notice', 'Hello world');\n }, 'flash() must be called within a request');\n assert.throw(() => {\n flash('notice', html`<p>hello ${'&'} world</p>`);\n }, 'flash() must be called within a request');\n });\n\n it('adds a flash using string message', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n\n assert.sameDeepMembers(flash(), [{ type: 'notice', message: 'hello world' }]);\n });\n });\n\n it('adds a flash and escapes unsafe HTML', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', '<b>hello world</b>');\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: '&lt;b&gt;hello world&lt;/b&gt;' },\n ]);\n });\n });\n\n it('adds a flash with HTML-safe message', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [{ type: 'notice', message: '<p>hello &amp; world</p>' }]);\n });\n });\n\n it('stores multiples flashes with the same type', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('notice', '<< goodbye world');\n flash('notice', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: 'hello world' },\n { type: 'notice', message: '&lt;&lt; goodbye world' },\n { type: 'notice', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('returns flash message for a given type', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash('notice'), [{ type: 'notice', message: 'hello world' }]);\n assert.sameDeepMembers(flash('error'), [\n { type: 'error', message: '&lt;&lt; goodbye world' },\n ]);\n assert.sameDeepMembers(flash('success'), [\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('returns all flashes', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: 'hello world' },\n { type: 'error', message: '&lt;&lt; goodbye world' },\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('clears flash after it has been read', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash('notice'), [{ type: 'notice', message: 'hello world' }]);\n assert.sameDeepMembers(flash('error'), [\n { type: 'error', message: '&lt;&lt; goodbye world' },\n ]);\n assert.sameDeepMembers(flash('success'), [\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n\n assert.deepEqual(flash('notice'), []);\n assert.deepEqual(flash('error'), []);\n assert.deepEqual(flash('success'), []);\n assert.isEmpty(flash());\n });\n });\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/flash",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
package/src/index.test.ts CHANGED
@@ -7,17 +7,19 @@ import { flashMiddleware, flash } from './index.js';
7
7
  describe('flash', () => {
8
8
  it('throws an error if no session present', () => {
9
9
  assert.throw(() => {
10
- flashMiddleware()({} as any, {} as any, () => {});
11
- });
10
+ flashMiddleware()({} as any, {} as any, () => {
11
+ flash('notice', 'Hello world');
12
+ });
13
+ }, 'No session found on request');
12
14
  });
13
15
 
14
16
  it('throws an error when middleware is not used', () => {
15
17
  assert.throw(() => {
16
18
  flash('notice', 'Hello world');
17
- });
19
+ }, 'flash() must be called within a request');
18
20
  assert.throw(() => {
19
21
  flash('notice', html`<p>hello ${'&'} world</p>`);
20
- });
22
+ }, 'flash() must be called within a request');
21
23
  });
22
24
 
23
25
  it('adds a flash using string message', () => {
package/src/index.ts CHANGED
@@ -62,28 +62,47 @@ interface FlashStorage {
62
62
  }
63
63
 
64
64
  function makeFlashStorage(req: Request): FlashStorage {
65
- const session = (req as any).session;
65
+ // The "flash storage" object will be stored in `AsyncLocalStorage` for the
66
+ // request. If we start some async I/O during a request (such as opening a
67
+ // database connection or a socket connection to Docker), that would by
68
+ // default keep the async context, and thus the request. This would prevent
69
+ // the request object (and any data associated with it) from being garbage
70
+ // collected. We use a `WeakRef` to fix this.
71
+ //
72
+ // This should always be safe, as the request object won't be garbage
73
+ // collected until the response has been sent, and we should never be reading
74
+ // or writing flash messages after the response has been sent.
75
+ const reqRef = new WeakRef(req);
66
76
 
67
- if (!session) {
68
- throw new Error('@prairielearn/flash requires session support');
77
+ function getSession() {
78
+ const req = reqRef.deref();
79
+ if (!req) throw new Error('Request has been garbage collected');
80
+ const session = (req as any).session;
81
+ if (!session) throw new Error('No session found on request');
82
+ return session;
69
83
  }
70
84
 
71
85
  return {
72
86
  add(type: FlashMessageType, message: string | HtmlSafeString) {
87
+ const session = getSession();
73
88
  session.flash ??= [];
74
89
  session.flash.push({ type, message: html`${message}`.toString() });
75
90
  },
76
91
  get(type: FlashMessageType) {
92
+ const session = getSession();
77
93
  const messages = session.flash ?? [];
78
94
  return messages.filter((message: FlashMessage) => message.type === type);
79
95
  },
80
96
  getAll() {
97
+ const session = getSession();
81
98
  return session.flash ?? [];
82
99
  },
83
100
  clear(type: FlashMessageType) {
101
+ const session = getSession();
84
102
  session.flash = session.flash?.filter((message: FlashMessage) => message.type !== type) ?? [];
85
103
  },
86
104
  clearAll() {
105
+ const session = getSession();
87
106
  session.flash = [];
88
107
  },
89
108
  } satisfies FlashStorage;