@webqit/port-plus 0.1.0 → 0.1.5

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 CHANGED
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "homepage": "https://port-plus.netlify.app/",
16
16
  "icon": "https://webqit.io/icon.svg",
17
- "version": "0.1.0",
17
+ "version": "0.1.5",
18
18
  "license": "MIT",
19
19
  "repository": {
20
20
  "type": "git",
@@ -1,16 +1,11 @@
1
- import { MessagePortPlus, _meta } from './MessagePortPlus.js';
1
+ import { MessagePortPlus } from './MessagePortPlus.js';
2
2
 
3
3
  export class MessageChannelPlus extends MessageChannel {
4
4
 
5
5
  constructor({ autoStart = true, postAwaitsOpen = false } = {}) {
6
6
  super();
7
7
  [this.port1, this.port2].forEach((port, i) => {
8
- const portPlusMeta = _meta(port);
9
-
10
- portPlusMeta.set('options', { autoStart, postAwaitsOpen });
11
- // Must come before upgradeInPlace()
12
-
13
- MessagePortPlus.upgradeInPlace(port);
8
+ MessagePortPlus.upgradeInPlace(port, { autoStart, postAwaitsOpen });
14
9
  });
15
10
  }
16
11
  }
@@ -1,7 +1,7 @@
1
1
  import { _isTypeObject } from '@webqit/util/js/index.js';
2
- import { MessagePortPlus, applyMutations } from './MessagePortPlus.js';
2
+ import { MessagePortPlus, applyMutations, _options } from './MessagePortPlus.js';
3
3
 
4
- export class MessageEventPlus extends Event {
4
+ export class MessageEventPlus extends MessageEvent {
5
5
 
6
6
  #originalTarget;
7
7
  get originalTarget() { return this.#originalTarget; }
@@ -50,7 +50,8 @@ export class MessageEventPlus extends Event {
50
50
  this.#honourDoneMutationFlags = honourDoneMutationFlags;
51
51
 
52
52
  this.#ports = ports;
53
- this.#ports.forEach((port) => MessagePortPlus.upgradeInPlace(port));
53
+ const options = this.#originalTarget && _options(this.#originalTarget) || {};
54
+ this.#ports.forEach((port) => MessagePortPlus.upgradeInPlace(port, { autoStart: options.autoStart, postAwaitsOpen: options.postAwaitsOpen }));
54
55
 
55
56
  if (_isTypeObject(this.#data) && this.#live) {
56
57
  if (typeof eventID !== 'string') {
@@ -2,6 +2,7 @@ import { _wq as $wq } from '@webqit/util/js/index.js';
2
2
  import { _isObject, _isTypeObject } from '@webqit/util/js/index.js';
3
3
  import { MessageEventPlus } from './MessageEventPlus.js';
4
4
  import Observer from '@webqit/observer';
5
+ import { WebSocketPort } from './WebSocketPort.js';
5
6
 
6
7
  export const _wq = (target, ...args) => $wq(target, 'port+', ...args);
7
8
  export const _options = (target) => $wq(target, 'port+', 'meta').get('options') || {};
@@ -13,7 +14,7 @@ const portPlusMethods = [
13
14
  'postMessage',
14
15
  'postRequest',
15
16
  'dispatchEvent',
16
- 'forwardPort',
17
+ 'relay',
17
18
  'start',
18
19
  'readyStateChange',
19
20
  'removeEventListener',
@@ -46,7 +47,7 @@ export class MessagePortPlus extends MessagePortPlusMixin(EventTarget) {
46
47
  export function MessagePortPlusMixin(superClass) {
47
48
  return class extends superClass {
48
49
 
49
- static upgradeInPlace(port) {
50
+ static upgradeInPlace(port, options = {}) {
50
51
  if (port instanceof MessagePortPlus) {
51
52
  return port;
52
53
  }
@@ -69,6 +70,9 @@ export function MessagePortPlusMixin(superClass) {
69
70
  Object.defineProperty(port, prop, { ...plus, configurable: true });
70
71
  }
71
72
 
73
+ const portPlusMeta = _wq(port, 'meta');
74
+ portPlusMeta.set('options', options);
75
+
72
76
  this.upgradeEvents(port);
73
77
  }
74
78
 
@@ -354,12 +358,15 @@ export function MessagePortPlusMixin(superClass) {
354
358
 
355
359
  // Format payload if not yet in the ['.wq'] format
356
360
  let _relayedFrom;
357
- if (!_isObject(message?.['.wq'])) {
358
- const { portOptions, wqOptions: { relayedFrom, ...wqOptions } } = preProcessPostMessage.call(this, message, transferOrOptions);
361
+ const { portOptions, wqOptions: { relayedFrom, ...wqOptions } } = preProcessPostMessage.call(this, message, transferOrOptions);
362
+ if (wqOptions.type !== 'message'
363
+ || wqOptions.live
364
+ || wqOptions.bubbles
365
+ || portOptions.transfer?.length && (this instanceof BroadcastChannel || this instanceof WebSocketPort)) {
359
366
  message = { message, ['.wq']: wqOptions };
360
- transferOrOptions = portOptions;
361
- _relayedFrom = relayedFrom;
362
367
  }
368
+ transferOrOptions = portOptions;
369
+ _relayedFrom = relayedFrom;
363
370
 
364
371
  // Exec
365
372
  const post = () => {
@@ -413,25 +420,45 @@ export function MessagePortPlusMixin(superClass) {
413
420
 
414
421
  // Forwarding
415
422
 
416
- forwardPort(eventTypes, targetPort, { resolveData = null, bidirectional = false, namespace1 = null, namespace2 = null } = {}) {
417
- if (!(this instanceof MessagePortPlus) || !(targetPort instanceof MessagePortPlus)) {
418
- throw new Error('Both ports must be instance of MessagePortPlus.');
423
+ relay({ to: targetPort, from: sourcePort, types = '*', channel = null, resolveMessage = null, bidirectional = false } = {}) {
424
+ if (targetPort && sourcePort) {
425
+ throw new Error('Only one of "to" and "from" may be specified');
426
+ }
427
+
428
+ if (sourcePort) {
429
+ targetPort = this;
430
+ } else sourcePort = this;
431
+
432
+ if (targetPort === sourcePort) {
433
+ throw new Error('Source and target ports cannot be the same');
419
434
  }
420
- if (typeof eventTypes !== 'function' && !(eventTypes = [].concat(eventTypes)).length) {
435
+
436
+ if (!(sourcePort instanceof MessagePortPlus) || !(targetPort instanceof MessagePortPlus)) {
437
+ throw new Error('Both source and target ports must be instance of MessagePortPlus.');
438
+ }
439
+
440
+ if (typeof types !== 'function' && !(types = [].concat(types)).length) {
421
441
  throw new Error('Event types must be specified.');
422
442
  }
423
443
 
424
- const downstreamRegistry = getDownstreamRegistry.call(this);
425
- const registration = { targetPort, eventTypes, options: { resolveData, namespace1, namespace2 } };
444
+ if (typeof channel === 'string') {
445
+ channel = { from: channel };
446
+ } else if (_isObject(channel)) {
447
+ if (Object.keys(channel).filter((k) => !['from', 'to'].includes(k)).length) {
448
+ throw new Error('Channel must be a string or an object of "from"/"to" members');
449
+ }
450
+ } else if (channel) {
451
+ throw new Error('Invalid channel parameter');
452
+ }
453
+
454
+ const downstreamRegistry = getDownstreamRegistry.call(sourcePort);
455
+ const registration = { targetPort, types, channel, resolveMessage };
426
456
  downstreamRegistry.add(registration);
427
457
 
428
458
  let cleanup2;
429
459
  if (bidirectional) {
430
- cleanup2 = this.forwardPort.call(
431
- targetPort,
432
- typeof eventTypes === 'function' ? eventTypes : eventTypes.filter((s) => s !== 'close'),
433
- this,
434
- { resolveData, bidirectional: false, namespace1: namespace2, namespace2: namespace1 }
460
+ cleanup2 = targetPort.relay(
461
+ { to: sourcePort, types, channel: channel && { to: channel.from, from: channel.to }, resolveMessage, bidirectional: false }
435
462
  );
436
463
  }
437
464
 
@@ -441,6 +468,40 @@ export function MessagePortPlusMixin(superClass) {
441
468
  };
442
469
  }
443
470
 
471
+ channel(channelSpec, resolveMessage = null) {
472
+ const channel = new MessageChannel;
473
+
474
+ MessagePortPlus.upgradeInPlace(channel.port1, { autoStart: this.options.autoStart, postAwaitsOpen: this.options.postAwaitsOpen });
475
+ MessagePortPlus.upgradeInPlace(channel.port2, { autoStart: this.options.autoStart, postAwaitsOpen: this.options.postAwaitsOpen });
476
+
477
+ const garbageCollection = getGarbageCollection.call(this);
478
+ garbageCollection.add(this.relay({ channel: channelSpec, to: channel.port1, bidirectional: true, resolveMessage }));
479
+
480
+ channel.port1.start();
481
+ this.readyStateChange('close').then(() => {
482
+ channel.port1.close();
483
+ });
484
+
485
+ return channel.port2;
486
+ }
487
+
488
+ projectMutations({ from, to, ...options }) {
489
+ if (!from || !to
490
+ || typeof from === 'string' && typeof to === 'string'
491
+ || _isTypeObject(from) && _isTypeObject(to)) {
492
+ throw new Error('Invalid "from"/"to" parameters');
493
+ }
494
+ if (typeof from === 'string') {
495
+ if (!_isTypeObject(to)) throw new Error('Invalid "to" parameter. Object/array expected');
496
+ return applyMutations.call(this, to, from, options);
497
+ }
498
+ if (typeof to === 'string') {
499
+ if (!_isTypeObject(from)) throw new Error('Invalid "from" parameter. Object/array expected');
500
+ return publishMutations.call(this, from, to, options);
501
+ }
502
+ throw new Error('Invalid "from"/"to" parameters');
503
+ }
504
+
444
505
  // Lifecycle
445
506
 
446
507
  get readyState() {
@@ -571,17 +632,8 @@ export function MessagePortPlusMockPortsMixin(superClass) {
571
632
  Object.defineProperty(event, 'ports', { value: [], configurable: true });
572
633
 
573
634
  for (let i = 0; i < numPorts; i++) {
574
- const channel = new MessageChannel;
575
- channel.port1.start();
576
-
577
- MessagePortPlus.upgradeInPlace(channel.port1);
578
- garbageCollection.add(portPlus.forwardPort('*', channel.port1, { bidirectional: true, namespace1: `${event.data['.wq'].eventID}:${i}` }));
579
- event.ports.push(channel.port2);
580
-
581
- portPlus.readyStateChange('close').then(() => {
582
- channel.port1.close();
583
- channel.port2.close();
584
- });
635
+ const port = portPlus.channel(`${event.data['.wq'].eventID}:${i}`);
636
+ event.ports.push(port);
585
637
  }
586
638
 
587
639
  return event;
@@ -598,7 +650,16 @@ export function MessagePortPlusMockPortsMixin(superClass) {
598
650
 
599
651
  for (let i = 0; i < numPorts; i++) {
600
652
  MessagePortPlus.upgradeInPlace(messagePorts[i]);
601
- garbageCollection.add(this.forwardPort('*', messagePorts[i], { bidirectional: true, namespace1: `${payload['.wq'].eventID}:${i}` }));
653
+
654
+ garbageCollection.add(this.relay({ channel: `${payload['.wq'].eventID}:${i}`, to: messagePorts[i], bidirectional: true }));
655
+
656
+ messagePorts[i].start();
657
+ messagePorts[i].readyStateChange('close').then(() => {
658
+ // Send a disconnect ping to port2
659
+ const { wqOptions } = preProcessPostMessage.call(this);
660
+ const pingData = { ['.wq']: wqOptions, ping: 'disconnect' };
661
+ this.postMessage(pingData, { type: `${payload['.wq'].eventID}:${i}:message` });
662
+ });
602
663
  }
603
664
 
604
665
  payload['.wq'].numPorts = numPorts; // IMPORTANT: numPorts must be set before ports are added
@@ -629,27 +690,27 @@ export function propagateEvent(event) {
629
690
  const { type, eventID, data, live, bubbles, ports } = event;
630
691
 
631
692
  const called = new WeakSet;
632
- for (const { targetPort, eventTypes, options } of downstreamRegistry) {
693
+ for (const { targetPort, types, channel, resolveMessage } of downstreamRegistry) {
633
694
  if (called.has(targetPort)) continue;
634
695
 
635
696
  let $type = type;
636
- if (options.namespace1) {
637
- [, $type] = (new RegExp(`^${options.namespace1.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}:([^:]+)$`)).exec(type) || [];
697
+ if (channel.from) {
698
+ [, $type] = (new RegExp(`^${channel.from.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}:([^:]+)$`)).exec(type) || [];
638
699
  if (!$type) continue;
639
700
  }
640
701
 
641
- const matches = typeof eventTypes === 'function'
642
- ? eventTypes($type, this, targetPort, options)
643
- : [].concat(eventTypes).find((t) => {
702
+ const matches = typeof types === 'function'
703
+ ? types($type, this, targetPort, channel)
704
+ : [].concat(types).find((t) => {
644
705
  return t === $type || t === '*';
645
706
  });
646
707
 
647
708
  if (!matches) continue;
648
709
  called.add(targetPort);
649
710
 
650
- targetPort.postMessage(options.resolveData ? options.resolveData(data, this, targetPort, options) : data, {
711
+ targetPort.postMessage(resolveMessage ? resolveMessage(data, this, targetPort, channel) : data, {
651
712
  transfer: ports,
652
- type: options.namespace2 ? `${options.namespace2}:${$type}` : $type,
713
+ type: channel.to ? `${channel.to}:${$type}` : $type,
653
714
  eventID,
654
715
  bubbles,
655
716
  live,
@@ -682,7 +743,7 @@ export function getReadyStateInternals() {
682
743
  const portPlusMeta = _meta(this);
683
744
  if (!portPlusMeta.has('readystate_registry')) {
684
745
  const $ref = (o) => {
685
- o.promise = new Promise((resolve) => o.resolve = resolve);
746
+ o.promise = new Promise((res, rej) => (o.resolve = res, o.reject = rej));
686
747
  return o;
687
748
  };
688
749
  portPlusMeta.set('readystate_registry', {
package/src/RelayPort.js CHANGED
@@ -1,39 +1,49 @@
1
+ import { _isObject } from '@webqit/util/js/index.js';
1
2
  import { StarPort } from './StarPort.js';
2
3
 
3
4
  export class RelayPort extends StarPort {
4
5
 
5
- #namespace;
6
- get namespace() { return this.#namespace; }
6
+ #channelSpec;
7
+ get channelSpec() { return this.#channelSpec; }
7
8
 
8
- constructor(namespace = null) {
9
+ constructor(channel = null) {
9
10
  super();
10
- this.#namespace = namespace;
11
+ if (typeof channel === 'string') {
12
+ channel = { from: channel, to: channel };
13
+ } else if (_isObject(channel)) {
14
+ if (Object.keys(channel).filter((k) => !['from', 'to'].includes(k)).length) {
15
+ throw new Error('Channel must be a string or an object of "from"/"to" members');
16
+ }
17
+ } else if (channel) {
18
+ throw new Error('Invalid channel parameter');
19
+ }
20
+ this.#channelSpec = channel;
11
21
  }
12
22
 
13
- addPort(portPlus, { resolveData = null } = {}) {
14
- const $resolveData = (data, ...args) => {
15
- if (resolveData) return resolveData(data, ...args);
23
+ addPort(portPlus, { resolveMessage = null } = {}) {
24
+ const $resolveMessage = (data, ...args) => {
25
+ if (resolveMessage) return resolveMessage(data, ...args);
16
26
  return data;
17
27
  };
18
28
 
19
29
  // Add port and forward its messages back to this relay instance
20
30
  const unadd = super.addPort(portPlus, { enableBubbling: false });
21
- const unforward = portPlus.forwardPort('*', this, { bidirectional: false, resolveData: $resolveData, namespace1: this.namespace, namespace2: this.namespace });
31
+ const unforward = portPlus.relay({ channel: this.#channelSpec, to: this, bidirectional: false/* IMPORTANT */, resolveMessage: $resolveMessage });
22
32
 
23
- const messageType_ping = this.namespace
24
- && `${this.namespace}:message`
33
+ const messageType_ping = this.#channelSpec.from
34
+ && `${this.#channelSpec.from}:message`
25
35
  || 'message';
26
36
 
27
37
  // PING: joins
28
38
  this.postMessage(
29
- $resolveData({ event: 'joins' }, portPlus, this),
39
+ $resolveMessage({ event: 'joins' }, portPlus, this),
30
40
  { type: messageType_ping, relayedFrom: portPlus }
31
41
  );
32
42
 
33
43
  const leaves = () => {
34
44
  // PING: leaves
35
45
  this.postMessage(
36
- $resolveData({ event: 'leaves' }, portPlus, this),
46
+ $resolveMessage({ event: 'leaves' }, portPlus, this),
37
47
  { type: messageType_ping, relayedFrom: portPlus }
38
48
  );
39
49
  };
@@ -1,2 +1,4 @@
1
1
  import * as PortPlus from './index.js';
2
- Object.assign(globalThis, PortPlus);
2
+
3
+ if (!globalThis.webqit) globalThis.webqit = {}
4
+ Object.assign(globalThis.webqit, PortPlus);
package/src/index.js CHANGED
@@ -4,4 +4,5 @@ export { MessageEventPlus } from './MessageEventPlus.js';
4
4
  export { MessagePortPlus } from './MessagePortPlus.js';
5
5
  export { RelayPort } from './RelayPort.js';
6
6
  export { StarPort } from './StarPort.js';
7
- export { WebSocketPort } from './WebSocketPort.js';
7
+ export { WebSocketPort } from './WebSocketPort.js';
8
+ export { default as Observer } from '@webqit/observer';
@@ -1,8 +1,8 @@
1
- import { MessagePortPlus, MessageChannelPlus, WebSocketPort, BroadcastChannelPlus } from '../src/index.js';
1
+ import { MessagePortPlus, MessageChannelPlus, BroadcastChannelPlus, Observer } from '../src/index.js';
2
2
 
3
3
  const t = new MessagePortPlus;
4
4
 
5
- let m, q = 3;
5
+ let m, q = 4;
6
6
 
7
7
  if (q === 1) {
8
8
  m = new MessageChannel;
@@ -30,9 +30,26 @@ m.port1.addEventListener('close', () => console.log('close1'));
30
30
  m.port2.addEventListener('open', () => console.log('open2'));
31
31
  m.port2.addEventListener('close', () => console.log('close2'));
32
32
 
33
- m.port1.addEventListener('message', (e) => console.log('message', e.data));
34
- m.port2.postMessage('hello');
33
+ m.port1.addEventListener('message', (e) => {
34
+ console.log('message', e.data);
35
+ console.log('numPorts', e.ports.length);
36
+ e.ports[0]?.addEventListener('message', (e) => {
37
+ console.log('Port message', e.data, 'isLive', e.live, );
38
+ Observer.observe(e.data, (mm) => {
39
+ console.log('-------mutations', e.data);
40
+ });
41
+ });
42
+ e.ports[0]?.addEventListener('close', (e) => console.log('Port close'));
43
+ });
44
+
45
+ const p = new MessageChannelPlus;
46
+ m.port2.postMessage('hello', [p.port1]);
47
+ const pMessage = { greeting: 'Hi' };
48
+ p.port2.postMessage(pMessage, { live: true });
49
+ Observer.set(pMessage, '_K', '_V');
50
+
51
+ await new Promise((r) => setTimeout(r, 3000));
52
+ p.port2.close();
35
53
 
36
54
  await new Promise((r) => setTimeout(r, 3000));
37
- //m.port1.close();
38
55
  m.port2.close();