Skip to content

Commit 225c5a8

Browse files
committed
refactor: add server implementation and add WIP Electron refactor
--- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: na - task: lint_typescript_tests status: na - task: lint_license_headers status: passed ---
1 parent 1877a42 commit 225c5a8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+4776
-224
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// MODULES //
22+
23+
var buffer2bytelength = require( '@stdlib/buffer/ctor' ).byteLength;
24+
var NODE_VERSION = require( '@stdlib/process/node-version' );
25+
26+
27+
// VARIABLES //
28+
29+
var SUPPORTS_BUFFER = ( parseInt( NODE_VERSION.split( '.' )[ 0 ], 10 ) < 6 );
30+
31+
32+
// MAIN //
33+
34+
/**
35+
* Returns the byte length of an encoded string.
36+
*
37+
* @private
38+
* @param {(string|Buffer)} str - input string
39+
* @returns {NonNegativeInteger} byte length
40+
*/
41+
function byteLength( str ) { // TODO: consider making a robust utility in `@stdlib/buffer/byte-length`
42+
if ( SUPPORTS_BUFFER ) {
43+
return buffer2bytelength( str );
44+
}
45+
// Earlier versions of Node.js do not support Buffers, ArrayBuffers, TypedArrays, DataViews, or SharedArrayBuffers, so we need to explicitly call `#.toString()`...
46+
return buffer2bytelength( str.toString() );
47+
}
48+
49+
50+
// EXPORTS //
51+
52+
module.exports = byteLength;

lib/node_modules/@stdlib/plot/base/view/lib/browser/index.js

Lines changed: 112 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,31 @@
2020

2121
// MODULES //
2222

23-
var path = require( 'path' );
24-
var readFileSync = require( '@stdlib/fs/read-file' ).sync;
25-
var replace = require( '@stdlib/string/base/replace' );
26-
var httpServer = require( '@stdlib/net/disposable-http-server' );
23+
var logger = require( 'debug' );
24+
var string2buffer = require( '@stdlib/buffer/from-string' );
25+
var openURL = require( '@stdlib/utils/open-url' );
26+
var createServer = require( '@stdlib/plot/base/server' );
27+
var routes = require( './routes' );
28+
var Router = require( './router.js' );
2729

2830

2931
// VARIABLES //
3032

31-
var TEMPLATE = path.join( __dirname, 'index.html' );
32-
var FOPTS = {
33-
'encoding': 'utf8'
34-
};
35-
var RE_PLOT = /\{\{plot\}\}/;
33+
var debug = logger( 'plot:view:browser' );
34+
35+
36+
// FUNCTIONS //
37+
38+
/**
39+
* Opens a plot in a web browser.
40+
*
41+
* @private
42+
* @param {Object} plot - plot instance
43+
*/
44+
function displayPlot( plot ) {
45+
var addr = plot.server.address();
46+
openURL( 'https://'+addr.address+':'+addr.port );
47+
}
3648

3749

3850
// MAIN //
@@ -41,14 +53,98 @@ var RE_PLOT = /\{\{plot\}\}/;
4153
* Opens a plot in a browser window.
4254
*
4355
* @private
44-
* @param {string} html - HTML string
56+
* @param {Object} plot - plot instance
4557
*/
46-
function view( html ) {
47-
// Create a disposable HTTP server...
48-
httpServer({
49-
'html': replace( readFileSync( TEMPLATE, FOPTS ), RE_PLOT, html ),
50-
'open': true
51-
});
58+
function view( plot ) {
59+
var server;
60+
var router;
61+
var events;
62+
var spec;
63+
64+
// Register a listener for tracking plot changes:
65+
plot.on( 'change', onChange );
66+
67+
// Cache the current plot state by serializing to JSON:
68+
spec = string2buffer( JSON.stringify( plot ) );
69+
70+
// Initialize a new request router:
71+
router = new Router();
72+
73+
// Register routes:
74+
routes( router );
75+
76+
// Initialize an events database:
77+
events = [];
78+
79+
server = plot.server;
80+
if ( !server || server.address() === null ) { // note: server.address() is `null` if a server has already been closed
81+
debug( 'Creating a server...' );
82+
createServer( onRequest, onReady );
83+
} else {
84+
displayPlot( plot );
85+
}
86+
87+
/**
88+
* Callback invoked once a server is ready to receive requests.
89+
*
90+
* @private
91+
* @param {(Error|null)} error - error object
92+
* @param {Server} server - server instance
93+
* @throws {Error} unexpected error
94+
*/
95+
function onReady( error, server ) {
96+
if ( error ) {
97+
throw error;
98+
}
99+
// Bind the server to a plot instance:
100+
plot.server = server;
101+
102+
displayPlot( plot );
103+
}
104+
105+
/**
106+
* Request handler.
107+
*
108+
* @private
109+
* @param {IncomingMessage} request - HTTP request object
110+
* @param {ServerResponse} response - HTTP response object
111+
* @returns {void}
112+
*/
113+
function onRequest( request, response ) {
114+
var handler;
115+
var ctx;
116+
117+
// Resolve a route handler:
118+
handler = router.resolve( request );
119+
120+
// Decorate route handler `this` contexts with additional functionality:
121+
ctx = {
122+
// Plot instance:
123+
'plot': plot,
124+
125+
// Cached plot schema:
126+
'spec': spec,
127+
128+
// Events database:
129+
'events': events
130+
};
131+
132+
// Decorate each `request` object with a `locals` object for passing along intermediate results within middleware handlers:
133+
request.locals = {};
134+
135+
// Invoke the route handler:
136+
handler.call( ctx, request, response );
137+
}
138+
139+
/**
140+
* Callback invoked upon a plot 'change' event.
141+
*
142+
* @private
143+
* @param {Event} event - event object
144+
*/
145+
function onChange() {
146+
spec = string2buffer( JSON.stringify( plot ) );
147+
}
52148
}
53149

54150

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// MAIN //
22+
23+
/**
24+
* Middleware function to set a header for allowing credentials to be sent with a request.
25+
*
26+
* @private
27+
* @param {Object} request - request object
28+
* @param {Object} response - response object
29+
* @param {Callback} next - callback to invoke upon completion
30+
* @returns {void}
31+
*/
32+
function allowCredentials( request, response, next ) {
33+
response.setHeader( 'Access-Control-Allow-Credentials', true );
34+
next();
35+
}
36+
37+
38+
// EXPORTS //
39+
40+
module.exports = allowCredentials;
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// MODULES //
22+
23+
var logger = require( 'debug' );
24+
var Buffer = require( '@stdlib/buffer/ctor' );
25+
26+
27+
// VARIABLES //
28+
29+
var debug = logger( 'plot:view:browser:middleware:body' );
30+
31+
// Define a limit for how large a request body can be:
32+
var BODY_LIMIT = 10 * 1024 * 1024; // 10 MB
33+
34+
35+
// MAIN //
36+
37+
/**
38+
* Middleware function to resolve a request body.
39+
*
40+
* @private
41+
* @param {Object} request - request object
42+
* @param {Object} response - response object
43+
* @param {Callback} next - callback to invoke upon completion
44+
* @returns {void}
45+
*/
46+
function rawBody( request, response, next ) {
47+
var chunks;
48+
var size;
49+
50+
chunks = [];
51+
size = 0;
52+
request.on( 'data', onData );
53+
request.on( 'aborted', onAborted );
54+
request.on( 'end', onEnd );
55+
request.on( 'error', onError );
56+
57+
/**
58+
* Callback invoked upon receiving the next chunk of data.
59+
*
60+
* @private
61+
* @param {string} chunk - chunk
62+
* @returns {void}
63+
*/
64+
function onData( chunk ) {
65+
size += chunk.length;
66+
if ( size > BODY_LIMIT ) {
67+
debug( 'Encountered an error when attempting to process a request body. Error: Request body is too large.' );
68+
return onEnd({
69+
'statusCode': 413,
70+
'error': 'Bad Request',
71+
'message': 'Payload too large.'
72+
});
73+
}
74+
chunks.push( chunk );
75+
}
76+
77+
/**
78+
* Callback invoked upon encountering an error.
79+
*
80+
* @private
81+
* @param {Error} error - error object
82+
*/
83+
function onError( error ) {
84+
debug( 'Encountered an error when attempting to process a request body. Error: %s', error.message );
85+
onEnd({
86+
'statusCode': 400,
87+
'error': 'Bad Request',
88+
'message': 'Unable to process request body'
89+
});
90+
}
91+
92+
/**
93+
* Callback invoked upon a request being aborted.
94+
*
95+
* @private
96+
*/
97+
function onAborted() {
98+
debug( 'Encountered an error when attempting to process a request body. Error: Request prematurely ended.' );
99+
onEnd({
100+
'statusCode': 400,
101+
'error': 'Bad Request',
102+
'message': 'Request prematurely ended.'
103+
});
104+
}
105+
106+
/**
107+
* Callback invoked upon receiving all chunks.
108+
*
109+
* @private
110+
* @param {Object} [error] - error object
111+
* @returns {void}
112+
*/
113+
function onEnd( error ) {
114+
request.removeListener( 'data', onData );
115+
request.removeListener( 'end', onEnd );
116+
request.removeListener( 'error', onError );
117+
request.removeListener( 'aborted', onAborted );
118+
if ( error ) {
119+
return next( error );
120+
}
121+
request.body = Buffer.concat( chunks, size ).toString( 'utf8' );
122+
next();
123+
}
124+
}
125+
126+
127+
// EXPORTS //
128+
129+
module.exports = rawBody;

0 commit comments

Comments
 (0)