@webqit/webflo 0.11.61-0 → 1.0.0

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 (118) hide show
  1. package/.gitignore +7 -7
  2. package/LICENSE +20 -20
  3. package/README.md +2079 -2074
  4. package/docker/Dockerfile +42 -42
  5. package/docker/README.md +91 -91
  6. package/docker/package.json +2 -2
  7. package/package.json +80 -81
  8. package/src/{Context.js → AbstractContext.js} +71 -79
  9. package/src/config-pi/deployment/Env.js +68 -68
  10. package/src/config-pi/deployment/Layout.js +63 -63
  11. package/src/config-pi/deployment/Origins.js +139 -139
  12. package/src/config-pi/deployment/Proxy.js +74 -74
  13. package/src/config-pi/deployment/index.js +17 -17
  14. package/src/config-pi/index.js +15 -15
  15. package/src/config-pi/runtime/Client.js +116 -98
  16. package/src/config-pi/runtime/Server.js +125 -125
  17. package/src/config-pi/runtime/client/Worker.js +109 -134
  18. package/src/config-pi/runtime/client/index.js +11 -11
  19. package/src/config-pi/runtime/index.js +17 -17
  20. package/src/config-pi/runtime/server/Headers.js +74 -74
  21. package/src/config-pi/runtime/server/Redirects.js +69 -69
  22. package/src/config-pi/runtime/server/index.js +13 -13
  23. package/src/config-pi/static/Manifest.js +319 -319
  24. package/src/config-pi/static/Ssg.js +49 -49
  25. package/src/config-pi/static/index.js +13 -13
  26. package/src/deployment-pi/index.js +10 -10
  27. package/src/deployment-pi/origins/index.js +216 -216
  28. package/src/index.js +11 -19
  29. package/src/runtime-pi/HttpEvent.js +126 -106
  30. package/src/runtime-pi/HttpUser.js +126 -0
  31. package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
  32. package/src/runtime-pi/MessagingOverChannel.js +85 -0
  33. package/src/runtime-pi/MessagingOverSocket.js +106 -0
  34. package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
  35. package/src/runtime-pi/WebfloCookieStorage.js +27 -0
  36. package/src/runtime-pi/WebfloEventTarget.js +39 -0
  37. package/src/runtime-pi/WebfloMessageEvent.js +58 -0
  38. package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
  39. package/src/runtime-pi/{Router.js → WebfloRouter.js} +99 -130
  40. package/src/runtime-pi/WebfloRuntime.js +52 -0
  41. package/src/runtime-pi/WebfloStorage.js +109 -0
  42. package/src/runtime-pi/client/ClientMessaging.js +5 -0
  43. package/src/runtime-pi/client/Context.js +3 -7
  44. package/src/runtime-pi/client/CookieStorage.js +17 -0
  45. package/src/runtime-pi/client/Router.js +38 -48
  46. package/src/runtime-pi/client/SessionStorage.js +33 -0
  47. package/src/runtime-pi/client/Url.js +156 -205
  48. package/src/runtime-pi/client/WebfloClient.js +544 -0
  49. package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
  50. package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
  51. package/src/runtime-pi/client/WebfloSubClient.js +165 -0
  52. package/src/runtime-pi/client/Workport.js +118 -178
  53. package/src/runtime-pi/client/generate.js +480 -471
  54. package/src/runtime-pi/client/index.js +16 -21
  55. package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
  56. package/src/runtime-pi/client/worker/Context.js +3 -7
  57. package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
  58. package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
  59. package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
  60. package/src/runtime-pi/client/worker/Workport.js +17 -85
  61. package/src/runtime-pi/client/worker/index.js +10 -21
  62. package/src/runtime-pi/index.js +6 -13
  63. package/src/runtime-pi/server/ClientMessaging.js +18 -0
  64. package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
  65. package/src/runtime-pi/server/Context.js +11 -15
  66. package/src/runtime-pi/server/CookieStorage.js +17 -0
  67. package/src/runtime-pi/server/Router.js +93 -159
  68. package/src/runtime-pi/server/SessionStorage.js +53 -0
  69. package/src/runtime-pi/server/WebfloServer.js +755 -0
  70. package/src/runtime-pi/server/index.js +10 -21
  71. package/src/runtime-pi/util-http.js +322 -86
  72. package/src/runtime-pi/util-url.js +146 -146
  73. package/src/runtime-pi/xURL.js +108 -105
  74. package/src/runtime-pi/xfetch.js +22 -22
  75. package/src/services-pi/cert/http-auth-hook.js +22 -22
  76. package/src/services-pi/cert/http-cleanup-hook.js +22 -22
  77. package/src/services-pi/cert/index.js +79 -79
  78. package/src/services-pi/index.js +8 -8
  79. package/src/static-pi/index.js +10 -10
  80. package/src/webflo.js +30 -30
  81. package/test/index.test.js +26 -26
  82. package/test/site/package.json +9 -9
  83. package/test/site/public/bundle.html +5 -5
  84. package/test/site/public/bundle.html.json +3 -3
  85. package/test/site/public/bundle.js +2 -2
  86. package/test/site/public/bundle.webflo.js +15 -15
  87. package/test/site/public/index.html +29 -29
  88. package/test/site/public/index1.html +34 -34
  89. package/test/site/public/page-2/bundle.html +4 -4
  90. package/test/site/public/page-2/bundle.js +2 -2
  91. package/test/site/public/page-2/index.html +45 -45
  92. package/test/site/public/page-2/main.html +2 -2
  93. package/test/site/public/page-4/subpage/bundle.js +2 -2
  94. package/test/site/public/page-4/subpage/index.html +30 -30
  95. package/test/site/public/sparoots.json +4 -4
  96. package/test/site/public/worker.js +3 -3
  97. package/test/site/server/index.js +15 -15
  98. package/src/runtime-pi/Application.js +0 -29
  99. package/src/runtime-pi/Cookies.js +0 -82
  100. package/src/runtime-pi/Runtime.js +0 -21
  101. package/src/runtime-pi/client/Application.js +0 -100
  102. package/src/runtime-pi/client/Runtime.js +0 -332
  103. package/src/runtime-pi/client/createStorage.js +0 -57
  104. package/src/runtime-pi/client/oohtml/full.js +0 -7
  105. package/src/runtime-pi/client/oohtml/namespacing.js +0 -7
  106. package/src/runtime-pi/client/oohtml/scripting.js +0 -8
  107. package/src/runtime-pi/client/oohtml/templating.js +0 -8
  108. package/src/runtime-pi/client/worker/Application.js +0 -44
  109. package/src/runtime-pi/client/worker/Runtime.js +0 -269
  110. package/src/runtime-pi/server/Application.js +0 -116
  111. package/src/runtime-pi/server/Runtime.js +0 -557
  112. package/src/runtime-pi/xFormData.js +0 -24
  113. package/src/runtime-pi/xHeaders.js +0 -146
  114. package/src/runtime-pi/xRequest.js +0 -46
  115. package/src/runtime-pi/xRequestHeaders.js +0 -109
  116. package/src/runtime-pi/xResponse.js +0 -33
  117. package/src/runtime-pi/xResponseHeaders.js +0 -117
  118. package/src/runtime-pi/xxHttpMessage.js +0 -102
@@ -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";
8
- import xURL from "./xURL.js";
9
-
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
- }
42
- }
43
-
44
- // url
45
- get url() {
46
- if (!this._url) {
47
- this._url = new this.URL(this._request.url);
48
- }
49
- return this._url;
50
- }
51
-
52
- // request
53
- get request() {
54
- return this._request;
55
- }
56
-
57
- // detail
58
- get detail() {
59
- return this._detail;
60
- }
61
-
62
- // Session
63
- get session() {
64
- if (!this._session) {
65
- this._session = this.sessionFactory();
66
- }
67
- return this._session;
68
- }
69
-
70
- // Storage
71
- get storage() {
72
- if (!this._storage) {
73
- this._storage = this.storageFactory();
74
- }
75
- return this._storage;
76
- }
77
-
78
- // Session factory
79
- sessionFactory(...args) {
80
- return this._sessionFactory(...args);
81
- }
82
-
83
- // storage factory
84
- storageFactory(...args) {
85
- return this._storageFactory(...args);
86
- }
87
-
88
- // Redirect Response
89
- redirect(url, code = 302) {
90
- return new this.Response(null, { status: code, headers: { Location: url } });
91
- }
92
-
93
- // "with()"
94
- async with(url, init = {}) {
95
- let request;
96
- if (url instanceof Request) {
97
- if (init instanceof Request) { [ /*url*/, init ] = await xRequest.rip(init); }
98
- request = !_isEmpty(init) ? new xRequest(url, init) : url;
99
- } 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 });
103
- }
104
- return new HttpEvent(request, this.detail, this._sessionFactory, this.storageFactory);
105
- }
106
-
1
+ import { _isEmpty, _isObject } from '@webqit/util/js/index.js';
2
+ import xURL from "./xURL.js";
3
+
4
+ export class HttpEvent {
5
+
6
+ static create(parentEvent, init = {}) {
7
+ return new this(parentEvent, init);
8
+ }
9
+
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);
19
+ }
20
+
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;
37
+ }
38
+
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;
45
+ }
46
+
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);
76
+ }
77
+ }
78
+
79
+ async respondWith(response) {
80
+ await this.#respondWith(response);
81
+ }
82
+
83
+ async defer() {
84
+ await this.#respondWith(new Response(null, { status: 202/*Accepted*/ }));
85
+ }
86
+
87
+ deferred() {
88
+ return this.#response?.status === 202;
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);
110
+ }
111
+
112
+ async with(url, init = {}) {
113
+ if (!this.request) {
114
+ return new HttpEvent(this, { ...this.#init, url });
115
+ }
116
+ let request, _;
117
+ if (url instanceof Request) {
118
+ if (init instanceof Request) { ({ url: _, ...init } = await Request.copy(init)); }
119
+ request = !_isEmpty(init) ? new Request(url, init) : url;
120
+ } else {
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 });
124
+ }
125
+ return new HttpEvent(this, { ...this.#init, request });
126
+ }
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
+ }
@@ -0,0 +1,27 @@
1
+ import { _isObject } from '@webqit/util/js/index.js';
2
+ import { renderCookieObj } from './util-http.js';
3
+ import { WebfloStorage } from './WebfloStorage.js';
4
+
5
+ export class WebfloCookieStorage extends WebfloStorage {
6
+ constructor(request, iterable = []) {
7
+ iterable = [...iterable].map(([key, value]) => [key, !_isObject(value) ? { name: key, value } : value]);
8
+ super(request, null, iterable);
9
+ this.saveOriginals();
10
+ }
11
+
12
+ render() {
13
+ return this.getAdded().map((key) => renderCookieObj({ name: key, ...this.get(key, true) })).concat(
14
+ this.getDeleted().map((key) => renderCookieObj({ name: key, value: '', maxAge: 0 }))
15
+ );
16
+ }
17
+
18
+ set(key, value) {
19
+ if (!_isObject(value)) { value = { name: key, value }; }
20
+ return super.set(key, value);
21
+ }
22
+
23
+ get(key, withDetail = false) {
24
+ if (!withDetail) return super.get(key)?.value;
25
+ return super.get(key);
26
+ }
27
+ }