@mojojs/core 0.5.3 → 0.5.7
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/lib/cli/gen-full-app.js +5 -8
- package/lib/router/pattern.js +3 -3
- package/lib/server.d.ts +2 -2
- package/lib/server.js +3 -3
- package/lib/user-agent/test.d.ts +2 -2
- package/lib/user-agent/test.js +7 -4
- package/lib/user-agent.js +1 -0
- package/lib/util.d.ts +3 -0
- package/lib/util.js +21 -0
- package/lib/websocket.d.ts +1 -1
- package/lib/websocket.js +2 -2
- package/package.json +8 -5
package/lib/cli/gen-full-app.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as util from './../util.js';
|
|
|
3
3
|
export default async function genLiteAppCommand(app, args) {
|
|
4
4
|
const name = args[1] ?? 'index.js';
|
|
5
5
|
process.stdout.write('Generating application directory struture:\n');
|
|
6
|
-
await util.cliCreateFile('config.
|
|
6
|
+
await util.cliCreateFile('config.yml', yamlConfig, { secret: crypto.randomBytes(16).toString('hex') });
|
|
7
7
|
await util.cliCreateFile(name, jsIndex);
|
|
8
8
|
await util.cliCreateDir('controllers');
|
|
9
9
|
await util.cliCreateFile('controllers/example.js', jsController);
|
|
@@ -27,17 +27,14 @@ genLiteAppCommand.usage = `Usage: APPLICATION gen-full-app [OPTIONS] [NAME]
|
|
|
27
27
|
Options:
|
|
28
28
|
-h, --help Show this summary of available options
|
|
29
29
|
`;
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
"<%= secret %>"
|
|
33
|
-
]
|
|
34
|
-
}
|
|
30
|
+
const yamlConfig = `---
|
|
31
|
+
secrets: [<%= secret %>]
|
|
35
32
|
`;
|
|
36
|
-
const jsIndex = `import mojo, {
|
|
33
|
+
const jsIndex = `import mojo, {yamlConfigPlugin} from '@mojojs/core';
|
|
37
34
|
|
|
38
35
|
export const app = mojo();
|
|
39
36
|
|
|
40
|
-
app.plugin(
|
|
37
|
+
app.plugin(yamlConfigPlugin);
|
|
41
38
|
app.secrets = [app.config.secrets];
|
|
42
39
|
|
|
43
40
|
app.get('/').to('example#welcome');
|
package/lib/router/pattern.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { escapeRegExp } from '../util.js';
|
|
2
2
|
const OP = {
|
|
3
3
|
placeholder: Symbol('placeholder'),
|
|
4
4
|
relaxed: Symbol('relaxed'),
|
|
@@ -85,7 +85,7 @@ export class Pattern {
|
|
|
85
85
|
continue;
|
|
86
86
|
}
|
|
87
87
|
else if (token[0] === OP.text) {
|
|
88
|
-
part =
|
|
88
|
+
part = escapeRegExp(token[1]);
|
|
89
89
|
optional = false;
|
|
90
90
|
}
|
|
91
91
|
else {
|
|
@@ -128,7 +128,7 @@ export class Pattern {
|
|
|
128
128
|
.slice()
|
|
129
129
|
.sort()
|
|
130
130
|
.reverse()
|
|
131
|
-
.map(val =>
|
|
131
|
+
.map(val => escapeRegExp(val))
|
|
132
132
|
.join('|') +
|
|
133
133
|
')');
|
|
134
134
|
}
|
package/lib/server.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Socket } from 'net';
|
|
|
4
4
|
import http from 'http';
|
|
5
5
|
import https from 'https';
|
|
6
6
|
import { URL } from 'url';
|
|
7
|
-
import
|
|
7
|
+
import { WebSocketServer } from 'ws';
|
|
8
8
|
declare type ListenArgs = any[];
|
|
9
9
|
interface ServerOptions {
|
|
10
10
|
cluster?: boolean;
|
|
@@ -28,6 +28,6 @@ export declare class Server {
|
|
|
28
28
|
stop(): Promise<void>;
|
|
29
29
|
_createServer(location: string): Promise<void>;
|
|
30
30
|
_handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void;
|
|
31
|
-
_handleUpgrade(wss:
|
|
31
|
+
_handleUpgrade(wss: WebSocketServer, req: http.IncomingMessage, socket: Socket, head: Buffer): void;
|
|
32
32
|
}
|
|
33
33
|
export {};
|
package/lib/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'os';
|
|
|
5
5
|
import { URL } from 'url';
|
|
6
6
|
import { WebSocket } from './websocket.js';
|
|
7
7
|
import Path from '@mojojs/path';
|
|
8
|
-
import
|
|
8
|
+
import { WebSocketServer } from 'ws';
|
|
9
9
|
export class Server {
|
|
10
10
|
constructor(app, options = {}) {
|
|
11
11
|
this.urls = [];
|
|
@@ -38,7 +38,7 @@ export class Server {
|
|
|
38
38
|
}
|
|
39
39
|
async start() {
|
|
40
40
|
await this.app.hooks.runHook('start', this.app);
|
|
41
|
-
if (this._cluster && cluster.
|
|
41
|
+
if (this._cluster && cluster.isPrimary) {
|
|
42
42
|
for (let i = 0; i < this._workers; i++) {
|
|
43
43
|
cluster.fork();
|
|
44
44
|
}
|
|
@@ -64,7 +64,7 @@ export class Server {
|
|
|
64
64
|
isHttps = true;
|
|
65
65
|
}
|
|
66
66
|
await this.app.warmup();
|
|
67
|
-
const wss = new
|
|
67
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
68
68
|
const server = (isHttps ? https : http).createServer(options, this._handleRequest.bind(this));
|
|
69
69
|
this._servers.push(server);
|
|
70
70
|
server.on('upgrade', this._handleUpgrade.bind(this, wss));
|
package/lib/user-agent/test.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export declare class TestUserAgent extends MockUserAgent {
|
|
|
35
35
|
headerExistsNot(name: string): this;
|
|
36
36
|
headerIs(name: string, value: string): this;
|
|
37
37
|
headerLike(name: string, regex: RegExp): this;
|
|
38
|
-
jsonIs(value: JSONValue): this;
|
|
38
|
+
jsonIs(value: JSONValue, pointer?: string): this;
|
|
39
39
|
messageOk(): Promise<JSONValue>;
|
|
40
40
|
static newTestUserAgent(app: App, options?: TestUserAgentOptions): Promise<TestUserAgent>;
|
|
41
41
|
optionsOk(url: string | URL, options?: UserAgentRequestOptions): Promise<this>;
|
|
@@ -49,7 +49,7 @@ export declare class TestUserAgent extends MockUserAgent {
|
|
|
49
49
|
typeLike(regex: RegExp): this;
|
|
50
50
|
websocketOk(url: string | URL, options?: UserAgentWebSocketOptions): Promise<void>;
|
|
51
51
|
get ws(): WebSocket;
|
|
52
|
-
yamlIs(value:
|
|
52
|
+
yamlIs(value: JSONValue, pointer?: string): this;
|
|
53
53
|
get _cheerio(): cheerio.Root;
|
|
54
54
|
_prepareTap(tap: Tap.Tap): void;
|
|
55
55
|
_requestOk(skip: SkipFunction, method: string, url: string | URL, options?: UserAgentRequestOptions): Promise<this>;
|
package/lib/user-agent/test.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from 'assert/strict';
|
|
2
2
|
import { on } from 'events';
|
|
3
|
+
import { jsonPointer } from '../util.js';
|
|
3
4
|
import { MockUserAgent } from './mock.js';
|
|
4
5
|
import cheerio from 'cheerio';
|
|
5
6
|
import yaml from 'js-yaml';
|
|
@@ -79,8 +80,9 @@ export class TestUserAgent extends MockUserAgent {
|
|
|
79
80
|
this.assert('match', [this.res.get(name), regex], `${name} is similar`, this.headerLike);
|
|
80
81
|
return this;
|
|
81
82
|
}
|
|
82
|
-
jsonIs(value) {
|
|
83
|
-
|
|
83
|
+
jsonIs(value, pointer = '') {
|
|
84
|
+
const expected = jsonPointer(JSON.parse(this.body.toString()), pointer);
|
|
85
|
+
this.assert('same', [expected, value], `exact match for JSON Pointer "${pointer}" (JSON)`, this.jsonIs);
|
|
84
86
|
return this;
|
|
85
87
|
}
|
|
86
88
|
async messageOk() {
|
|
@@ -142,8 +144,9 @@ export class TestUserAgent extends MockUserAgent {
|
|
|
142
144
|
throw new Error('No active WebSocket connection');
|
|
143
145
|
return ws;
|
|
144
146
|
}
|
|
145
|
-
yamlIs(value) {
|
|
146
|
-
|
|
147
|
+
yamlIs(value, pointer = '') {
|
|
148
|
+
const expected = jsonPointer(yaml.load(this.body.toString()), pointer);
|
|
149
|
+
this.assert('same', [expected, value], `exact match for JSON Pointer "${pointer}" (YAML)`, this.yamlIs);
|
|
147
150
|
return this;
|
|
148
151
|
}
|
|
149
152
|
get _cheerio() {
|
package/lib/user-agent.js
CHANGED
|
@@ -74,6 +74,7 @@ class UserAgent extends EventEmitter {
|
|
|
74
74
|
const filtered = this._filterSharedConfig(options);
|
|
75
75
|
await this._loadCookies(filtered.url, filtered);
|
|
76
76
|
this.emit('websocket', filtered);
|
|
77
|
+
filtered.url.protocol = filtered.url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
77
78
|
const ws = new WS(filtered.url, filtered.protocols, { headers: filtered.headers });
|
|
78
79
|
return await new Promise((resolve, reject) => {
|
|
79
80
|
let handshake;
|
package/lib/util.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
import type { JSONValue } from './types.js';
|
|
2
3
|
import type { Mode } from 'fs';
|
|
3
4
|
import { setTimeout } from 'timers/promises';
|
|
4
5
|
export declare function captureOutput(fn: () => void, options?: {
|
|
@@ -11,6 +12,7 @@ export declare function cliCreateFile(path: string, template: string, values?: R
|
|
|
11
12
|
}): Promise<void>;
|
|
12
13
|
export declare function cliFixPackage(): Promise<void>;
|
|
13
14
|
export declare function decodeURIComponentSafe(value: string): string | null;
|
|
15
|
+
export declare function escapeRegExp(string: string): string;
|
|
14
16
|
export declare function exceptionContext(error: Error, options?: {
|
|
15
17
|
lines?: number;
|
|
16
18
|
}): Promise<{
|
|
@@ -24,6 +26,7 @@ export declare function exceptionContext(error: Error, options?: {
|
|
|
24
26
|
} | null>;
|
|
25
27
|
export declare function htmlEscape(value: string | SafeString): string;
|
|
26
28
|
export declare function htmlTag(name: string, attrs?: Record<string, string> | string | SafeString, content?: string | SafeString): SafeString;
|
|
29
|
+
export declare function jsonPointer(value: JSONValue, pointer: string): JSONValue | undefined;
|
|
27
30
|
export declare function loadModules(dirs: string[]): Promise<Record<string, any>>;
|
|
28
31
|
export declare const sleep: typeof setTimeout;
|
|
29
32
|
export declare function tablify(rows?: string[][]): string;
|
package/lib/util.js
CHANGED
|
@@ -106,6 +106,9 @@ export function decodeURIComponentSafe(value) {
|
|
|
106
106
|
return null;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
export function escapeRegExp(string) {
|
|
110
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
111
|
+
}
|
|
109
112
|
export async function exceptionContext(error, options = {}) {
|
|
110
113
|
const stack = error.stack ?? '';
|
|
111
114
|
const match = stack.split('\n')[1].match(/^\s*at .+ \(([^)]+):(\d+):(\d+)\)\s*$/);
|
|
@@ -152,6 +155,24 @@ export function htmlTag(name, attrs = {}, content = '') {
|
|
|
152
155
|
}
|
|
153
156
|
return new SafeString(result.join(''));
|
|
154
157
|
}
|
|
158
|
+
export function jsonPointer(value, pointer) {
|
|
159
|
+
if (!pointer.startsWith('/'))
|
|
160
|
+
return pointer.length > 0 ? null : value;
|
|
161
|
+
let data = value;
|
|
162
|
+
for (const part of pointer.replace(/^\//, '').split('/')) {
|
|
163
|
+
const unescaped = part.replaceAll('~1', '/').replaceAll('~0', '~');
|
|
164
|
+
if (typeof data === 'object' && data[unescaped] !== undefined) {
|
|
165
|
+
data = data[unescaped];
|
|
166
|
+
}
|
|
167
|
+
else if (Array.isArray(data) && /^\d+$/.test(unescaped)) {
|
|
168
|
+
data = data[parseInt(unescaped)];
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return data;
|
|
175
|
+
}
|
|
155
176
|
export async function loadModules(dirs) {
|
|
156
177
|
const modules = {};
|
|
157
178
|
for (const dir of dirs.map(path => new Path(path))) {
|
package/lib/websocket.d.ts
CHANGED
|
@@ -29,6 +29,6 @@ declare class WebSocket extends EventEmitter {
|
|
|
29
29
|
send(message: JSONValue | Buffer): Promise<void>;
|
|
30
30
|
_messageIterator(): AsyncIterableIterator<Array<JSONValue | Buffer>>;
|
|
31
31
|
_safeHandler<T extends keyof WebSocketControlEvents>(event: T, ...args: Parameters<WebSocketControlEvents[T]>): void;
|
|
32
|
-
_safeMessageHandler(message:
|
|
32
|
+
_safeMessageHandler(message: Buffer, isBinary: boolean): void;
|
|
33
33
|
}
|
|
34
34
|
export { WebSocket };
|
package/lib/websocket.js
CHANGED
|
@@ -48,10 +48,10 @@ class WebSocket extends EventEmitter {
|
|
|
48
48
|
this.emit('error', error);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
_safeMessageHandler(message) {
|
|
51
|
+
_safeMessageHandler(message, isBinary) {
|
|
52
52
|
try {
|
|
53
53
|
if (!this.jsonMode) {
|
|
54
|
-
this.emit('message', message);
|
|
54
|
+
this.emit('message', isBinary ? message : message.toString());
|
|
55
55
|
}
|
|
56
56
|
else {
|
|
57
57
|
this.emit('message', JSON.parse(message.toString()));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mojojs/core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "Real-time web framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@types/stack-utils": "^2.0.0",
|
|
62
62
|
"@types/tap": "^15.0.3",
|
|
63
63
|
"@types/tough-cookie": "^4.0.0",
|
|
64
|
-
"@types/ws": "^
|
|
64
|
+
"@types/ws": "^8.2.0",
|
|
65
65
|
"@typescript-eslint/eslint-plugin": "^5.1.0",
|
|
66
66
|
"autocannon": "^7.3.0",
|
|
67
67
|
"concurrently": "^6.2.0",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"eslint-plugin-node": "^11.1.0",
|
|
72
72
|
"eslint-plugin-prettier": "^4.0.0",
|
|
73
73
|
"prettier": "2.4.1",
|
|
74
|
+
"tap": "^15.0.6",
|
|
74
75
|
"typescript": "^=4.3.2"
|
|
75
76
|
},
|
|
76
77
|
"engines": {
|
|
@@ -84,15 +85,17 @@
|
|
|
84
85
|
"cheerio": "^1.0.0-rc.10",
|
|
85
86
|
"cookie": "^0.4.1",
|
|
86
87
|
"ejs": "^3.1.6",
|
|
87
|
-
"escape-string-regexp": "^5.0.0",
|
|
88
88
|
"form-data": "^4.0.0",
|
|
89
89
|
"js-yaml": "^4.1.0",
|
|
90
90
|
"lru-cache": "^6.0.0",
|
|
91
91
|
"mime-types": "^2.1.30",
|
|
92
92
|
"nopt": "^5.0.0",
|
|
93
93
|
"stack-utils": "^2.0.3",
|
|
94
|
-
"tap": "^15.0.6",
|
|
95
94
|
"tough-cookie": "^4.0.0",
|
|
96
|
-
"ws": "^
|
|
95
|
+
"ws": "^8.2.3"
|
|
96
|
+
},
|
|
97
|
+
"optionalDependencies": {
|
|
98
|
+
"bufferutil": "^4.0.5",
|
|
99
|
+
"utf-8-validate": "^5.0.7"
|
|
97
100
|
}
|
|
98
101
|
}
|