@webqit/webflo 0.11.61 → 1.0.1
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/package.json +1 -1
- package/src/{Context.js → AbstractContext.js} +1 -9
- package/src/deployment-pi/origins/index.js +1 -1
- package/src/index.js +1 -9
- package/src/runtime-pi/HttpEvent.js +101 -81
- package/src/runtime-pi/HttpUser.js +126 -0
- package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
- package/src/runtime-pi/MessagingOverChannel.js +85 -0
- package/src/runtime-pi/MessagingOverSocket.js +106 -0
- package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
- package/src/runtime-pi/WebfloCookieStorage.js +27 -0
- package/src/runtime-pi/WebfloEventTarget.js +39 -0
- package/src/runtime-pi/WebfloMessageEvent.js +58 -0
- package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
- package/src/runtime-pi/{Router.js → WebfloRouter.js} +3 -34
- package/src/runtime-pi/WebfloRuntime.js +52 -0
- package/src/runtime-pi/WebfloStorage.js +109 -0
- package/src/runtime-pi/client/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/Context.js +2 -6
- package/src/runtime-pi/client/CookieStorage.js +17 -0
- package/src/runtime-pi/client/Router.js +3 -13
- package/src/runtime-pi/client/SessionStorage.js +33 -0
- package/src/runtime-pi/client/Url.js +24 -72
- package/src/runtime-pi/client/WebfloClient.js +544 -0
- package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
- package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
- package/src/runtime-pi/client/WebfloSubClient.js +165 -0
- package/src/runtime-pi/client/Workport.js +89 -161
- package/src/runtime-pi/client/generate.js +3 -3
- package/src/runtime-pi/client/index.js +13 -18
- package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/worker/Context.js +2 -6
- package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
- package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
- package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
- package/src/runtime-pi/client/worker/Workport.js +13 -73
- package/src/runtime-pi/client/worker/index.js +7 -18
- package/src/runtime-pi/index.js +1 -8
- package/src/runtime-pi/server/ClientMessaging.js +18 -0
- package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
- package/src/runtime-pi/server/Context.js +2 -6
- package/src/runtime-pi/server/CookieStorage.js +17 -0
- package/src/runtime-pi/server/Router.js +2 -68
- package/src/runtime-pi/server/SessionStorage.js +53 -0
- package/src/runtime-pi/server/WebfloServer.js +755 -0
- package/src/runtime-pi/server/index.js +7 -18
- package/src/runtime-pi/util-http.js +268 -32
- package/src/runtime-pi/xURL.js +25 -22
- package/src/runtime-pi/xfetch.js +2 -2
- package/src/runtime-pi/Application.js +0 -29
- package/src/runtime-pi/Cookies.js +0 -82
- package/src/runtime-pi/Runtime.js +0 -21
- package/src/runtime-pi/client/Application.js +0 -76
- package/src/runtime-pi/client/Runtime.js +0 -525
- package/src/runtime-pi/client/createStorage.js +0 -58
- package/src/runtime-pi/client/worker/Application.js +0 -44
- package/src/runtime-pi/client/worker/Runtime.js +0 -275
- package/src/runtime-pi/server/Application.js +0 -101
- package/src/runtime-pi/server/Runtime.js +0 -558
- package/src/runtime-pi/xFormData.js +0 -24
- package/src/runtime-pi/xHeaders.js +0 -146
- package/src/runtime-pi/xRequest.js +0 -46
- package/src/runtime-pi/xRequestHeaders.js +0 -109
- package/src/runtime-pi/xResponse.js +0 -33
- package/src/runtime-pi/xResponseHeaders.js +0 -117
- package/src/runtime-pi/xxHttpMessage.js +0 -102
package/package.json
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
+
export class AbstractContext {
|
|
1
2
|
|
|
2
|
-
export default class Context {
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Initializes a context.
|
|
6
|
-
*
|
|
7
|
-
* @param Object dict
|
|
8
|
-
* @param String CD
|
|
9
|
-
*/
|
|
10
3
|
constructor(dict, CD = null) {
|
|
11
4
|
// dict can be plain object or some Context instance itself
|
|
12
5
|
// Using it as only a prototype protects it from being mutated down here
|
|
@@ -76,5 +69,4 @@ export default class Context {
|
|
|
76
69
|
set logger(value) {
|
|
77
70
|
Object.defineProperty(this.dict, 'logger', { value } );
|
|
78
71
|
}
|
|
79
|
-
|
|
80
72
|
}
|
|
@@ -198,7 +198,7 @@ export async function webhook(httpEvent, router, next) {
|
|
|
198
198
|
}, _isNumeric(deployParams.ondeploy_autoexit) ? parseInt(deployParams.ondeploy_autoexit) : 0);
|
|
199
199
|
}
|
|
200
200
|
resolve(
|
|
201
|
-
new
|
|
201
|
+
new Response(`Deployment ${ exitCode === 0 ? 'success' : 'error: ' + exitCode }!`, { status: exitCode === 0 ? 200 : 500 })
|
|
202
202
|
);
|
|
203
203
|
if (cx.logger) {
|
|
204
204
|
cx.logger.log('');
|
package/src/index.js
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
1
|
import * as config from './config-pi/index.js';
|
|
6
2
|
import * as deployment from './deployment-pi/index.js';
|
|
7
3
|
import * as runtime from './runtime-pi/index.js';
|
|
8
4
|
import * as services from './services-pi/index.js';
|
|
9
|
-
import Context from './Context.js';
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
* @exports
|
|
13
|
-
*/
|
|
6
|
+
export { AbstractContext as Context } from './AbstractContext.js';
|
|
14
7
|
export {
|
|
15
8
|
config,
|
|
16
9
|
deployment,
|
|
17
10
|
runtime,
|
|
18
11
|
services,
|
|
19
|
-
Context,
|
|
20
12
|
}
|
|
@@ -1,107 +1,127 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
|
-
import { _isEmpty } from '@webqit/util/js/index.js';
|
|
6
|
-
import xRequest from "./xRequest.js";
|
|
7
|
-
import xResponse from "./xResponse.js";
|
|
1
|
+
import { _isEmpty, _isObject } from '@webqit/util/js/index.js';
|
|
8
2
|
import xURL from "./xURL.js";
|
|
9
3
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Initializes a new HttpEvent instance.
|
|
17
|
-
*
|
|
18
|
-
* @param Request _request
|
|
19
|
-
* @param Object _detail
|
|
20
|
-
* @param Function _sessionFactory
|
|
21
|
-
* @param Function _storageFactory
|
|
22
|
-
*/
|
|
23
|
-
constructor(_request, _detail, _sessionFactory, _storageFactory) {
|
|
24
|
-
this._request = _request;
|
|
25
|
-
this._detail = _detail || {};
|
|
26
|
-
this._sessionFactory = _sessionFactory;
|
|
27
|
-
this._storageFactory = _storageFactory;
|
|
28
|
-
// -------
|
|
29
|
-
this.Request = xRequest;
|
|
30
|
-
this.Response = xResponse;
|
|
31
|
-
this.URL = xURL;
|
|
32
|
-
// -------
|
|
33
|
-
this.port = {
|
|
34
|
-
listeners: [],
|
|
35
|
-
post(message) {
|
|
36
|
-
const promises = this.listeners.map(listener => listener(message))
|
|
37
|
-
.filter(returnValue => returnValue instanceof Promise);
|
|
38
|
-
if (process.length) return Promise.all(promises);
|
|
39
|
-
},
|
|
40
|
-
listen(listener) { this.listeners.push(listener); },
|
|
41
|
-
}
|
|
4
|
+
export class HttpEvent {
|
|
5
|
+
|
|
6
|
+
static create(parentEvent, init = {}) {
|
|
7
|
+
return new this(parentEvent, init);
|
|
42
8
|
}
|
|
43
9
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
10
|
+
#parentEvent;
|
|
11
|
+
#init;
|
|
12
|
+
#url;
|
|
13
|
+
#requestCloneCallback;
|
|
14
|
+
|
|
15
|
+
constructor(parentEvent, init = {}) {
|
|
16
|
+
this.#parentEvent = parentEvent;
|
|
17
|
+
this.#init = init;
|
|
18
|
+
this.#url = new xURL(init.url || init.request.url);
|
|
50
19
|
}
|
|
51
20
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
21
|
+
get url() { return this.#url; }
|
|
22
|
+
|
|
23
|
+
get request() { return this.#init.request; }
|
|
24
|
+
|
|
25
|
+
get detail() { return this.#init.detail; }
|
|
26
|
+
|
|
27
|
+
get cookies() { return this.#init.cookies; }
|
|
28
|
+
|
|
29
|
+
get session() { return this.#init.session; }
|
|
30
|
+
|
|
31
|
+
get user() { return this.#init.user; }
|
|
32
|
+
|
|
33
|
+
get client() { return this.#init.client; }
|
|
34
|
+
|
|
35
|
+
set onRequestClone(callback) {
|
|
36
|
+
this.#requestCloneCallback = callback;
|
|
55
37
|
}
|
|
56
38
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
39
|
+
clone() {
|
|
40
|
+
const request = this.#requestCloneCallback?.() || this.request;
|
|
41
|
+
const init = { ...this.#init, request };
|
|
42
|
+
const instance = this.constructor.create(init);
|
|
43
|
+
instance.#requestCloneCallback = this.#requestCloneCallback;
|
|
44
|
+
return instance;
|
|
60
45
|
}
|
|
61
46
|
|
|
62
|
-
|
|
63
|
-
get
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
#response = null;
|
|
48
|
+
get response() { return this.#response; }
|
|
49
|
+
|
|
50
|
+
#responseOrigin = null;
|
|
51
|
+
|
|
52
|
+
async #respondWith(response) {
|
|
53
|
+
/*
|
|
54
|
+
if (this.#response) {
|
|
55
|
+
throw new Error(`Event has already been responded to! (${this.#responseOrigin})`);
|
|
56
|
+
}
|
|
57
|
+
*/
|
|
58
|
+
this.#response = response;
|
|
59
|
+
/*
|
|
60
|
+
if (!this.#responseOrigin) {
|
|
61
|
+
const stack = new Error().stack;
|
|
62
|
+
const stackLines = stack.split('\n');
|
|
63
|
+
this.#responseOrigin = stackLines[3].trim();
|
|
64
|
+
}
|
|
65
|
+
*/
|
|
66
|
+
if (this.#parentEvent instanceof HttpEvent) {
|
|
67
|
+
/*
|
|
68
|
+
// Set responseOrigin first to prevent parent from repeating the work
|
|
69
|
+
this.#parentEvent.#responseOrigin = this.#responseOrigin;
|
|
70
|
+
*/
|
|
71
|
+
// Ensure the respondWith() method is how we propagate response
|
|
72
|
+
await this.#parentEvent.respondWith(this.#response);
|
|
73
|
+
} else {
|
|
74
|
+
// The callback passed at root
|
|
75
|
+
await this.#parentEvent?.(response);
|
|
66
76
|
}
|
|
67
|
-
return this._session;
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!this._storage) {
|
|
73
|
-
this._storage = this.storageFactory();
|
|
74
|
-
}
|
|
75
|
-
return this._storage;
|
|
79
|
+
async respondWith(response) {
|
|
80
|
+
await this.#respondWith(response);
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return this._sessionFactory(...args);
|
|
83
|
+
async defer() {
|
|
84
|
+
await this.#respondWith(new Response(null, { status: 202/*Accepted*/ }));
|
|
81
85
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return this._storageFactory(...args);
|
|
86
|
+
|
|
87
|
+
deferred() {
|
|
88
|
+
return this.#response?.status === 202;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
async redirect(url, status = 302) {
|
|
92
|
+
await this.#respondWith(new Response(null, { status, headers: {
|
|
93
|
+
Location: url
|
|
94
|
+
} }));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async redirectWith(url, data, ...args) {
|
|
98
|
+
if (!_isObject(data)) {
|
|
99
|
+
throw new Error('Data must be a JSON object');
|
|
100
|
+
}
|
|
101
|
+
const messageID = (0 | Math.random() * 9e6).toString(36);
|
|
102
|
+
const $url = new URL(url, this.request.url);
|
|
103
|
+
$url.searchParams.set('redirect-message', messageID);
|
|
104
|
+
this.session.set(`redirect-message:${messageID}`, data);
|
|
105
|
+
await this.redirect($url, ...args);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
redirected() {
|
|
109
|
+
return [301, 302, 303, 307, 308].includes(this.#response?.status);
|
|
91
110
|
}
|
|
92
111
|
|
|
93
|
-
// "with()"
|
|
94
112
|
async with(url, init = {}) {
|
|
95
|
-
|
|
113
|
+
if (!this.request) {
|
|
114
|
+
return new HttpEvent(this, { ...this.#init, url });
|
|
115
|
+
}
|
|
116
|
+
let request, _;
|
|
96
117
|
if (url instanceof Request) {
|
|
97
|
-
if (init instanceof Request) {
|
|
98
|
-
request = !_isEmpty(init) ? new
|
|
118
|
+
if (init instanceof Request) { ({ url: _, ...init } = await Request.copy(init)); }
|
|
119
|
+
request = !_isEmpty(init) ? new Request(url, init) : url;
|
|
99
120
|
} else {
|
|
100
|
-
url = new
|
|
101
|
-
|
|
102
|
-
request = new
|
|
121
|
+
url = new xURL(url, this.#url.origin);
|
|
122
|
+
init = await Request.copy(this.request, init);
|
|
123
|
+
request = new Request(url, { ...init, referrer: this.request.url });
|
|
103
124
|
}
|
|
104
|
-
return new HttpEvent(
|
|
125
|
+
return new HttpEvent(this, { ...this.#init, request });
|
|
105
126
|
}
|
|
106
|
-
|
|
107
127
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { _isObject } from '@webqit/util/js/index.js';
|
|
2
|
+
import { WebfloStorage } from './WebfloStorage.js';
|
|
3
|
+
|
|
4
|
+
export class HttpUser extends WebfloStorage {
|
|
5
|
+
|
|
6
|
+
static create(request, session, client) {
|
|
7
|
+
return new this(request, session, client);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
#session;
|
|
11
|
+
#client;
|
|
12
|
+
|
|
13
|
+
constructor(request, session, client) {
|
|
14
|
+
super(request, session);
|
|
15
|
+
this.#session = session;
|
|
16
|
+
this.#client = client;
|
|
17
|
+
// Trigger this
|
|
18
|
+
this.#dict;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get #dict() {
|
|
22
|
+
if (!this.#session.has('user')) {
|
|
23
|
+
this.#session.set('user', {});
|
|
24
|
+
}
|
|
25
|
+
return this.#session.get('user');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
[ Symbol.iterator ]() { return this.entries()[ Symbol.iterator ](); }
|
|
29
|
+
|
|
30
|
+
get size() { return Object.keys(this.#dict).length; }
|
|
31
|
+
|
|
32
|
+
set(key, value) {
|
|
33
|
+
Reflect.set(this.#dict, key, value);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get(key) {
|
|
38
|
+
return Reflect.get(this.#dict, key);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
has(key) {
|
|
42
|
+
return Reflect.has(this.#dict, key);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
delete(key) {
|
|
46
|
+
return Reflect.deleteProperty(this.#dict, key);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
keys() {
|
|
50
|
+
return Object.keys(this.#dict);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
values() {
|
|
54
|
+
return Object.values(this.#dict);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
entries() {
|
|
58
|
+
return Object.entries(this.#dict);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
clear() {
|
|
62
|
+
for (const key of this.keys()) {
|
|
63
|
+
Reflect.deleteProperty(this.#dict, key);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
forEach(callback) {
|
|
68
|
+
this.entries().forEach(callback);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
json(arg = null) {
|
|
72
|
+
if (!arguments.length || typeof arg === 'boolean') {
|
|
73
|
+
return {...this.#dict};
|
|
74
|
+
}
|
|
75
|
+
if (!_isObject(arg)) {
|
|
76
|
+
throw new Error(`Argument must be a valid JSON object`);
|
|
77
|
+
}
|
|
78
|
+
Object.assign(this.#dict, arg);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
isSignedIn() {
|
|
82
|
+
return this.has('id');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async signIn(...args) {
|
|
86
|
+
return await this.require(
|
|
87
|
+
['id'].concat(typeof args[0] === 'string' || Array.isArray(args[0]) ? args.unshift() : []),
|
|
88
|
+
...args
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async signOut() {
|
|
93
|
+
const handler = this.getReverseHandlers().get('id')?.[0];
|
|
94
|
+
let response;
|
|
95
|
+
if (typeof handler === 'string') {
|
|
96
|
+
response = new Response(null, { status: 302, headers: {
|
|
97
|
+
Location: url
|
|
98
|
+
}});
|
|
99
|
+
}
|
|
100
|
+
if (typeof handler === 'function') {
|
|
101
|
+
response = await handler(this);
|
|
102
|
+
}
|
|
103
|
+
this.clear();
|
|
104
|
+
return response;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
confirm(data, callback, options = {}) {
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
this.#client.postRequest(
|
|
110
|
+
data,
|
|
111
|
+
(event) => resolve(callback ? callback(event) : event),
|
|
112
|
+
{ ...options, messageType: 'confirm' }
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
prompt(data, callback, options = {}) {
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
this.#client.postRequest(
|
|
120
|
+
data,
|
|
121
|
+
(event) => resolve(callback ? callback(event) : event),
|
|
122
|
+
{ ...options, messageType: 'prompt' }
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MessagingOverChannel } from './MessagingOverChannel.js';
|
|
2
|
+
|
|
3
|
+
export class MessagingOverBroadcast extends MessagingOverChannel {
|
|
4
|
+
|
|
5
|
+
constructor(parentNode, instanceOrChannelName, params = {}) {
|
|
6
|
+
const port = typeof instanceOrChannelName === 'string' ? new BroadcastChannel(instanceOrChannelName) : instanceOrChannelName;
|
|
7
|
+
super(parentNode, port, params);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { _isObject } from '@webqit/util/js/index.js';
|
|
2
|
+
import { WebfloMessagingAPI } from './WebfloMessagingAPI.js';
|
|
3
|
+
import { WebfloMessageEvent } from './WebfloMessageEvent.js';
|
|
4
|
+
|
|
5
|
+
export class MessagingOverChannel extends WebfloMessagingAPI {
|
|
6
|
+
|
|
7
|
+
#port;
|
|
8
|
+
get port() { return this.#port; }
|
|
9
|
+
|
|
10
|
+
#isPrimary;
|
|
11
|
+
get isPrimary() { return this.#isPrimary; }
|
|
12
|
+
|
|
13
|
+
constructor(parentNode, port, { isPrimary = false, ...params } = {}) {
|
|
14
|
+
super(parentNode, params);
|
|
15
|
+
this.#port = port;
|
|
16
|
+
this.#isPrimary = isPrimary;
|
|
17
|
+
this.#port.start?.();
|
|
18
|
+
const messageHandler = async (event) => {
|
|
19
|
+
if (this.isPrimary && event.data === 'connection') {
|
|
20
|
+
this.$emit('connected');
|
|
21
|
+
}
|
|
22
|
+
if (event.data === 'close') {
|
|
23
|
+
// Endpoint 2 is closed
|
|
24
|
+
this.#port.removeEventListener('message', messageHandler);
|
|
25
|
+
this.dispatchEvent(new Event('close'));
|
|
26
|
+
this.$destroy();
|
|
27
|
+
}
|
|
28
|
+
if (!_isObject(event.data) || !['messageType', 'message'].every((k) => k in event.data)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.dispatchEvent(new ChannelMessageEvent(
|
|
32
|
+
this,
|
|
33
|
+
event.data.messageType,
|
|
34
|
+
event.data.message,
|
|
35
|
+
event.ports
|
|
36
|
+
));
|
|
37
|
+
};
|
|
38
|
+
this.#port.addEventListener('message', messageHandler);
|
|
39
|
+
const nativeCloseMethod = this.#port.close;
|
|
40
|
+
Object.defineProperty(this.#port, 'close', { value: () => {
|
|
41
|
+
// This endpoint is closed
|
|
42
|
+
this.#port.removeEventListener('message', messageHandler);
|
|
43
|
+
this.dispatchEvent(new Event('close'));
|
|
44
|
+
this.$destroy();
|
|
45
|
+
// Then post to the other end
|
|
46
|
+
this.#port.postMessage('close');
|
|
47
|
+
// Then restore nativeCloseMethod and execute normally
|
|
48
|
+
Object.defineProperty(this.#port, 'close', { value: nativeCloseMethod, configurable: true });
|
|
49
|
+
this.#port.close();
|
|
50
|
+
}, configurable: true });
|
|
51
|
+
if (!this.isPrimary) {
|
|
52
|
+
// We are client
|
|
53
|
+
this.$emit('connected');
|
|
54
|
+
this.#port.postMessage('connection');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
postMessage(message, transferOrOptions = []) {
|
|
59
|
+
this.on('connected', () => {
|
|
60
|
+
if (Array.isArray(transferOrOptions)) {
|
|
61
|
+
transferOrOptions = { transfer: transferOrOptions };
|
|
62
|
+
}
|
|
63
|
+
const { messageType = 'message', ...options } = transferOrOptions;
|
|
64
|
+
return this.#port.postMessage({
|
|
65
|
+
messageType,
|
|
66
|
+
message
|
|
67
|
+
}, options);
|
|
68
|
+
});
|
|
69
|
+
super.postMessage(message, transferOrOptions);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fire(messageType, message) {
|
|
73
|
+
this.dispatchEvent(new ChannelMessageEvent(
|
|
74
|
+
this,
|
|
75
|
+
messageType,
|
|
76
|
+
message
|
|
77
|
+
));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
close() {
|
|
81
|
+
return this.#port.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class ChannelMessageEvent extends WebfloMessageEvent {}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { WebfloMessageEvent } from './WebfloMessageEvent.js';
|
|
2
|
+
import { WebfloMessagingAPI } from './WebfloMessagingAPI.js';
|
|
3
|
+
|
|
4
|
+
export class MessagingOverSocket extends WebfloMessagingAPI {
|
|
5
|
+
|
|
6
|
+
#socket;
|
|
7
|
+
get socket() { return this.#socket; }
|
|
8
|
+
|
|
9
|
+
constructor(parentNode, instanceOrConnectionID, params = {}) {
|
|
10
|
+
super(parentNode, params);
|
|
11
|
+
this.#socket = typeof instanceOrConnectionID === 'string' ? new WebSocket(`/${instanceOrConnectionID}`) : instanceOrConnectionID;
|
|
12
|
+
const messageHandler = async (event) => {
|
|
13
|
+
let json;
|
|
14
|
+
try {
|
|
15
|
+
if (!(json = JSON.parse(event.data))
|
|
16
|
+
|| !['messageType', 'message', 'messageID'].every((k) => k in json)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
} catch(e) {
|
|
20
|
+
// throw a better error
|
|
21
|
+
}
|
|
22
|
+
this.dispatchEvent(new SocketMessageEvent(
|
|
23
|
+
this,
|
|
24
|
+
json.messageType,
|
|
25
|
+
json.message,
|
|
26
|
+
json.messageID,
|
|
27
|
+
json.numPorts
|
|
28
|
+
));
|
|
29
|
+
};
|
|
30
|
+
const openHandler = (e) => {
|
|
31
|
+
this.$emit('connected');
|
|
32
|
+
this.dispatchEvent(new Event('open'));
|
|
33
|
+
};
|
|
34
|
+
const errorHandler = (e) => {
|
|
35
|
+
this.dispatchEvent(new Event('error'));
|
|
36
|
+
};
|
|
37
|
+
const closeHandler = (e) => {
|
|
38
|
+
this.#socket.removeEventListener('message', messageHandler);
|
|
39
|
+
this.#socket.removeEventListener('open', openHandler);
|
|
40
|
+
this.#socket.removeEventListener('error', errorHandler);
|
|
41
|
+
this.#socket.removeEventListener('close', closeHandler);
|
|
42
|
+
this.dispatchEvent(new Event('close'));
|
|
43
|
+
this.$destroy();
|
|
44
|
+
};
|
|
45
|
+
this.#socket.addEventListener('message', messageHandler);
|
|
46
|
+
this.#socket.addEventListener('open', openHandler);
|
|
47
|
+
this.#socket.addEventListener('error', errorHandler);
|
|
48
|
+
this.#socket.addEventListener('close', closeHandler);
|
|
49
|
+
if (this.#socket.readyState === this.#socket.constructor.OPEN) {
|
|
50
|
+
this.$emit('connected');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
postMessage(message, transferOrOptions = []) {
|
|
55
|
+
this.on('connected', () => {
|
|
56
|
+
if (Array.isArray(transferOrOptions)) {
|
|
57
|
+
transferOrOptions = { transfer: transferOrOptions };
|
|
58
|
+
}
|
|
59
|
+
const { transfer = [], messageType = 'message', ...options } = transferOrOptions;
|
|
60
|
+
const messagePorts = transfer.filter((t) => t instanceof MessagePort);
|
|
61
|
+
const messageID = (0 | Math.random() * 9e6).toString(36);
|
|
62
|
+
this.#socket.send(JSON.stringify({
|
|
63
|
+
messageType,
|
|
64
|
+
message,
|
|
65
|
+
messageID,
|
|
66
|
+
numPorts: messagePorts.length
|
|
67
|
+
}), options);
|
|
68
|
+
for (let i = 0; i < messagePorts.length; i ++) {
|
|
69
|
+
this.addEventListener(`${messageType}:${messageID}:${i}`, (event) => {
|
|
70
|
+
messagePorts[i].postMessage(event.data, event.ports);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
super.postMessage(message, transferOrOptions);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fire(messageType, message) {
|
|
78
|
+
this.dispatchEvent(new SocketMessageEvent(
|
|
79
|
+
this,
|
|
80
|
+
messageType,
|
|
81
|
+
message
|
|
82
|
+
));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
close(...args) {
|
|
86
|
+
return this.#socket.close(...args);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class SocketMessageEvent extends WebfloMessageEvent {
|
|
91
|
+
constructor(originalTarget, messageType, message, messageID = null, numPorts = 0) {
|
|
92
|
+
const ports = [];
|
|
93
|
+
for (let i = 0; i < numPorts; i ++) {
|
|
94
|
+
const channel = new MessageChannel;
|
|
95
|
+
channel.port1.addEventListener('message', (event) => {
|
|
96
|
+
this.originalTarget.postMessage(event.data, {
|
|
97
|
+
messageType: `${messageType}:${messageID}:${i}`,
|
|
98
|
+
transfer: event.ports
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
channel.port1.start();
|
|
102
|
+
ports.push(channel.port2);
|
|
103
|
+
}
|
|
104
|
+
super(originalTarget, messageType, message, ports);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { WebfloMessagingAPI } from './WebfloMessagingAPI.js';
|
|
2
|
+
|
|
3
|
+
export class MultiportMessagingAPI extends WebfloMessagingAPI {
|
|
4
|
+
|
|
5
|
+
get runtime() { return this.params.runtime || this.parentNode; }
|
|
6
|
+
|
|
7
|
+
#ports = new Set;
|
|
8
|
+
get ports() { return this.#ports; }
|
|
9
|
+
|
|
10
|
+
[ Symbol.iterator ]() { return this.#ports[ Symbol.iterator ](); }
|
|
11
|
+
|
|
12
|
+
add(port) {
|
|
13
|
+
if (!(port instanceof WebfloMessagingAPI)) {
|
|
14
|
+
throw new TypeError('Argument must be a Webflo messaging interface');
|
|
15
|
+
}
|
|
16
|
+
port.setParent(this);
|
|
17
|
+
this.#ports.add(port);
|
|
18
|
+
this.$emit('add', port);
|
|
19
|
+
if (!this.isConnected()) {
|
|
20
|
+
this.$emit('connected');
|
|
21
|
+
}
|
|
22
|
+
port.addEventListener('close', () => {
|
|
23
|
+
this.remove(port);
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
remove(port) {
|
|
28
|
+
if (port.parentNode === this) {
|
|
29
|
+
port.setParent(null);
|
|
30
|
+
}
|
|
31
|
+
this.#ports.delete(port);
|
|
32
|
+
this.$emit('remove', port);
|
|
33
|
+
if (!this.#isReplaceAction && this.#ports.size === 0) {
|
|
34
|
+
this.$emit('empty');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#isReplaceAction;
|
|
39
|
+
replace(port) {
|
|
40
|
+
this.#isReplaceAction = true;
|
|
41
|
+
for (const port of this.#ports) {
|
|
42
|
+
this.remove(port);
|
|
43
|
+
}
|
|
44
|
+
this.#isReplaceAction = false;
|
|
45
|
+
this.add(port);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get(index, callback = null) {
|
|
49
|
+
const _leadMax = this.#ports.size - 1;
|
|
50
|
+
if (index > _leadMax && callback) {
|
|
51
|
+
return this.on('add', () => this.get(index, callback), { once: true });
|
|
52
|
+
}
|
|
53
|
+
const port = [...this.#ports][index];
|
|
54
|
+
if (callback) {
|
|
55
|
+
callback(port);
|
|
56
|
+
} else return port;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ----------------- */
|
|
60
|
+
|
|
61
|
+
postMessage(message, transferOrOptions = []) {
|
|
62
|
+
this.on('connected', () => {
|
|
63
|
+
for (const port of this.#ports) {
|
|
64
|
+
port.postMessage(message, transferOrOptions);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
super.postMessage(message, transferOrOptions);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
close(...args) {
|
|
71
|
+
for (const port of this.#ports) {
|
|
72
|
+
port.close?.(...args);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ----------------- */
|
|
77
|
+
|
|
78
|
+
createBroadcastChannel(name) {
|
|
79
|
+
return new BroadcastChannel(name);
|
|
80
|
+
}
|
|
81
|
+
}
|