@prairielearn/flash 1.0.5 → 1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @prairielearn/flash
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0f5ef9c18: Add support for html-safe flash messages
8
+
9
+ ## 1.0.6
10
+
11
+ ### Patch Changes
12
+
13
+ - bd0053577: Upgrade all dependencies
14
+
3
15
  ## 1.0.5
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -34,7 +34,7 @@ app.get('/set-flash', (req, res) => {
34
34
  flash('notice', 'Your preferences have been updated.');
35
35
  flash('success', 'Course created successfully.');
36
36
  flash('warning', 'Syncing completed with 5 warnings.');
37
- flash('error', 'Group must have fewer than 10 members.');
37
+ flash('error', html`Group must have <em>fewer than 10 members</em>.`);
38
38
 
39
39
  res.redirect('/display-flash');
40
40
  });
@@ -47,7 +47,7 @@ app.get('/display-flash', (req, res) => {
47
47
 
48
48
  The `flash()` function has three behaviors:
49
49
 
50
- - `flash(type: string, message: string)`: Set a message with the given type.
50
+ - `flash(type: string, message: string | HtmlSafeString)`: Set a message with the given type.
51
51
  - `flash(type: string): FlashMessage[]`: Get all messages with the given type.
52
52
  - `flash(types: string[]): FlashMessage[]`: Get all messages with any of the given types.
53
53
  - `flash(): FlashMessage[]`: Get all messages.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
+ import { HtmlSafeString } from '@prairielearn/html';
2
3
  export type FlashMessageType = 'notice' | 'success' | 'warning' | 'error';
3
4
  export interface FlashMessage {
4
5
  type: FlashMessageType;
@@ -6,4 +7,4 @@ export interface FlashMessage {
6
7
  }
7
8
  export declare function flashMiddleware(): (req: Request, _res: Response, next: NextFunction) => void;
8
9
  export declare function flash(type?: FlashMessageType | FlashMessageType[]): FlashMessage[];
9
- export declare function flash(type: FlashMessageType, message: string): void;
10
+ export declare function flash(type: FlashMessageType, message: string | HtmlSafeString): void;
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.flash = exports.flashMiddleware = void 0;
4
4
  const node_async_hooks_1 = require("node:async_hooks");
5
+ const html_1 = require("@prairielearn/html");
5
6
  const als = new node_async_hooks_1.AsyncLocalStorage();
6
7
  function flashMiddleware() {
7
8
  return (req, _res, next) => {
@@ -42,7 +43,7 @@ function makeFlashStorage(req) {
42
43
  return {
43
44
  add(type, message) {
44
45
  session.flash ??= [];
45
- session.flash.push({ type, message });
46
+ session.flash.push({ type, message: (0, html_1.html) `${message}`.toString() });
46
47
  },
47
48
  get(type) {
48
49
  const messages = session.flash ?? [];
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uDAAqD;AAGrD,MAAM,GAAG,GAAG,IAAI,oCAAiB,EAAgB,CAAC;AASlD,SAAgB,eAAe;IAC7B,OAAO,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB,EAAE,EAAE;QAC1D,MAAM,YAAY,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAC3C,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC,CAAC;AACJ,CAAC;AALD,0CAKC;AAID,SAAgB,KAAK,CAAC,IAA4C,EAAE,OAAgB;IAClF,MAAM,YAAY,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IACpC,IAAI,CAAC,YAAY,EAAE;QACjB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;KAC5D;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAChE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,OAAO,QAAQ,CAAC;KACjB;IAED,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,EAAE;QACnC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAChC,OAAO;KACR;IAED,IAAI,IAAI,IAAI,IAAI,EAAE;QAChB,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,OAAO,CAAC;KAChB;IAED,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC;IACvC,YAAY,CAAC,QAAQ,EAAE,CAAC;IACxB,OAAO,QAAQ,CAAC;AAClB,CAAC;AA1BD,sBA0BC;AAUD,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE;QAChB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;KACjE;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,OAAc,CAAC;IAEnC,OAAO;QACL,GAAG,CAAC,IAAsB,EAAE,OAAe;YACzC,OAAO,CAAC,KAAK,KAAK,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACxC,CAAC;QACD,GAAG,CAAC,IAAsB;YACxB,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YACrC,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC3E,CAAC;QACD,MAAM;YACJ,OAAO,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7B,CAAC;QACD,KAAK,CAAC,IAAsB;YAC1B,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;QAChG,CAAC;QACD,QAAQ;YACN,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC;QACrB,CAAC;KACqB,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uDAAqD;AAErD,6CAA0D;AAE1D,MAAM,GAAG,GAAG,IAAI,oCAAiB,EAAgB,CAAC;AASlD,SAAgB,eAAe;IAC7B,OAAO,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB,EAAE,EAAE;QAC1D,MAAM,YAAY,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAC3C,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC,CAAC;AACJ,CAAC;AALD,0CAKC;AAID,SAAgB,KAAK,CACnB,IAA4C,EAC5C,OAAiC;IAEjC,MAAM,YAAY,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IACpC,IAAI,CAAC,YAAY,EAAE;QACjB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;KAC5D;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAChE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,OAAO,QAAQ,CAAC;KACjB;IAED,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,EAAE;QACnC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAChC,OAAO;KACR;IAED,IAAI,IAAI,IAAI,IAAI,EAAE;QAChB,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,OAAO,CAAC;KAChB;IAED,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC;IACvC,YAAY,CAAC,QAAQ,EAAE,CAAC;IACxB,OAAO,QAAQ,CAAC;AAClB,CAAC;AA7BD,sBA6BC;AAUD,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE;QAChB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;KACjE;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,OAAc,CAAC;IAEnC,OAAO;QACL,GAAG,CAAC,IAAsB,EAAE,OAAgC;YAC1D,OAAO,CAAC,KAAK,KAAK,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAA,WAAI,EAAA,GAAG,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,GAAG,CAAC,IAAsB;YACxB,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YACrC,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC3E,CAAC;QACD,MAAM;YACJ,OAAO,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7B,CAAC;QACD,KAAK,CAAC,IAAsB;YAC1B,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;QAChG,CAAC;QACD,QAAQ;YACN,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC;QACrB,CAAC;KACqB,CAAC;AAC3B,CAAC"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const chai_1 = require("chai");
4
4
  const index_1 = require("./index");
5
+ const html_1 = require("@prairielearn/html");
5
6
  describe('flash', () => {
6
7
  it('throws an error if no session present', () => {
7
8
  chai_1.assert.throw(() => {
@@ -12,8 +13,11 @@ describe('flash', () => {
12
13
  chai_1.assert.throw(() => {
13
14
  (0, index_1.flash)('notice', 'Hello world');
14
15
  });
16
+ chai_1.assert.throw(() => {
17
+ (0, index_1.flash)('notice', (0, html_1.html) `<p>hello ${'&'} world</p>`);
18
+ });
15
19
  });
16
- it('adds a flash', () => {
20
+ it('adds a flash using string message', () => {
17
21
  const req = {
18
22
  session: {},
19
23
  };
@@ -23,6 +27,28 @@ describe('flash', () => {
23
27
  chai_1.assert.sameDeepMembers((0, index_1.flash)(), [{ type: 'notice', message: 'hello world' }]);
24
28
  });
25
29
  });
30
+ it('adds a flash and escapes unsafe HTML', () => {
31
+ const req = {
32
+ session: {},
33
+ };
34
+ const res = {};
35
+ (0, index_1.flashMiddleware)()(req, res, () => {
36
+ (0, index_1.flash)('notice', '<b>hello world</b>');
37
+ chai_1.assert.sameDeepMembers((0, index_1.flash)(), [
38
+ { type: 'notice', message: '&lt;b&gt;hello world&lt;/b&gt;' },
39
+ ]);
40
+ });
41
+ });
42
+ it('adds a flash with HTML-safe message', () => {
43
+ const req = {
44
+ session: {},
45
+ };
46
+ const res = {};
47
+ (0, index_1.flashMiddleware)()(req, res, () => {
48
+ (0, index_1.flash)('notice', (0, html_1.html) `<p>hello ${'&'} world</p>`);
49
+ chai_1.assert.sameDeepMembers((0, index_1.flash)(), [{ type: 'notice', message: '<p>hello &amp; world</p>' }]);
50
+ });
51
+ });
26
52
  it('stores multiples flashes with the same type', () => {
27
53
  const req = {
28
54
  session: {},
@@ -30,10 +56,12 @@ describe('flash', () => {
30
56
  const res = {};
31
57
  (0, index_1.flashMiddleware)()(req, res, () => {
32
58
  (0, index_1.flash)('notice', 'hello world');
33
- (0, index_1.flash)('notice', 'goodbye world');
59
+ (0, index_1.flash)('notice', '<< goodbye world');
60
+ (0, index_1.flash)('notice', (0, html_1.html) `<p>hello ${'&'} world</p>`);
34
61
  chai_1.assert.sameDeepMembers((0, index_1.flash)(), [
35
62
  { type: 'notice', message: 'hello world' },
36
- { type: 'notice', message: 'goodbye world' },
63
+ { type: 'notice', message: '&lt;&lt; goodbye world' },
64
+ { type: 'notice', message: '<p>hello &amp; world</p>' },
37
65
  ]);
38
66
  });
39
67
  });
@@ -44,9 +72,15 @@ describe('flash', () => {
44
72
  const res = {};
45
73
  (0, index_1.flashMiddleware)()(req, res, () => {
46
74
  (0, index_1.flash)('notice', 'hello world');
47
- (0, index_1.flash)('error', 'goodbye world');
75
+ (0, index_1.flash)('error', '<< goodbye world');
76
+ (0, index_1.flash)('success', (0, html_1.html) `<p>hello ${'&'} world</p>`);
48
77
  chai_1.assert.sameDeepMembers((0, index_1.flash)('notice'), [{ type: 'notice', message: 'hello world' }]);
49
- chai_1.assert.sameDeepMembers((0, index_1.flash)('error'), [{ type: 'error', message: 'goodbye world' }]);
78
+ chai_1.assert.sameDeepMembers((0, index_1.flash)('error'), [
79
+ { type: 'error', message: '&lt;&lt; goodbye world' },
80
+ ]);
81
+ chai_1.assert.sameDeepMembers((0, index_1.flash)('success'), [
82
+ { type: 'success', message: '<p>hello &amp; world</p>' },
83
+ ]);
50
84
  });
51
85
  });
52
86
  it('returns all flashes', () => {
@@ -56,10 +90,12 @@ describe('flash', () => {
56
90
  const res = {};
57
91
  (0, index_1.flashMiddleware)()(req, res, () => {
58
92
  (0, index_1.flash)('notice', 'hello world');
59
- (0, index_1.flash)('error', 'goodbye world');
93
+ (0, index_1.flash)('error', '<< goodbye world');
94
+ (0, index_1.flash)('success', (0, html_1.html) `<p>hello ${'&'} world</p>`);
60
95
  chai_1.assert.sameDeepMembers((0, index_1.flash)(), [
61
96
  { type: 'notice', message: 'hello world' },
62
- { type: 'error', message: 'goodbye world' },
97
+ { type: 'error', message: '&lt;&lt; goodbye world' },
98
+ { type: 'success', message: '<p>hello &amp; world</p>' },
63
99
  ]);
64
100
  });
65
101
  });
@@ -70,11 +106,18 @@ describe('flash', () => {
70
106
  const res = {};
71
107
  (0, index_1.flashMiddleware)()(req, res, () => {
72
108
  (0, index_1.flash)('notice', 'hello world');
73
- (0, index_1.flash)('error', 'goodbye world');
109
+ (0, index_1.flash)('error', '<< goodbye world');
110
+ (0, index_1.flash)('success', (0, html_1.html) `<p>hello ${'&'} world</p>`);
74
111
  chai_1.assert.sameDeepMembers((0, index_1.flash)('notice'), [{ type: 'notice', message: 'hello world' }]);
75
- chai_1.assert.sameDeepMembers((0, index_1.flash)('error'), [{ type: 'error', message: 'goodbye world' }]);
112
+ chai_1.assert.sameDeepMembers((0, index_1.flash)('error'), [
113
+ { type: 'error', message: '&lt;&lt; goodbye world' },
114
+ ]);
115
+ chai_1.assert.sameDeepMembers((0, index_1.flash)('success'), [
116
+ { type: 'success', message: '<p>hello &amp; world</p>' },
117
+ ]);
76
118
  chai_1.assert.deepEqual((0, index_1.flash)('notice'), []);
77
119
  chai_1.assert.deepEqual((0, index_1.flash)('error'), []);
120
+ chai_1.assert.deepEqual((0, index_1.flash)('success'), []);
78
121
  chai_1.assert.isEmpty((0, index_1.flash)());
79
122
  });
80
123
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/flash",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "main": "dist/index.js",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,11 +12,14 @@
12
12
  "dev": "tsc --watch --preserveWatchOutput",
13
13
  "test": "mocha --no-config --require ts-node/register src/index.test.ts"
14
14
  },
15
+ "dependencies": {
16
+ "@prairielearn/html": "^3.0.8"
17
+ },
15
18
  "devDependencies": {
16
19
  "@prairielearn/tsconfig": "^0.0.0",
17
- "@types/express": "^4.17.17",
18
- "@types/express-session": "^1.17.7",
19
- "@types/node": "^18.17.14",
20
+ "@types/express": "^4.17.18",
21
+ "@types/express-session": "^1.17.8",
22
+ "@types/node": "^18.18.2",
20
23
  "mocha": "^10.2.0",
21
24
  "ts-node": "^10.9.1",
22
25
  "typescript": "^5.2.2"
package/src/index.test.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { assert } from 'chai';
2
2
 
3
3
  import { flashMiddleware, flash } from './index';
4
+ import { html } from '@prairielearn/html';
4
5
 
5
6
  describe('flash', () => {
6
7
  it('throws an error if no session present', () => {
@@ -13,9 +14,12 @@ describe('flash', () => {
13
14
  assert.throw(() => {
14
15
  flash('notice', 'Hello world');
15
16
  });
17
+ assert.throw(() => {
18
+ flash('notice', html`<p>hello ${'&'} world</p>`);
19
+ });
16
20
  });
17
21
 
18
- it('adds a flash', () => {
22
+ it('adds a flash using string message', () => {
19
23
  const req = {
20
24
  session: {},
21
25
  } as any;
@@ -28,6 +32,34 @@ describe('flash', () => {
28
32
  });
29
33
  });
30
34
 
35
+ it('adds a flash and escapes unsafe HTML', () => {
36
+ const req = {
37
+ session: {},
38
+ } as any;
39
+ const res = {} as any;
40
+
41
+ flashMiddleware()(req, res, () => {
42
+ flash('notice', '<b>hello world</b>');
43
+
44
+ assert.sameDeepMembers(flash(), [
45
+ { type: 'notice', message: '&lt;b&gt;hello world&lt;/b&gt;' },
46
+ ]);
47
+ });
48
+ });
49
+
50
+ it('adds a flash with HTML-safe message', () => {
51
+ const req = {
52
+ session: {},
53
+ } as any;
54
+ const res = {} as any;
55
+
56
+ flashMiddleware()(req, res, () => {
57
+ flash('notice', html`<p>hello ${'&'} world</p>`);
58
+
59
+ assert.sameDeepMembers(flash(), [{ type: 'notice', message: '<p>hello &amp; world</p>' }]);
60
+ });
61
+ });
62
+
31
63
  it('stores multiples flashes with the same type', () => {
32
64
  const req = {
33
65
  session: {},
@@ -36,11 +68,13 @@ describe('flash', () => {
36
68
 
37
69
  flashMiddleware()(req, res, () => {
38
70
  flash('notice', 'hello world');
39
- flash('notice', 'goodbye world');
71
+ flash('notice', '<< goodbye world');
72
+ flash('notice', html`<p>hello ${'&'} world</p>`);
40
73
 
41
74
  assert.sameDeepMembers(flash(), [
42
75
  { type: 'notice', message: 'hello world' },
43
- { type: 'notice', message: 'goodbye world' },
76
+ { type: 'notice', message: '&lt;&lt; goodbye world' },
77
+ { type: 'notice', message: '<p>hello &amp; world</p>' },
44
78
  ]);
45
79
  });
46
80
  });
@@ -53,10 +87,16 @@ describe('flash', () => {
53
87
 
54
88
  flashMiddleware()(req, res, () => {
55
89
  flash('notice', 'hello world');
56
- flash('error', 'goodbye world');
90
+ flash('error', '<< goodbye world');
91
+ flash('success', html`<p>hello ${'&'} world</p>`);
57
92
 
58
93
  assert.sameDeepMembers(flash('notice'), [{ type: 'notice', message: 'hello world' }]);
59
- assert.sameDeepMembers(flash('error'), [{ type: 'error', message: 'goodbye world' }]);
94
+ assert.sameDeepMembers(flash('error'), [
95
+ { type: 'error', message: '&lt;&lt; goodbye world' },
96
+ ]);
97
+ assert.sameDeepMembers(flash('success'), [
98
+ { type: 'success', message: '<p>hello &amp; world</p>' },
99
+ ]);
60
100
  });
61
101
  });
62
102
 
@@ -68,11 +108,13 @@ describe('flash', () => {
68
108
 
69
109
  flashMiddleware()(req, res, () => {
70
110
  flash('notice', 'hello world');
71
- flash('error', 'goodbye world');
111
+ flash('error', '<< goodbye world');
112
+ flash('success', html`<p>hello ${'&'} world</p>`);
72
113
 
73
114
  assert.sameDeepMembers(flash(), [
74
115
  { type: 'notice', message: 'hello world' },
75
- { type: 'error', message: 'goodbye world' },
116
+ { type: 'error', message: '&lt;&lt; goodbye world' },
117
+ { type: 'success', message: '<p>hello &amp; world</p>' },
76
118
  ]);
77
119
  });
78
120
  });
@@ -85,13 +127,20 @@ describe('flash', () => {
85
127
 
86
128
  flashMiddleware()(req, res, () => {
87
129
  flash('notice', 'hello world');
88
- flash('error', 'goodbye world');
130
+ flash('error', '<< goodbye world');
131
+ flash('success', html`<p>hello ${'&'} world</p>`);
89
132
 
90
133
  assert.sameDeepMembers(flash('notice'), [{ type: 'notice', message: 'hello world' }]);
91
- assert.sameDeepMembers(flash('error'), [{ type: 'error', message: 'goodbye world' }]);
134
+ assert.sameDeepMembers(flash('error'), [
135
+ { type: 'error', message: '&lt;&lt; goodbye world' },
136
+ ]);
137
+ assert.sameDeepMembers(flash('success'), [
138
+ { type: 'success', message: '<p>hello &amp; world</p>' },
139
+ ]);
92
140
 
93
141
  assert.deepEqual(flash('notice'), []);
94
142
  assert.deepEqual(flash('error'), []);
143
+ assert.deepEqual(flash('success'), []);
95
144
  assert.isEmpty(flash());
96
145
  });
97
146
  });
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { Request, Response, NextFunction } from 'express';
3
+ import { HtmlSafeString, html } from '@prairielearn/html';
3
4
 
4
5
  const als = new AsyncLocalStorage<FlashStorage>();
5
6
 
@@ -18,8 +19,11 @@ export function flashMiddleware() {
18
19
  }
19
20
 
20
21
  export function flash(type?: FlashMessageType | FlashMessageType[]): FlashMessage[];
21
- export function flash(type: FlashMessageType, message: string): void;
22
- export function flash(type?: FlashMessageType | FlashMessageType[], message?: string) {
22
+ export function flash(type: FlashMessageType, message: string | HtmlSafeString): void;
23
+ export function flash(
24
+ type?: FlashMessageType | FlashMessageType[],
25
+ message?: string | HtmlSafeString,
26
+ ) {
23
27
  const flashStorage = als.getStore();
24
28
  if (!flashStorage) {
25
29
  throw new Error('flash() must be called within a request');
@@ -48,7 +52,7 @@ export function flash(type?: FlashMessageType | FlashMessageType[], message?: st
48
52
  }
49
53
 
50
54
  interface FlashStorage {
51
- add(type: FlashMessageType, message: string): void;
55
+ add(type: FlashMessageType, message: string | HtmlSafeString): void;
52
56
  get(type: FlashMessageType): FlashMessage[] | null;
53
57
  getAll(): FlashMessage[];
54
58
  clear(type: FlashMessageType): void;
@@ -63,9 +67,9 @@ function makeFlashStorage(req: Request): FlashStorage {
63
67
  const session = req.session as any;
64
68
 
65
69
  return {
66
- add(type: FlashMessageType, message: string) {
70
+ add(type: FlashMessageType, message: string | HtmlSafeString) {
67
71
  session.flash ??= [];
68
- session.flash.push({ type, message });
72
+ session.flash.push({ type, message: html`${message}`.toString() });
69
73
  },
70
74
  get(type: FlashMessageType) {
71
75
  const messages = session.flash ?? [];