@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.
Files changed (66) hide show
  1. package/package.json +1 -1
  2. package/src/{Context.js → AbstractContext.js} +1 -9
  3. package/src/deployment-pi/origins/index.js +1 -1
  4. package/src/index.js +1 -9
  5. package/src/runtime-pi/HttpEvent.js +101 -81
  6. package/src/runtime-pi/HttpUser.js +126 -0
  7. package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
  8. package/src/runtime-pi/MessagingOverChannel.js +85 -0
  9. package/src/runtime-pi/MessagingOverSocket.js +106 -0
  10. package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
  11. package/src/runtime-pi/WebfloCookieStorage.js +27 -0
  12. package/src/runtime-pi/WebfloEventTarget.js +39 -0
  13. package/src/runtime-pi/WebfloMessageEvent.js +58 -0
  14. package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
  15. package/src/runtime-pi/{Router.js → WebfloRouter.js} +3 -34
  16. package/src/runtime-pi/WebfloRuntime.js +52 -0
  17. package/src/runtime-pi/WebfloStorage.js +109 -0
  18. package/src/runtime-pi/client/ClientMessaging.js +5 -0
  19. package/src/runtime-pi/client/Context.js +2 -6
  20. package/src/runtime-pi/client/CookieStorage.js +17 -0
  21. package/src/runtime-pi/client/Router.js +3 -13
  22. package/src/runtime-pi/client/SessionStorage.js +33 -0
  23. package/src/runtime-pi/client/Url.js +24 -72
  24. package/src/runtime-pi/client/WebfloClient.js +544 -0
  25. package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
  26. package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
  27. package/src/runtime-pi/client/WebfloSubClient.js +165 -0
  28. package/src/runtime-pi/client/Workport.js +89 -161
  29. package/src/runtime-pi/client/generate.js +3 -3
  30. package/src/runtime-pi/client/index.js +13 -18
  31. package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
  32. package/src/runtime-pi/client/worker/Context.js +2 -6
  33. package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
  34. package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
  35. package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
  36. package/src/runtime-pi/client/worker/Workport.js +13 -73
  37. package/src/runtime-pi/client/worker/index.js +7 -18
  38. package/src/runtime-pi/index.js +1 -8
  39. package/src/runtime-pi/server/ClientMessaging.js +18 -0
  40. package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
  41. package/src/runtime-pi/server/Context.js +2 -6
  42. package/src/runtime-pi/server/CookieStorage.js +17 -0
  43. package/src/runtime-pi/server/Router.js +2 -68
  44. package/src/runtime-pi/server/SessionStorage.js +53 -0
  45. package/src/runtime-pi/server/WebfloServer.js +755 -0
  46. package/src/runtime-pi/server/index.js +7 -18
  47. package/src/runtime-pi/util-http.js +268 -32
  48. package/src/runtime-pi/xURL.js +25 -22
  49. package/src/runtime-pi/xfetch.js +2 -2
  50. package/src/runtime-pi/Application.js +0 -29
  51. package/src/runtime-pi/Cookies.js +0 -82
  52. package/src/runtime-pi/Runtime.js +0 -21
  53. package/src/runtime-pi/client/Application.js +0 -76
  54. package/src/runtime-pi/client/Runtime.js +0 -525
  55. package/src/runtime-pi/client/createStorage.js +0 -58
  56. package/src/runtime-pi/client/worker/Application.js +0 -44
  57. package/src/runtime-pi/client/worker/Runtime.js +0 -275
  58. package/src/runtime-pi/server/Application.js +0 -101
  59. package/src/runtime-pi/server/Runtime.js +0 -558
  60. package/src/runtime-pi/xFormData.js +0 -24
  61. package/src/runtime-pi/xHeaders.js +0 -146
  62. package/src/runtime-pi/xRequest.js +0 -46
  63. package/src/runtime-pi/xRequestHeaders.js +0 -109
  64. package/src/runtime-pi/xResponse.js +0 -33
  65. package/src/runtime-pi/xResponseHeaders.js +0 -117
  66. package/src/runtime-pi/xxHttpMessage.js +0 -102
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.11.61",
15
+ "version": "1.0.1",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -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 httpEvent.Response(`Deployment ${ exitCode === 0 ? 'success' : 'error: ' + exitCode }!`, { status: exitCode === 0 ? 200 : 500 })
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
- * The xHttpEvent Mixin
12
- */
13
- export default class HttpEvent {
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
- // url
45
- get url() {
46
- if (!this._url) {
47
- this._url = new this.URL(this._request.url);
48
- }
49
- return this._url;
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
- // request
53
- get request() {
54
- return this._request;
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
- // detail
58
- get detail() {
59
- return this._detail;
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
- // Session
63
- get session() {
64
- if (!this._session) {
65
- this._session = this.sessionFactory();
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
- // Storage
71
- get storage() {
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
- // Session factory
79
- sessionFactory(...args) {
80
- return this._sessionFactory(...args);
83
+ async defer() {
84
+ await this.#respondWith(new Response(null, { status: 202/*Accepted*/ }));
81
85
  }
82
-
83
- // storage factory
84
- storageFactory(...args) {
85
- return this._storageFactory(...args);
86
+
87
+ deferred() {
88
+ return this.#response?.status === 202;
86
89
  }
87
90
 
88
- // Redirect Response
89
- redirect(url, code = 302) {
90
- return new this.Response(null, { status: code, headers: { Location: url } });
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
- let request;
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) { [ /*url*/, init ] = await xRequest.rip(init); }
98
- request = !_isEmpty(init) ? new xRequest(url, init) : url;
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 this.URL(url, this.url.origin);
101
- [ /*url*/, init ] = await xRequest.rip(this._request);
102
- request = new xRequest(url, { ...init, referrer: this.request.url });
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(request, this.detail, this._sessionFactory, this.storageFactory);
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
+ }