@server/next 0.21.13 → 0.22.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.21.13",
3
+ "version": "0.22.0",
4
4
  "description": "A fully-fledged web server with routing, file uploads, sessions, static files, schema validation, websockets, testing, etc.",
5
5
  "homepage": "https://server-js.com/",
6
6
  "repository": "https://github.com/franciscop/server-next.git",
@@ -14,19 +14,26 @@ export default function config(options = {}) {
14
14
  options.cors = options.cors || env.CORS || null;
15
15
  if (options.cors) {
16
16
  if (options.cors === true) {
17
- options.cors = { origin: true };
17
+ options.cors = { origin: options.cors };
18
18
  }
19
19
  if (typeof options.cors === "string") {
20
20
  options.cors = { origin: options.cors };
21
21
  }
22
+ if (Array.isArray(options.cors)) {
23
+ options.cors = { origin: options.cors };
24
+ }
25
+ if (Array.isArray(options.cors.origin)) {
26
+ options.cors.origin = options.cors.origin.join(",");
27
+ }
22
28
  if (typeof options.cors.origin === "string") {
23
29
  options.cors.origin = options.cors.origin.toLowerCase();
24
30
  }
31
+
25
32
  if (!options.cors.methods) {
26
- options.cors.methods = "GET,HEAD,POST,PUT,PATCH";
33
+ options.cors.methods = "GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS";
27
34
  }
28
35
  if (!options.cors.headers) {
29
- options.cors.methods = "*";
36
+ options.cors.headers = "*";
30
37
  }
31
38
  }
32
39
 
@@ -1,32 +1,24 @@
1
+ const localhost = /^https?:\/\/localhost(:\d+)?$/;
2
+
1
3
  // Based on https://expressjs.com/en/resources/middleware/cors.html#configuration-options
2
- export default function cors(configOrigin, origin = "") {
4
+ export default function cors(config, origin = "") {
3
5
  origin = origin.toLowerCase();
4
6
 
5
7
  // When it's true, reflect the origin
6
- if (configOrigin === true) return origin || null;
8
+ if (config === true) return origin || null;
7
9
 
8
10
  // A star should always return a star
9
- if (configOrigin === "*") return "*";
11
+ if (config === "*") return "*";
10
12
 
11
13
  // No origin, it's okay since that means we don't need CORS
12
14
  if (!origin) return null;
13
15
 
14
16
  // Coming from localhost
15
- if (/^https?:\/\/localhost(:\d+)?$/.test(origin)) {
16
- return origin;
17
- }
17
+ if (localhost.test(origin)) return origin;
18
18
 
19
- if (
20
- configOrigin
21
- .toLowerCase()
22
- .split(/\s*,\s*/g)
23
- .includes(origin)
24
- ) {
25
- return origin;
26
- }
19
+ const arr = Array.isArray(config) ? config : config.split(/\s*,\s*/g);
20
+ if (arr.includes(origin)) return origin;
27
21
 
28
- console.warn(
29
- `CORS: Origin "${origin}" not allowed. Allowed origins: ${configOrigin}`,
30
- );
22
+ console.warn(`CORS: Origin "${origin}" not allowed. Allowed "${config}"`);
31
23
  return null;
32
24
  }
@@ -0,0 +1,64 @@
1
+ import server from "../index.js";
2
+
3
+ const origin = "http://localhost:3000/";
4
+
5
+ describe("cors", () => {
6
+ it("can simply enable the cors", async () => {
7
+ const cors = "*";
8
+ const { headers } = await server({ cors })
9
+ .get("/", () => 200)
10
+ .test()
11
+ .get("/", { headers: { origin } });
12
+
13
+ expect(headers).toEqual({
14
+ "access-control-allow-origin": "*",
15
+ "access-control-allow-headers": "*",
16
+ "access-control-allow-methods": "GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS",
17
+ });
18
+ });
19
+
20
+ it("can disable the cors", async () => {
21
+ const cors = false;
22
+ const { headers } = await server({ cors })
23
+ .get("/", () => 200)
24
+ .test()
25
+ .get("/", { headers: { origin } });
26
+ expect(headers["access-control-allow-origin"]).toBe(undefined);
27
+ });
28
+
29
+ it("gets the wildcard without the origin", async () => {
30
+ const cors = "*";
31
+ const { headers } = await server({ cors })
32
+ .get("/", () => 200)
33
+ .test()
34
+ .get("/");
35
+ expect(headers["access-control-allow-origin"]).toBe("*");
36
+ });
37
+
38
+ it("gets the origin with true", async () => {
39
+ const cors = true;
40
+ const { headers } = await server({ cors })
41
+ .get("/", () => 200)
42
+ .test()
43
+ .get("/", { headers: { origin } });
44
+ expect(headers["access-control-allow-origin"]).toBe(origin);
45
+ });
46
+
47
+ it("gets the correct origin with multiple as string", async () => {
48
+ const cors = "https://a.com/,https://b.com/";
49
+ const { headers } = await server({ cors })
50
+ .get("/", () => 200)
51
+ .test()
52
+ .get("/", { headers: { origin: "https://b.com/" } });
53
+ expect(headers["access-control-allow-origin"]).toBe("https://b.com/");
54
+ });
55
+
56
+ it("gets the correct origin with multiple as array", async () => {
57
+ const cors = ["https://a.com/", "https://b.com/"];
58
+ const { headers } = await server({ cors })
59
+ .get("/", () => 200)
60
+ .test()
61
+ .get("/", { headers: { origin: "https://b.com/" } });
62
+ expect(headers["access-control-allow-origin"]).toBe("https://b.com/");
63
+ });
64
+ });
@@ -30,6 +30,7 @@ export default {
30
30
  js: "text/javascript",
31
31
  json: "application/json",
32
32
  jsonld: "application/ld+json",
33
+ md: "text/markdown",
33
34
  mid: "audio/midi",
34
35
  midi: "audio/midi",
35
36
  mjs: "text/javascript",
@@ -55,6 +56,7 @@ export default {
55
56
  sh: "application/x-sh",
56
57
  svg: "image/svg+xml",
57
58
  tar: "application/x-tar",
59
+ text: "text/plain",
58
60
  tif: "image/tiff",
59
61
  tiff: "image/tiff",
60
62
  ts: "video/mp2t",
package/src/index.js CHANGED
@@ -224,7 +224,7 @@ server.prototype.test = function () {
224
224
  const fetch = async (path, options = {}) => {
225
225
  if (!options.headers) options.headers = {};
226
226
  if (options.body && typeof options.body !== "string") {
227
- options.headers["content-type"] = "application/json; charset=utf-8";
227
+ options.headers["content-type"] = "application/json";
228
228
  options.body = JSON.stringify(options.body);
229
229
  }
230
230
  if (cookie && !options.headers.cookie) {
package/src/index.test.js CHANGED
@@ -75,13 +75,13 @@ describe("simple post works", () => {
75
75
  const { body, status, headers } = await api.post("/", "New Data");
76
76
  expect(status).toBe(201);
77
77
  expect(body).toBe("New Data");
78
- expect(headers["content-type"]).toBe("text/plain; charset=utf-8");
78
+ expect(headers["content-type"]).toBe("text/plain");
79
79
  });
80
80
 
81
81
  it("will return JSON", async () => {
82
82
  const { body, status, headers } = await api.post("/", { hello: "world" });
83
83
  expect(status).toBe(201);
84
84
  expect(body).toEqual({ hello: "world" });
85
- expect(headers["content-type"]).toBe("application/json; charset=utf-8");
85
+ expect(headers["content-type"]).toBe("application/json");
86
86
  });
87
87
  });
@@ -44,11 +44,11 @@ export default async function parseResponse(out, ctx) {
44
44
  // Set the proper CORS headers
45
45
  const origin = cors(ctx.options.cors.origin, ctx.headers.origin);
46
46
  if (origin) {
47
- headers.set("Access-Control-Allow-Methods", ctx.options.cors.methods);
48
- headers.set("Access-Control-Allow-Headers", ctx.options.cors.headers);
49
- headers.set("Access-Control-Allow-Origin", origin);
47
+ out.headers.set("Access-Control-Allow-Origin", origin);
48
+ out.headers.set("Access-Control-Allow-Methods", ctx.options.cors.methods);
49
+ out.headers.set("Access-Control-Allow-Headers", ctx.options.cors.headers);
50
50
  if (ctx.options.cors.credentials) {
51
- headers.set("Access-Control-Allow-Credentials", "true");
51
+ out.headers.set("Access-Control-Allow-Credentials", "true");
52
52
  }
53
53
  }
54
54
  }
package/src/reply.js CHANGED
@@ -27,8 +27,20 @@ Reply.prototype.status = function (status) {
27
27
  // `.html`, `html`, `text/html`
28
28
  Reply.prototype.type = function (type) {
29
29
  if (!type) return this;
30
- this.res.headers["content-type"] = types[type.replace(/^\./)] || type;
31
- return this;
30
+ type = types[type.replace(/^\./)] || type;
31
+ return this.headers({ "content-type": type });
32
+ };
33
+
34
+ // Prompt for download from the user side
35
+ Reply.prototype.download = function (name = "", type) {
36
+ // filename.txt and no explicit type => add headers "text/plain"
37
+ if (name && !ext) type = name.split(".")[1];
38
+
39
+ // Add the Content-Type if there's a type
40
+ if (type) this.type(type);
41
+
42
+ const filename = name ? `; filename="${name}"` : "";
43
+ return this.headers({ "content-disposition": `attachment${filename}` });
32
44
  };
33
45
 
34
46
  // Set extra headers
@@ -56,7 +68,7 @@ Reply.prototype.cookies = function (cookies) {
56
68
  // FINAL
57
69
  Reply.prototype.json = function (body) {
58
70
  return this.headers({
59
- "content-type": "application/json; charset=utf-8",
71
+ "content-type": "application/json",
60
72
  }).send(JSON.stringify(body));
61
73
  };
62
74
 
@@ -95,9 +107,7 @@ Reply.prototype.send = function (body = "") {
95
107
  // Not yet set, so infer the type from type of string
96
108
  if (!this.res.headers["content-type"]) {
97
109
  const isHtml = body.startsWith("<");
98
- this.res.headers["content-type"] = isHtml
99
- ? "text/html; charset=utf-8"
100
- : "text/plain; charset=utf-8";
110
+ this.res.headers["content-type"] = isHtml ? "text/html" : "text/plain";
101
111
  }
102
112
 
103
113
  const headers = this.generateHeaders();
@@ -129,8 +139,9 @@ export { Reply };
129
139
 
130
140
  // PARTIAL
131
141
  export const status = (...args) => new Reply().status(...args);
132
- export const type = (...args) => new Reply().type(...args);
133
142
  export const headers = (...args) => new Reply().headers(...args);
143
+ export const type = (...args) => new Reply().type(...args);
144
+ export const download = (...args) => new Reply().download(...args);
134
145
  export const cookies = (...args) => new Reply().cookies(...args);
135
146
 
136
147
  // FINAL
package/src/url.test.js CHANGED
@@ -9,7 +9,7 @@ describe("can match the url", () => {
9
9
 
10
10
  const { body, headers } = await api.get("/hello");
11
11
  expect(body).toEqual({ id: "hello" });
12
- expect(headers["content-type"]).toEqual("application/json; charset=utf-8");
12
+ expect(headers["content-type"]).toEqual("application/json");
13
13
  });
14
14
 
15
15
  it("but it doesn't if it's a use", async () => {
@@ -21,6 +21,6 @@ describe("can match the url", () => {
21
21
 
22
22
  const { body, headers, ...rest } = await api.get("/hello");
23
23
  expect(body).toEqual({ id: "hello" });
24
- expect(headers["content-type"]).toEqual("application/json; charset=utf-8");
24
+ expect(headers["content-type"]).toEqual("application/json");
25
25
  });
26
26
  });