@webqit/webflo 0.8.48-0 → 0.8.49
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/docker/Dockerfile +25 -0
- package/docker/README.md +69 -0
- package/package.json +7 -3
- package/src/cmd/origins.js +2 -2
- package/src/modules/Router.js +130 -0
- package/src/modules/_FormData.js +60 -0
- package/src/modules/_Headers.js +88 -0
- package/src/modules/_MessageStream.js +191 -0
- package/src/modules/_NavigationEvent.js +89 -0
- package/src/modules/_Request.js +61 -0
- package/src/modules/_RequestHeaders.js +72 -0
- package/src/modules/_Response.js +56 -0
- package/src/modules/_ResponseHeaders.js +81 -0
- package/src/modules/_URL.js +111 -0
- package/src/modules/client/Client.js +32 -25
- package/src/modules/client/Http.js +20 -5
- package/src/modules/client/NavigationEvent.js +20 -0
- package/src/modules/client/Router.js +30 -106
- package/src/modules/client/StdRequest.js +4 -8
- package/src/modules/client/Storage.js +6 -6
- package/src/modules/client/Worker.js +11 -49
- package/src/modules/client/WorkerClient.js +5 -38
- package/src/modules/client/WorkerComm.js +183 -0
- package/src/modules/client/effects/sounds.js +64 -0
- package/src/modules/server/NavigationEvent.js +38 -0
- package/src/modules/server/Router.js +53 -126
- package/src/modules/server/Server.js +174 -82
- package/src/modules/util.js +7 -7
- package/src/modules/NavigationEvent.js +0 -53
- package/src/modules/Response.js +0 -98
- package/src/modules/XURL.js +0 -125
- package/src/modules/server/StdIncomingMessage.js +0 -73
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Base installations
|
|
2
|
+
FROM node:12-alpine
|
|
3
|
+
RUN apk --no-cache add git
|
|
4
|
+
|
|
5
|
+
# We'll install npm packages at one-level higher than
|
|
6
|
+
# actuall app root, so that we can bind-mount host system's app root
|
|
7
|
+
# without overriding the container's node_modules directory
|
|
8
|
+
WORKDIR /home/www
|
|
9
|
+
RUN npm install
|
|
10
|
+
RUN npm install @webqit/webflo -g
|
|
11
|
+
RUN npm install @webqit/playui-cli -g
|
|
12
|
+
|
|
13
|
+
# Move one-level in, for the reasons above
|
|
14
|
+
WORKDIR /home/www/app
|
|
15
|
+
|
|
16
|
+
# To auto-start app (flags optional), we would add...
|
|
17
|
+
# CMD ["webflo", "start", "--env=dev", "--http-only"]
|
|
18
|
+
|
|
19
|
+
# To build the image locally...
|
|
20
|
+
# docker build --no-cache -t webflo ./docker
|
|
21
|
+
|
|
22
|
+
# To publish to docker hub...
|
|
23
|
+
# docker login -u webqit
|
|
24
|
+
# docker tag webflo webqit/webflo
|
|
25
|
+
# docker push webqit/webflo
|
package/docker/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Webflo Container
|
|
2
|
+
This is simply a node.js container with the `@webqit/webflo` framework installed. Once started, any webflo app can be deployed into the container from any git repository.
|
|
3
|
+
|
|
4
|
+
## Usage
|
|
5
|
+
This container image lives on Docker Hub and can be pulled to a local machine or a remote Virtual Machine (VM) on any cloud platform.
|
|
6
|
+
|
|
7
|
+
+ [To Use Locally](#to-use-locally)
|
|
8
|
+
+ [To Use In the Cloud](#to-use-in-the-cloud)
|
|
9
|
+
* [To Deploy An App From Any Repo](#to-deploy-an-app-from-any-repo)
|
|
10
|
+
|
|
11
|
+
### To Use Locally
|
|
12
|
+
Ensure you have docker installed on your computer and run the following command from any location on your terminal:
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
docker pull webqit/webflo:latest
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The above command pulls the `webqit/webflo` image to your local machine. (But this can be automatically done by docker on running any docker commands that reference the `webqit/webflo` image.)
|
|
19
|
+
|
|
20
|
+
Next is to use the following commands to start the container and the Webflo runtime. In each case, the first part of the command starts the container, while the second part (from `webflo start`) starts the application.
|
|
21
|
+
|
|
22
|
+
#### To Start
|
|
23
|
+
Start the container using `docker run`; map a port (e.g `80`) of your host machine to `3000` of the container (unless changed webflo expects to run on port `3000`); optionally, give your container a name; reference `webqit/webflo` as the image to use; and lastly, start webflo using `webflo start`.
|
|
24
|
+
|
|
25
|
+
```shell
|
|
26
|
+
docker run -d -p 80:3000 --name my-app webqit/webflo webflo start
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Visit [localhost](http://localhost) to view your app.
|
|
30
|
+
|
|
31
|
+
#### To Start In Dev Mode
|
|
32
|
+
Webflo's *dev* mode is the perfect mode for developing locally. All you do is append the `--env=dev` flag to your webflo commands. [(Learn more)](#)
|
|
33
|
+
|
|
34
|
+
```shell
|
|
35
|
+
docker run -d -p 80:3000 --name my-app webqit/webflo webflo start --env=dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
In *dev* mode, webflo automatically restarts as you make changes to your codebase. Since webflo now lives inside a container, you'll need to *bind* the directory of your source code on your host machine to the `/home/www/app` directory of the container.
|
|
39
|
+
|
|
40
|
+
```shell
|
|
41
|
+
docker run -d -v /Users/me/my-app:/home/www/app -p 80:3000 --name my-app webqit/webflo webflo start --env=dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### To Use In the Cloud
|
|
45
|
+
TODO
|
|
46
|
+
|
|
47
|
+
### To Deploy An App From Any Repo
|
|
48
|
+
Whether running locally or in the cloud, webflo can easily take your application from any git repo. This follows webflo's normal `deploy` command.
|
|
49
|
+
|
|
50
|
+
Simply point docker at your container (using `docker exec [container-name]`) and execute the `webflo deploy` command.
|
|
51
|
+
|
|
52
|
+
```shell
|
|
53
|
+
docker exec my-app webflo deploy https://github.com/me/my-app
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If you will need to install any npm dependencies, you would run `npm install` on the appropriate directory in your container.
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
docker exec my-app npm install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If you will need to run additional webflo commands (e.g `webflo restart` to restart the application), you would follow the same pattern above.
|
|
63
|
+
|
|
64
|
+
```shell
|
|
65
|
+
docker exec my-app webflo restart
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Extending this Build
|
|
69
|
+
TODO
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"vanila-javascript"
|
|
13
13
|
],
|
|
14
14
|
"homepage": "https://webqit.io/tooling/webflo",
|
|
15
|
-
"version": "0.8.
|
|
15
|
+
"version": "0.8.49",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
@@ -38,17 +38,21 @@
|
|
|
38
38
|
"@octokit/webhooks": "^7.15.1",
|
|
39
39
|
"@webqit/backpack": "^0.0.37",
|
|
40
40
|
"@webqit/browser-pie": "^0.0.16",
|
|
41
|
-
"@webqit/pseudo-browser": "^0.3.
|
|
41
|
+
"@webqit/pseudo-browser": "^0.3.35",
|
|
42
42
|
"@webqit/util": "^0.8.7",
|
|
43
|
-
"accepts": "^1.3.7",
|
|
44
43
|
"client-sessions": "^0.8.0",
|
|
45
44
|
"cookie": "^0.4.1",
|
|
45
|
+
"form-data-encoder": "^1.6.0",
|
|
46
|
+
"formdata-node": "^4.3.0",
|
|
46
47
|
"formidable": "^2.0.0-dev.20200131.2",
|
|
47
48
|
"is-glob": "^4.0.1",
|
|
48
49
|
"micromatch": "^3.1.10",
|
|
50
|
+
"mime-types": "^2.1.33",
|
|
49
51
|
"minimatch": "^3.0.4",
|
|
52
|
+
"node-fetch": "^2.6.1",
|
|
50
53
|
"pm2": "^5.1.0",
|
|
51
54
|
"simple-git": "^2.20.1",
|
|
55
|
+
"stream-slice": "^0.1.2",
|
|
52
56
|
"touch": "^3.1.0",
|
|
53
57
|
"undici": "^4.3.1",
|
|
54
58
|
"uuid": "^8.3.2",
|
package/src/cmd/origins.js
CHANGED
|
@@ -64,9 +64,9 @@ export async function deploy(Ui, origin, flags = {}, layout = {}) {
|
|
|
64
64
|
Fs.mkdirSync(origin.deploy_path, {recursive: true});
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
git.cwd(origin.deploy_path);
|
|
67
|
+
await git.cwd(origin.deploy_path);
|
|
68
68
|
// Must come after git.cwd()
|
|
69
|
-
git.init();
|
|
69
|
+
await git.init();
|
|
70
70
|
|
|
71
71
|
const hosts = {
|
|
72
72
|
github: 'https://github.com',
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @imports
|
|
4
|
+
*/
|
|
5
|
+
import _isString from '@webqit/util/js/isString.js';
|
|
6
|
+
import _isFunction from '@webqit/util/js/isFunction.js';
|
|
7
|
+
import _isArray from '@webqit/util/js/isArray.js';
|
|
8
|
+
import _arrFrom from '@webqit/util/arr/from.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ---------------------------
|
|
12
|
+
* The Router class
|
|
13
|
+
* ---------------------------
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export default class Router {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Constructs a new Router instance
|
|
20
|
+
* over route definitions.
|
|
21
|
+
*
|
|
22
|
+
* @param string|array path
|
|
23
|
+
* @param object layout
|
|
24
|
+
* @param object context
|
|
25
|
+
*
|
|
26
|
+
* @return void
|
|
27
|
+
*/
|
|
28
|
+
constructor(path, layout, context) {
|
|
29
|
+
this.path = _isArray(path) ? path : (path + '').split('/').filter(a => a);
|
|
30
|
+
this.layout = layout;
|
|
31
|
+
this.context = context;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Performs dynamic routing.
|
|
36
|
+
*
|
|
37
|
+
* @param array|string method
|
|
38
|
+
* @param Object event
|
|
39
|
+
* @param any arg
|
|
40
|
+
* @param function _default
|
|
41
|
+
*
|
|
42
|
+
* @return object
|
|
43
|
+
*/
|
|
44
|
+
async route(method, event, arg, _default) {
|
|
45
|
+
|
|
46
|
+
const $this = this;
|
|
47
|
+
|
|
48
|
+
// ----------------
|
|
49
|
+
// The loop
|
|
50
|
+
// ----------------
|
|
51
|
+
const next = async function(thisTick) {
|
|
52
|
+
const _this = { ...$this.context };
|
|
53
|
+
if (!thisTick.trail || thisTick.trail.length < thisTick.destination.length) {
|
|
54
|
+
thisTick = await $this.readTick(thisTick);
|
|
55
|
+
// -------------
|
|
56
|
+
_this.pathname = `/${thisTick.trail.join('/')}`;
|
|
57
|
+
_this.stepname = thisTick.trail[thisTick.trail.length - 1];
|
|
58
|
+
$this.finalizeHandlerContext(_this, thisTick);
|
|
59
|
+
// -------------
|
|
60
|
+
if (thisTick.exports) {
|
|
61
|
+
const methods = _arrFrom(thisTick.method);
|
|
62
|
+
const handler = _isFunction(thisTick.exports) && methods.includes('default') ? thisTick.exports : methods.reduce((_handler, name) => _handler || thisTick.exports[name.toLowerCase()], null);
|
|
63
|
+
if (handler) {
|
|
64
|
+
// -------------
|
|
65
|
+
// Dynamic response
|
|
66
|
+
// -------------
|
|
67
|
+
const _next = async (..._args) => {
|
|
68
|
+
const nextTick = { ...thisTick, arg: _args[0] };
|
|
69
|
+
if (_args.length > 1) {
|
|
70
|
+
var _url = _args[1], _request, requestInit = { ...(_args[2] || {}) };
|
|
71
|
+
if (_args[1] instanceof nextTick.event.Request) {
|
|
72
|
+
_request = _args[1];
|
|
73
|
+
_url = _request.url;
|
|
74
|
+
} else if (!_isString(_url)) {
|
|
75
|
+
throw new Error('Router redirect url must be a string!');
|
|
76
|
+
}
|
|
77
|
+
var newDestination = _url.startsWith('/') ? _url : $this.pathJoin(`/${thisTick.trail.join('/')}`, _url);
|
|
78
|
+
if (newDestination.startsWith('../')) {
|
|
79
|
+
throw new Error('Router redirect cannot traverse beyond the routing directory! (' + _url + ' >> ' + newDestination + ')');
|
|
80
|
+
}
|
|
81
|
+
if (requestInit.method) {
|
|
82
|
+
nextTick.method = requestInit.method;
|
|
83
|
+
if (_isArray(requestInit.method)) {
|
|
84
|
+
requestInit.method = requestInit.method[0];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (_request) {
|
|
88
|
+
nextTick.event = thisTick.event.retarget(_request, { ...requestInit, _proxy: { url: newDestination/** non-standard but works */ } });
|
|
89
|
+
} else {
|
|
90
|
+
nextTick.event = thisTick.event.retarget(newDestination, requestInit);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
nextTick.source = thisTick.destination.join('/');
|
|
94
|
+
nextTick.destination = newDestination.split('?').shift().split('/').map(a => a.trim()).filter(a => a);
|
|
95
|
+
nextTick.trail = _args[1].startsWith('/') ? [] : thisTick.trail.reduce((_commonRoot, _seg, i) => _commonRoot.length === i && _seg === nextTick.destination[i] ? _commonRoot.concat(_seg) : _commonRoot, []);
|
|
96
|
+
nextTick.trailOnFile = thisTick.trailOnFile.slice(0, nextTick.trail.length);
|
|
97
|
+
}
|
|
98
|
+
return next(nextTick);
|
|
99
|
+
};
|
|
100
|
+
// -------------
|
|
101
|
+
const nextPathname = thisTick.destination.slice(thisTick.trail.length);
|
|
102
|
+
_next.pathname = nextPathname.join('/');
|
|
103
|
+
_next.stepname = nextPathname[0];
|
|
104
|
+
// -------------
|
|
105
|
+
return await handler.call(_this, thisTick.event, thisTick.arg, _next/*next*/);
|
|
106
|
+
}
|
|
107
|
+
// Handler not found but exports found
|
|
108
|
+
return next(thisTick);
|
|
109
|
+
} else if ((thisTick.currentSegmentOnFile || {}).dirExists) {
|
|
110
|
+
// Exports not found but directory found
|
|
111
|
+
return next(thisTick);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// -------------
|
|
115
|
+
// Local file
|
|
116
|
+
// -------------
|
|
117
|
+
if (_default) {
|
|
118
|
+
return await _default.call(_this, thisTick.event, thisTick.arg);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return next({
|
|
123
|
+
destination: this.path,
|
|
124
|
+
event,
|
|
125
|
+
method,
|
|
126
|
+
arg,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @imports
|
|
4
|
+
*/
|
|
5
|
+
import { _isTypeObject, _isNumeric } from '@webqit/util/js/index.js';
|
|
6
|
+
import { _before } from '@webqit/util/str/index.js';
|
|
7
|
+
import { wwwFormSet, wwwFormPathSerializeCallback } from './util.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The _Headers Mixin
|
|
11
|
+
*/
|
|
12
|
+
const _FormData = NativeFormData => class extends NativeFormData {
|
|
13
|
+
|
|
14
|
+
tee(callback = null) {
|
|
15
|
+
const formData1 = new this.constructor, formData2 = new this.constructor;
|
|
16
|
+
for (var [ name, value ] of this.entries()) {
|
|
17
|
+
const formDataType = formDataType(value);
|
|
18
|
+
if ((callback && callback(value, name, formDataType)) || (!callback && !formDataType)) {
|
|
19
|
+
formData1.append(name, value);
|
|
20
|
+
} else {
|
|
21
|
+
formData2.append(name, value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return [ formData1, formData2 ];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
json(data = {}, callback = null) {
|
|
28
|
+
if (arguments.length) {
|
|
29
|
+
Object.keys(data).forEach(key => {
|
|
30
|
+
wwwFormPathSerializeCallback(key, data[key], (_wwwFormPath, _value) => {
|
|
31
|
+
if (!callback || callback(_wwwFormPath, _value, _isTypeObject(_value))) {
|
|
32
|
+
this.append(_wwwFormPath, _value);
|
|
33
|
+
}
|
|
34
|
+
}, value => !formDataType(value));
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
var jsonBuild; // We'll dynamically determine if this should be an array or an object
|
|
39
|
+
for (var [ name, value ] of this.entries()) {
|
|
40
|
+
if (!jsonBuild) {
|
|
41
|
+
jsonBuild = _isNumeric(_before(name, '[')) ? [] : {};
|
|
42
|
+
}
|
|
43
|
+
wwwFormSet(jsonBuild, name, value);
|
|
44
|
+
}
|
|
45
|
+
return jsonBuild;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default _FormData;
|
|
51
|
+
|
|
52
|
+
export const formDataType = (value, list = null) => {
|
|
53
|
+
if (!_isTypeObject(value)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const toStringTag = value[Symbol.toStringTag];
|
|
57
|
+
return (list || [
|
|
58
|
+
'Uint8Array', 'Uint16Array', 'Uint32Array', 'ArrayBuffer', 'Blob', 'File', 'FormData', 'Stream'
|
|
59
|
+
]).reduce((_toStringTag, type) => _toStringTag || (toStringTag === type ? type : null), null);
|
|
60
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @imports
|
|
4
|
+
*/
|
|
5
|
+
import { _after, _beforeLast } from "@webqit/util/str/index.js";
|
|
6
|
+
import { _isString, _getType, _isObject, _isFunction } from "@webqit/util/js/index.js";
|
|
7
|
+
import { _isTypeObject } from '@webqit/util/js/index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The _Headers Mixin
|
|
11
|
+
*/
|
|
12
|
+
const _Headers = NativeHeaders => class extends NativeHeaders {
|
|
13
|
+
|
|
14
|
+
// construct
|
|
15
|
+
constructor(definition = {}) {
|
|
16
|
+
if (definition instanceof NativeHeaders) {
|
|
17
|
+
// It's another Headers instance
|
|
18
|
+
super(definition);
|
|
19
|
+
} else {
|
|
20
|
+
super();
|
|
21
|
+
this.json(definition);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
json(headers = {}, replace = true) {
|
|
26
|
+
if (arguments.length) {
|
|
27
|
+
const _setters = getAllPropertyDescriptors(this);
|
|
28
|
+
const setters = Object.keys(_setters).reduce((list, key) => list.concat((typeof key !== 'symbol') && ('set' in _setters[key]) ? key : []), []);
|
|
29
|
+
Object.keys(headers).forEach(name => {
|
|
30
|
+
var nameCs = setters.reduce((prev, curr) => prev || (curr === name || curr.toLocaleLowerCase() === name ? curr : null), null);
|
|
31
|
+
if (nameCs) {
|
|
32
|
+
if (replace || this[nameCs] === undefined) {
|
|
33
|
+
this[nameCs] = headers[name];
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
if (replace || !this.has(name)) {
|
|
37
|
+
this.set(name, headers[name]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const _headers = {};
|
|
44
|
+
for (var [ name, value ] of this) {
|
|
45
|
+
_headers[name] = value;
|
|
46
|
+
}
|
|
47
|
+
return _headers;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
set cacheControl(value) {
|
|
51
|
+
return this.set('Cache-Control', value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get cacheControl() {
|
|
55
|
+
return this.get('Cache-Control');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set contentLength(value) {
|
|
59
|
+
return this.set('Content-Length', value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get contentLength() {
|
|
63
|
+
return this.get('Content-Length');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
set contentType(value) {
|
|
67
|
+
return this.set('Content-Type', value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get contentType() {
|
|
71
|
+
return this.get('Content-Type');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default _Headers;
|
|
77
|
+
|
|
78
|
+
function getAllPropertyDescriptors(obj) {
|
|
79
|
+
if (!obj) {
|
|
80
|
+
return Object.create(null);
|
|
81
|
+
} else {
|
|
82
|
+
const proto = Object.getPrototypeOf(obj);
|
|
83
|
+
return proto === Object.prototype ? {} : {
|
|
84
|
+
...getAllPropertyDescriptors(proto),
|
|
85
|
+
...Object.getOwnPropertyDescriptors(obj)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @imports
|
|
4
|
+
*/
|
|
5
|
+
import { _isArray, _isEmpty, _isNumber, _isObject, _isPlainArray, _isPlainObject, _isString } from '@webqit/util/js/index.js';
|
|
6
|
+
import { formDataType } from './_FormData.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The _Request Mixin
|
|
10
|
+
*/
|
|
11
|
+
const _MessageStream = (NativeMessageStream, Headers, FormData) => {
|
|
12
|
+
const MessageStream = class extends NativeMessageStream {
|
|
13
|
+
|
|
14
|
+
constructor(input, init = {}) {
|
|
15
|
+
var _proxy = {}, _meta = {};
|
|
16
|
+
if ((('headers' in init) && !(init instanceof Headers)) || ('_proxy' in init) || ('meta' in init)) {
|
|
17
|
+
init = { ...init };
|
|
18
|
+
if (('headers' in init) && !(init instanceof Headers)) {
|
|
19
|
+
init.headers = new Headers(init.headers);
|
|
20
|
+
}
|
|
21
|
+
if (('_proxy' in init)) {
|
|
22
|
+
_proxy = init._proxy;
|
|
23
|
+
delete init._proxy;
|
|
24
|
+
}
|
|
25
|
+
if (('meta' in init)) {
|
|
26
|
+
_meta = init.meta;
|
|
27
|
+
delete init.meta;
|
|
28
|
+
}
|
|
29
|
+
arguments[1] = init;
|
|
30
|
+
}
|
|
31
|
+
super(...arguments);
|
|
32
|
+
this._proxy = _proxy;
|
|
33
|
+
this._meta = _meta;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
clone() {
|
|
37
|
+
const clone = new this.constructor(super.clone());
|
|
38
|
+
clone._proxy = this._proxy;
|
|
39
|
+
clone._headers = this._headers;
|
|
40
|
+
clone._typedDataCache = this._typedDataCache;
|
|
41
|
+
clone._meta = this._meta;
|
|
42
|
+
return clone;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get url() {
|
|
46
|
+
return 'url' in this._proxy ? this._proxy.url : super.url;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get headers() {
|
|
50
|
+
if (!this._headers) {
|
|
51
|
+
this._headers = new Headers(super.headers);
|
|
52
|
+
}
|
|
53
|
+
return this._headers;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get original() {
|
|
57
|
+
return this._typedDataCache.original;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get originalType() {
|
|
61
|
+
return this._typedDataCache.originalType;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get meta() {
|
|
65
|
+
return this._meta || {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async arrayBuffer() {
|
|
69
|
+
if (this._typedDataCache.arrayBuffer) {
|
|
70
|
+
return this._typedDataCache.arrayBuffer;
|
|
71
|
+
}
|
|
72
|
+
return super.arrayBuffer();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async blob() {
|
|
76
|
+
if (this._typedDataCache.blob) {
|
|
77
|
+
return this._typedDataCache.blob;
|
|
78
|
+
}
|
|
79
|
+
return super.blob();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async formData() {
|
|
83
|
+
if (this._typedDataCache.formData) {
|
|
84
|
+
return this._typedDataCache.formData;
|
|
85
|
+
}
|
|
86
|
+
const formData = await super.formData();
|
|
87
|
+
formData.tee = FormData.prototype.tee.bind(formData);
|
|
88
|
+
formData.json = FormData.prototype.json.bind(formData);
|
|
89
|
+
return formData;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async json() {
|
|
93
|
+
if (this._typedDataCache.json) {
|
|
94
|
+
return this._typedDataCache.json;
|
|
95
|
+
}
|
|
96
|
+
return super.json();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async text() {
|
|
100
|
+
if (this._typedDataCache.text) {
|
|
101
|
+
return this._typedDataCache.text;
|
|
102
|
+
}
|
|
103
|
+
return super.text();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Payload
|
|
107
|
+
jsonBuild(force = false) {
|
|
108
|
+
if (!this._typedDataCache.jsonBuild || force) {
|
|
109
|
+
this._typedDataCache.jsonBuild = new Promise(async resolve => {
|
|
110
|
+
var request = this, jsonBuild, contentType = request.headers.get('content-type') || '';
|
|
111
|
+
var type = contentType === 'application/json' || this._typedDataCache.json ? 'json' : (
|
|
112
|
+
contentType === 'application/x-www-form-urlencoded' || contentType.startsWith('multipart/') || this._typedDataCache.formData || (!contentType && !['get'].includes((request.method || '').toLowerCase())) ? 'formData' : (
|
|
113
|
+
contentType === 'text/plain' ? 'plain' : 'other'
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
if (type === 'formData') {
|
|
117
|
+
jsonBuild = (await request.formData()).json();
|
|
118
|
+
} else {
|
|
119
|
+
jsonBuild = type === 'json' ? await request.json() : (
|
|
120
|
+
type === 'plain' ? await request.text() : request.body
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
resolve(jsonBuild);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return this._typedDataCache.jsonBuild;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
};
|
|
130
|
+
// ----------
|
|
131
|
+
MessageStream.Headers = Headers;
|
|
132
|
+
// ----------
|
|
133
|
+
return MessageStream;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default _MessageStream;
|
|
137
|
+
|
|
138
|
+
export function encodeBody(body, globals) {
|
|
139
|
+
const detailsObj = { body, original: body };
|
|
140
|
+
if (_isString(body) || _isNumber(body)) {
|
|
141
|
+
detailsObj.originalType = 'text';
|
|
142
|
+
detailsObj.text = body;
|
|
143
|
+
detailsObj.headers = {
|
|
144
|
+
contentLength: (body + '').length,
|
|
145
|
+
};
|
|
146
|
+
return detailsObj;
|
|
147
|
+
}
|
|
148
|
+
detailsObj.originalType = formDataType(body);
|
|
149
|
+
if ([ 'Blob', 'File' ].includes(detailsObj.originalType)) {
|
|
150
|
+
detailsObj.blob = body;
|
|
151
|
+
detailsObj.headers = {
|
|
152
|
+
contentType: body.type,
|
|
153
|
+
contentLength: body.size,
|
|
154
|
+
};
|
|
155
|
+
} else if ([ 'Uint8Array', 'Uint16Array', 'Uint32Array', 'ArrayBuffer' ].includes(detailsObj.originalType)) {
|
|
156
|
+
detailsObj.arrayBuffer = body;
|
|
157
|
+
detailsObj.headers = {
|
|
158
|
+
contentLength: body.byteLength,
|
|
159
|
+
};
|
|
160
|
+
} else if (detailsObj.originalType === 'FormData') {
|
|
161
|
+
detailsObj.formData = body;
|
|
162
|
+
encodeFormData(detailsObj, body, globals);
|
|
163
|
+
} else if ((_isObject(body) && _isPlainObject(body)) || (_isArray(body) && _isPlainArray(body))) {
|
|
164
|
+
// Deserialize object while detecting if multipart
|
|
165
|
+
var hasBlobs, formData = new globals.FormData;
|
|
166
|
+
formData.json(body, (path, value, objectType) => {
|
|
167
|
+
hasBlobs = hasBlobs || objectType;
|
|
168
|
+
return true;
|
|
169
|
+
});
|
|
170
|
+
if (hasBlobs) {
|
|
171
|
+
detailsObj.formData = formData;
|
|
172
|
+
encodeFormData(detailsObj, formData, globals);
|
|
173
|
+
} else {
|
|
174
|
+
detailsObj.json = body;
|
|
175
|
+
detailsObj.body = JSON.stringify(body);
|
|
176
|
+
detailsObj.headers = {
|
|
177
|
+
contentType: 'application/json',
|
|
178
|
+
contentLength: Buffer.byteLength(detailsObj.body, 'utf8'), // Buffer.from(string).length
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
detailsObj.jsonBuild = body;
|
|
182
|
+
}
|
|
183
|
+
return detailsObj;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const encodeFormData = (detailsObj, formData, globals) => {
|
|
187
|
+
if (!globals.FormDataEncoder || !globals.ReadableStream) return;
|
|
188
|
+
const encoder = new globals.FormDataEncoder(formData);
|
|
189
|
+
detailsObj.body = globals.ReadableStream.from(encoder.encode());
|
|
190
|
+
detailsObj.headers = encoder.headers;
|
|
191
|
+
};
|