@spirobel/mininext 0.3.2 → 0.3.3
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/dist/mininext/html.d.ts +73 -0
- package/dist/mininext/html.js +288 -0
- package/dist/mininext/mininext.d.ts +15 -0
- package/dist/mininext/mininext.js +185 -0
- package/dist/mininext/url.d.ts +213 -0
- package/dist/mininext/url.js +449 -0
- package/dist/mininext.js +26 -2
- package/dist/tests/html.test.d.ts +1 -0
- package/dist/tests/html.test.js +38 -0
- package/mininext/html.ts +19 -7
- package/mininext/mininext.ts +32 -2
- package/package.json +1 -1
package/dist/mininext.js
CHANGED
|
@@ -11,7 +11,19 @@ async function build(backendPath = "backend/backend.ts") {
|
|
|
11
11
|
await devServer();
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
const
|
|
14
|
+
const streamPlugin = {
|
|
15
|
+
name: "node stream in the frontend",
|
|
16
|
+
setup(build) {
|
|
17
|
+
build.onResolve({ filter: /^stream$/ }, (args) => {
|
|
18
|
+
const path_to_crypto_lib = path.resolve(projectRoot(), "node_modules/stream-browserify/index.js");
|
|
19
|
+
if (path_to_crypto_lib)
|
|
20
|
+
return {
|
|
21
|
+
path: path_to_crypto_lib,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const bufferPlugin = {
|
|
15
27
|
name: "node buffer in the frontend",
|
|
16
28
|
setup(build) {
|
|
17
29
|
build.onResolve({ filter: /^buffer$/ }, (args) => {
|
|
@@ -23,6 +35,18 @@ const myPlugin = {
|
|
|
23
35
|
});
|
|
24
36
|
},
|
|
25
37
|
};
|
|
38
|
+
const cryptoPlugin = {
|
|
39
|
+
name: "node crypto in the frontend",
|
|
40
|
+
setup(build) {
|
|
41
|
+
build.onResolve({ filter: /^crypto$/ }, (args) => {
|
|
42
|
+
const path_to_crypto_lib = path.resolve(projectRoot(), "node_modules/crypto-browserify/index.js");
|
|
43
|
+
if (path_to_crypto_lib)
|
|
44
|
+
return {
|
|
45
|
+
path: path_to_crypto_lib,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|
|
26
50
|
async function buildBackend(backendPath = "backend/backend.ts") {
|
|
27
51
|
global.FrontendScriptUrls = [];
|
|
28
52
|
global.FrontendScripts = [];
|
|
@@ -63,7 +87,7 @@ async function buildFrontend(file) {
|
|
|
63
87
|
naming: "[name]-[hash].[ext]",
|
|
64
88
|
minify: Bun.argv[2] === "dev" ? false : true, //production
|
|
65
89
|
target: "browser",
|
|
66
|
-
plugins: [
|
|
90
|
+
plugins: [bufferPlugin, streamPlugin, cryptoPlugin],
|
|
67
91
|
});
|
|
68
92
|
if (!result?.outputs[0]?.path)
|
|
69
93
|
console.log(result);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { expect, test, mock } from "bun:test";
|
|
2
|
+
import { html, url } from "../mininext/mininext";
|
|
3
|
+
// Example of creating a mock request object
|
|
4
|
+
const mockRequestObject = {
|
|
5
|
+
method: "GET", // or 'POST', etc.
|
|
6
|
+
url: "http://example.com/api/some-endpoint",
|
|
7
|
+
body: JSON.stringify({ key: "value" }),
|
|
8
|
+
headers: {
|
|
9
|
+
"Content-Type": "application/json",
|
|
10
|
+
get: () => undefined,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
const makeMockRequest = mock(() => mockRequestObject);
|
|
14
|
+
test("no xss when BasedHtml inside of html", async () => {
|
|
15
|
+
const req = makeMockRequest();
|
|
16
|
+
url.set([
|
|
17
|
+
[
|
|
18
|
+
"/",
|
|
19
|
+
(mini) => {
|
|
20
|
+
const basedHtmlString = html `<h2>
|
|
21
|
+
this html string is resolved (it can't contain functions like
|
|
22
|
+
mini.html HtmlStrings)
|
|
23
|
+
</h2>
|
|
24
|
+
${"<script>alert(1)</script>"}`;
|
|
25
|
+
return mini.html `<h1> ${"<script>alert(1)</script>"}this HtmlString can contain functions,
|
|
26
|
+
that get resolved at request time.</h1>${basedHtmlString}
|
|
27
|
+
<h3>${(mini) => mini.html ` ${"<script>alert(1)</script>"}it gives you convenient access to the request object,
|
|
28
|
+
anywhere in your code base:${mini.req.url}.
|
|
29
|
+
no need to do "props drilling" anymore. just write a function like this:
|
|
30
|
+
(mini: Mini)=> return some html and you are golden. ${"<script>alert(1)</script>"} `}`;
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
]);
|
|
34
|
+
const response = await url.match(req, "/");
|
|
35
|
+
const responseText = await response?.text();
|
|
36
|
+
expect(responseText).not.toInclude("<script>alert(1)</script>");
|
|
37
|
+
expect(responseText).toInclude("<script>alert(1)</script>");
|
|
38
|
+
});
|
package/mininext/html.ts
CHANGED
|
@@ -7,6 +7,9 @@ import type {
|
|
|
7
7
|
export type HtmlStringValues<T = unknown> =
|
|
8
8
|
| HtmlString
|
|
9
9
|
| HtmlString[]
|
|
10
|
+
| BasedHtml
|
|
11
|
+
| BasedHtml[]
|
|
12
|
+
| (BasedHtml | HtmlString)[]
|
|
10
13
|
| string
|
|
11
14
|
| number
|
|
12
15
|
| HtmlHandler<T>
|
|
@@ -36,8 +39,11 @@ export class HtmlString extends Array {
|
|
|
36
39
|
}
|
|
37
40
|
} else if (typeof htmlPiece === "function") {
|
|
38
41
|
let resolvedHtmlPiece = await htmlPiece(mini); //passing mini
|
|
42
|
+
//same cases as outer if statement
|
|
39
43
|
if (resolvedHtmlPiece instanceof HtmlString) {
|
|
40
44
|
resolvedHtmlPiece = await resolvedHtmlPiece.resolve(mini);
|
|
45
|
+
} else if (htmlPiece instanceof BasedHtml) {
|
|
46
|
+
this[index] = htmlPiece;
|
|
41
47
|
} else {
|
|
42
48
|
if (this instanceof JsonString || this instanceof DangerJsonInHtml) {
|
|
43
49
|
resolvedHtmlPiece = JSON.stringify(resolvedHtmlPiece);
|
|
@@ -49,6 +55,8 @@ export class HtmlString extends Array {
|
|
|
49
55
|
}
|
|
50
56
|
// Replace the function with the resolved HTML piece in place
|
|
51
57
|
this[index] = resolvedHtmlPiece;
|
|
58
|
+
} else if (htmlPiece instanceof BasedHtml) {
|
|
59
|
+
this[index] = htmlPiece;
|
|
52
60
|
}
|
|
53
61
|
}
|
|
54
62
|
this.resolved = true;
|
|
@@ -79,7 +87,9 @@ export function html<X = unknown>(
|
|
|
79
87
|
// we can pass arrays of HtmlString and they will get flattened in the HtmlResponder
|
|
80
88
|
if (
|
|
81
89
|
Array.isArray(value) &&
|
|
82
|
-
value.every(
|
|
90
|
+
value.every(
|
|
91
|
+
(val) => val instanceof HtmlString || val instanceof BasedHtml
|
|
92
|
+
)
|
|
83
93
|
) {
|
|
84
94
|
// If the value is an array of HtmlString objects, add the whole array as a single value
|
|
85
95
|
const notResolved = new HtmlString(...(value as any[]));
|
|
@@ -95,7 +105,7 @@ export function html<X = unknown>(
|
|
|
95
105
|
to pass through a html template function to get escaped. You can do
|
|
96
106
|
html -> dangerjson -> html if you want!
|
|
97
107
|
</div>`;
|
|
98
|
-
} else if (!(value instanceof HtmlString)) {
|
|
108
|
+
} else if (!(value instanceof HtmlString || value instanceof BasedHtml)) {
|
|
99
109
|
const notEmpty = value || "";
|
|
100
110
|
// values will be escaped by default
|
|
101
111
|
values[index] = Bun.escapeHTML(notEmpty + "");
|
|
@@ -144,7 +154,9 @@ function JsonTemplateProcessor(danger: boolean = false) {
|
|
|
144
154
|
// we can pass arrays of HtmlString and they will get flattened in the HtmlResponder
|
|
145
155
|
if (
|
|
146
156
|
Array.isArray(value) &&
|
|
147
|
-
value.every(
|
|
157
|
+
value.every(
|
|
158
|
+
(val) => val instanceof HtmlString || val instanceof BasedHtml
|
|
159
|
+
)
|
|
148
160
|
) {
|
|
149
161
|
// If the value is an array of HtmlString objects, add the whole array as a single value
|
|
150
162
|
const notResolved = new HtmlString(...(value as any[]));
|
|
@@ -154,8 +166,8 @@ function JsonTemplateProcessor(danger: boolean = false) {
|
|
|
154
166
|
} else if (typeof value === "function") {
|
|
155
167
|
jsonStringArray.resolved = false;
|
|
156
168
|
values[index] = value;
|
|
157
|
-
} else if (value instanceof HtmlString) {
|
|
158
|
-
if (!value.resolved) {
|
|
169
|
+
} else if (value instanceof HtmlString || value instanceof BasedHtml) {
|
|
170
|
+
if (value instanceof HtmlString && !value.resolved) {
|
|
159
171
|
jsonStringArray.resolved = false;
|
|
160
172
|
}
|
|
161
173
|
values[index] = value;
|
|
@@ -265,11 +277,11 @@ export async function htmlResponder(
|
|
|
265
277
|
}
|
|
266
278
|
const definitelyResolved = await maybeUnresolved.resolve(mini);
|
|
267
279
|
const flattend = definitelyResolved.flat(Infinity);
|
|
268
|
-
|
|
269
280
|
async function* stepGen() {
|
|
270
281
|
let index = 0;
|
|
271
282
|
while (index < flattend.length) {
|
|
272
|
-
|
|
283
|
+
const step = flattend[index++];
|
|
284
|
+
if (step) yield String(step);
|
|
273
285
|
}
|
|
274
286
|
}
|
|
275
287
|
function Stream(a: any) {
|
package/mininext/mininext.ts
CHANGED
|
@@ -24,7 +24,22 @@ async function build(backendPath: string = "backend/backend.ts") {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const streamPlugin: BunPlugin = {
|
|
28
|
+
name: "node stream in the frontend",
|
|
29
|
+
setup(build) {
|
|
30
|
+
build.onResolve({ filter: /^stream$/ }, (args) => {
|
|
31
|
+
const path_to_stream_lib = path.resolve(
|
|
32
|
+
projectRoot(),
|
|
33
|
+
"node_modules/stream-browserify/index.js"
|
|
34
|
+
);
|
|
35
|
+
if (path_to_stream_lib)
|
|
36
|
+
return {
|
|
37
|
+
path: path_to_stream_lib,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const bufferPlugin: BunPlugin = {
|
|
28
43
|
name: "node buffer in the frontend",
|
|
29
44
|
setup(build) {
|
|
30
45
|
build.onResolve({ filter: /^buffer$/ }, (args) => {
|
|
@@ -39,6 +54,21 @@ const myPlugin: BunPlugin = {
|
|
|
39
54
|
});
|
|
40
55
|
},
|
|
41
56
|
};
|
|
57
|
+
const cryptoPlugin: BunPlugin = {
|
|
58
|
+
name: "node crypto in the frontend",
|
|
59
|
+
setup(build) {
|
|
60
|
+
build.onResolve({ filter: /^crypto$/ }, (args) => {
|
|
61
|
+
const path_to_crypto_lib = path.resolve(
|
|
62
|
+
projectRoot(),
|
|
63
|
+
"node_modules/crypto-browserify/index.js"
|
|
64
|
+
);
|
|
65
|
+
if (path_to_crypto_lib)
|
|
66
|
+
return {
|
|
67
|
+
path: path_to_crypto_lib,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
42
72
|
async function buildBackend(backendPath: string = "backend/backend.ts") {
|
|
43
73
|
global.FrontendScriptUrls = [];
|
|
44
74
|
global.FrontendScripts = [];
|
|
@@ -83,7 +113,7 @@ async function buildFrontend(file: string) {
|
|
|
83
113
|
naming: "[name]-[hash].[ext]",
|
|
84
114
|
minify: Bun.argv[2] === "dev" ? false : true, //production
|
|
85
115
|
target: "browser",
|
|
86
|
-
plugins: [
|
|
116
|
+
plugins: [bufferPlugin, streamPlugin, cryptoPlugin],
|
|
87
117
|
});
|
|
88
118
|
if (!result?.outputs[0]?.path) console.log(result);
|
|
89
119
|
const url = path.basename(result.outputs[0].path);
|