@spirobel/mininext 0.3.1 → 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/dist/url.d.ts +8 -1
- package/dist/url.js +9 -0
- package/mininext/html.ts +19 -7
- package/mininext/mininext.ts +32 -2
- package/mininext/url.ts +15 -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/dist/url.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/// <reference types="bun-types" />
|
|
3
3
|
import type { Server, WebSocketHandler } from "bun";
|
|
4
4
|
import { html, json, dangerjson, HtmlString } from "./html";
|
|
5
|
-
import type { DangerJsonInHtml, JsonString, JsonStringValues } from "./html";
|
|
5
|
+
import type { BasedHtml, DangerJsonInHtml, JsonString, JsonStringValues } from "./html";
|
|
6
6
|
export type Form = {
|
|
7
7
|
post: boolean;
|
|
8
8
|
urlencoded: boolean;
|
|
@@ -193,6 +193,13 @@ export declare class url {
|
|
|
193
193
|
* @param wsObject the websocketsocket object {@link WebSocketHandler}
|
|
194
194
|
*/
|
|
195
195
|
static setWebsocket<T = undefined>(wsObject: WebSocketHandler<T>): void;
|
|
196
|
+
/**
|
|
197
|
+
* Send a message to all connected {@link ServerWebSocket} subscribed to a topic
|
|
198
|
+
* @param topic The topic to publish to
|
|
199
|
+
* @param message The data to send
|
|
200
|
+
* @returns 0 if the message was dropped, -1 if backpressure was applied, or the number of bytes sent.
|
|
201
|
+
*/
|
|
202
|
+
static publishHtml(topic: string, message: BasedHtml): number;
|
|
196
203
|
/**
|
|
197
204
|
* Fetch handler that is called by the server when a request is made to any of the urls.
|
|
198
205
|
* @param {Request} req - The Request object.
|
package/dist/url.js
CHANGED
|
@@ -395,6 +395,15 @@ export class url {
|
|
|
395
395
|
static setWebsocket(wsObject) {
|
|
396
396
|
url.websocket = wsObject;
|
|
397
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* Send a message to all connected {@link ServerWebSocket} subscribed to a topic
|
|
400
|
+
* @param topic The topic to publish to
|
|
401
|
+
* @param message The data to send
|
|
402
|
+
* @returns 0 if the message was dropped, -1 if backpressure was applied, or the number of bytes sent.
|
|
403
|
+
*/
|
|
404
|
+
static publishHtml(topic, message) {
|
|
405
|
+
return url.server.publish(topic, message);
|
|
406
|
+
}
|
|
398
407
|
/**
|
|
399
408
|
* Fetch handler that is called by the server when a request is made to any of the urls.
|
|
400
409
|
* @param {Request} req - The Request object.
|
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);
|
package/mininext/url.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { Server, WebSocketHandler } from "bun";
|
|
2
2
|
import { htmlResponder, html, json, dangerjson, HtmlString } from "./html";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
BasedHtml,
|
|
5
|
+
DangerJsonInHtml,
|
|
6
|
+
JsonString,
|
|
7
|
+
JsonStringValues,
|
|
8
|
+
} from "./html";
|
|
4
9
|
export type Form = {
|
|
5
10
|
post: boolean;
|
|
6
11
|
urlencoded: boolean;
|
|
@@ -497,7 +502,15 @@ export class url {
|
|
|
497
502
|
static setWebsocket<T = undefined>(wsObject: WebSocketHandler<T>) {
|
|
498
503
|
url.websocket = wsObject as WebSocketHandler;
|
|
499
504
|
}
|
|
500
|
-
|
|
505
|
+
/**
|
|
506
|
+
* Send a message to all connected {@link ServerWebSocket} subscribed to a topic
|
|
507
|
+
* @param topic The topic to publish to
|
|
508
|
+
* @param message The data to send
|
|
509
|
+
* @returns 0 if the message was dropped, -1 if backpressure was applied, or the number of bytes sent.
|
|
510
|
+
*/
|
|
511
|
+
static publishHtml(topic: string, message: BasedHtml) {
|
|
512
|
+
return url.server.publish(topic, message as string);
|
|
513
|
+
}
|
|
501
514
|
/**
|
|
502
515
|
* Fetch handler that is called by the server when a request is made to any of the urls.
|
|
503
516
|
* @param {Request} req - The Request object.
|