core-3nweb-client-lib 0.43.20 → 0.44.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 (42) hide show
  1. package/build/api-defs/files.d.ts +233 -160
  2. package/build/core/index.d.ts +1 -0
  3. package/build/core/index.js +3 -2
  4. package/build/core/storage/index.d.ts +1 -0
  5. package/build/core/storage/index.js +4 -1
  6. package/build/core/storage/synced/storage.d.ts +1 -0
  7. package/build/core/storage/synced/storage.js +4 -1
  8. package/build/core-ipc/file.d.ts +47 -0
  9. package/build/core-ipc/file.js +121 -2
  10. package/build/core-ipc/fs.js +55 -62
  11. package/build/lib-client/asmail/recipient.js +4 -2
  12. package/build/lib-client/cryptor/cryptor-wasm.js +1 -1
  13. package/build/lib-client/cryptor/cryptor.wasm +0 -0
  14. package/build/lib-client/fs-utils/files.js +5 -1
  15. package/build/lib-client/ws-utils.js +2 -7
  16. package/build/lib-client/xsp-fs/common.d.ts +2 -0
  17. package/build/lib-client/xsp-fs/common.js +2 -1
  18. package/build/lib-client/xsp-fs/exceptions.js +1 -1
  19. package/build/lib-client/xsp-fs/file-node.d.ts +3 -0
  20. package/build/lib-client/xsp-fs/file-node.js +55 -3
  21. package/build/lib-client/xsp-fs/file.d.ts +3 -0
  22. package/build/lib-client/xsp-fs/file.js +5 -1
  23. package/build/lib-client/xsp-fs/folder-node.d.ts +5 -4
  24. package/build/lib-client/xsp-fs/folder-node.js +257 -368
  25. package/build/lib-client/xsp-fs/fs.d.ts +6 -2
  26. package/build/lib-client/xsp-fs/fs.js +33 -2
  27. package/build/lib-client/xsp-fs/link-node.js +1 -1
  28. package/build/lib-client/xsp-fs/node-in-fs.d.ts +21 -0
  29. package/build/lib-client/xsp-fs/node-in-fs.js +172 -3
  30. package/build/lib-common/exceptions/file.d.ts +1 -1
  31. package/build/lib-common/exceptions/file.js +3 -2
  32. package/build/lib-common/ipc/generic-ipc.js +33 -28
  33. package/build/lib-common/ipc/ws-ipc.d.ts +2 -0
  34. package/build/lib-common/ipc/ws-ipc.js +35 -8
  35. package/build/lib-common/map-of-sets.d.ts +1 -0
  36. package/build/lib-common/map-of-sets.js +3 -0
  37. package/build/protos/asmail.proto.js +3315 -883
  38. package/build/protos/file.proto.js +1974 -0
  39. package/build/protos/fs.proto.js +3301 -869
  40. package/package.json +1 -1
  41. package/protos/file.proto +44 -0
  42. package/protos/fs.proto +42 -27
@@ -28,9 +28,11 @@ type SyncStatus = web3n.files.SyncStatus;
28
28
  type WritableFSVersionedAPI = web3n.files.WritableFSVersionedAPI;
29
29
  type OptionsToAdopteRemote = web3n.files.OptionsToAdopteRemote;
30
30
  type OptionsToAdoptRemoteItem = web3n.files.OptionsToAdoptRemoteItem;
31
- type OptionsToAdoptAllRemoteItems = web3n.files.OptionsToAdoptAllRemoteItems;
31
+ type OptionsToDiffFileVersions = web3n.files.OptionsToDiffFileVersions;
32
32
  type OptionsToUploadLocal = web3n.files.OptionsToUploadLocal;
33
33
  type FolderDiff = web3n.files.FolderDiff;
34
+ type FileDiff = web3n.files.FileDiff;
35
+ type OptionsToMergeFolderVersions = web3n.files.OptionsToMergeFolderVersions;
34
36
  export declare class XspFS implements WritableFS {
35
37
  readonly writable: boolean;
36
38
  name: string;
@@ -178,11 +180,13 @@ declare class S implements WritableFSSyncAPI {
178
180
  adoptRemote(path: string, opts?: OptionsToAdopteRemote): Promise<void>;
179
181
  private getFolderNode;
180
182
  diffCurrentAndRemoteFolderVersions(path: string, remoteVersion?: number): Promise<FolderDiff | undefined>;
183
+ private getFileNode;
184
+ diffCurrentAndRemoteFileVersions(path: string, opts?: OptionsToDiffFileVersions): Promise<FileDiff | undefined>;
181
185
  adoptRemoteFolderItem(path: string, itemName: string, opts?: OptionsToAdoptRemoteItem): Promise<number>;
182
186
  statRemoteItem(path: string, remoteItemName: string, remoteVersion?: number): Promise<Stats>;
183
187
  listRemoteFolderItem(path: string, remoteItemName: string, remoteVersion?: number): Promise<ListingEntry[]>;
184
188
  getRemoteFileItem(path: string, remoteItemName: string, remoteVersion?: number): Promise<ReadonlyFile>;
185
189
  getRemoteFolderItem(path: string, remoteItemName: string, remoteVersion?: number): Promise<ReadonlyFS>;
186
- adoptAllRemoteItems(path: string, opts?: OptionsToAdoptAllRemoteItems): Promise<number | undefined>;
190
+ mergeFolderCurrentAndRemoteVersions(path: string, opts?: OptionsToMergeFolderVersions): Promise<number | undefined>;
187
191
  }
188
192
  export {};
@@ -799,6 +799,22 @@ class S {
799
799
  throw (0, common_1.setPathInExc)(exc, path);
800
800
  }
801
801
  }
802
+ async getFileNode(path) {
803
+ const node = await this.n.get(path);
804
+ if (node.type !== 'file') {
805
+ throw (0, file_1.makeFileException)('notFile', path);
806
+ }
807
+ return node;
808
+ }
809
+ async diffCurrentAndRemoteFileVersions(path, opts) {
810
+ const node = await this.getFileNode(path);
811
+ try {
812
+ return await node.diffCurrentAndRemote(opts === null || opts === void 0 ? void 0 : opts.remoteVersion, !!(opts === null || opts === void 0 ? void 0 : opts.compareContentIfSameMTime));
813
+ }
814
+ catch (exc) {
815
+ throw (0, common_1.setPathInExc)(exc, path);
816
+ }
817
+ }
802
818
  async adoptRemoteFolderItem(path, itemName, opts) {
803
819
  const node = await this.getFolderNode(path);
804
820
  try {
@@ -843,9 +859,24 @@ class S {
843
859
  throw (0, file_1.makeFileException)('notDirectory', `${path}/${remoteItemName}`);
844
860
  }
845
861
  }
846
- async adoptAllRemoteItems(path, opts) {
862
+ // async adoptAllRemoteItems(
863
+ // path: string, opts?: OptionsToAdoptAllRemoteItems
864
+ // ): Promise<number|undefined> {
865
+ // const folderNode = await this.getFolderNode(path);
866
+ // return await folderNode.adoptItemsFromRemoteVersion(opts);
867
+ // }
868
+ async mergeFolderCurrentAndRemoteVersions(path, opts) {
847
869
  const folderNode = await this.getFolderNode(path);
848
- return await folderNode.adoptItemsFromRemoteVersion(opts);
870
+ const newLocalVersion = await folderNode.mergeCurrentAndRemoteVersions(opts);
871
+ if (newLocalVersion && (newLocalVersion < 0)) {
872
+ const { folderPath } = split(path);
873
+ if (folderPath.length > 0) {
874
+ const parent = await this.n.get(folderPath.join('/'));
875
+ // XXX removing folder in parent -- what happens with children?
876
+ await parent.removeChild(folderNode);
877
+ }
878
+ }
879
+ return newLocalVersion;
849
880
  }
850
881
  }
851
882
  Object.freeze(S.prototype);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2016 - 2018, 2020, 2022, 2025 3NSoft Inc.
3
+ Copyright (C) 2016 - 2018, 2020, 2022, 2025 - 2026 3NSoft Inc.
4
4
 
5
5
  This program is free software: you can redistribute it and/or modify it under
6
6
  the terms of the GNU General Public License as published by the Free Software
@@ -11,6 +11,8 @@ type OptionsToAdopteRemote = web3n.files.OptionsToAdopteRemote;
11
11
  type OptionsToUploadLocal = web3n.files.OptionsToUploadLocal;
12
12
  type VersionedReadFlags = web3n.files.VersionedReadFlags;
13
13
  type Stats = web3n.files.Stats;
14
+ type CommonDiff = web3n.files.CommonDiff;
15
+ type SyncVersionsBranch = web3n.files.SyncVersionsBranch;
14
16
  export declare abstract class NodeInFS<P extends NodePersistance> implements Node {
15
17
  protected readonly storage: Storage;
16
18
  readonly type: NodeType;
@@ -102,6 +104,25 @@ export declare abstract class NodeInFS<P extends NodePersistance> implements Nod
102
104
  uploadTaskId: number;
103
105
  }>;
104
106
  private makeHeaderForUploadIfVersionChanges;
107
+ private diffWithArchivedRemote;
108
+ protected commonDiffWithRemote(isCurrentLocal: boolean, remoteVersion: number, remote: {
109
+ attrs: CommonAttrs;
110
+ xattrs?: XAttrs;
111
+ }, syncedVersion: number, synced: {
112
+ attrs: CommonAttrs;
113
+ xattrs?: XAttrs;
114
+ }): CommonDiff;
115
+ protected getRemoteVersionToDiff(remoteVersion: number | undefined): Promise<{
116
+ rm: false;
117
+ isCurrentLocal: boolean;
118
+ remoteVersion: number;
119
+ syncedVersion?: number;
120
+ } | {
121
+ rm: true;
122
+ rmDiff: CommonDiff;
123
+ } | undefined>;
105
124
  }
106
125
  export declare function shouldReadCurrentVersion(flags: VersionedReadFlags | undefined): boolean;
126
+ export declare function areBytesEqual(b1: Uint8Array, b2: Uint8Array): boolean;
127
+ export declare function versionFromRemoteBranch(remote: SyncVersionsBranch, remoteVersion: number | undefined): number | undefined;
107
128
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2015 - 2020, 2022, 2025 3NSoft Inc.
3
+ Copyright (C) 2015 - 2020, 2022, 2025 - 2026 3NSoft Inc.
4
4
 
5
5
  This program is free software: you can redistribute it and/or modify it under
6
6
  the terms of the GNU General Public License as published by the Free Software
@@ -18,6 +18,8 @@
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.NodeInFS = void 0;
20
20
  exports.shouldReadCurrentVersion = shouldReadCurrentVersion;
21
+ exports.areBytesEqual = areBytesEqual;
22
+ exports.versionFromRemoteBranch = versionFromRemoteBranch;
21
23
  /**
22
24
  * Everything in this module is assumed to be inside of a file system
23
25
  * reliance set.
@@ -32,6 +34,7 @@ const operators_1 = require("rxjs/operators");
32
34
  const attrs_1 = require("./attrs");
33
35
  const assert_1 = require("../../lib-common/assert");
34
36
  const runtime_1 = require("../../lib-common/exceptions/runtime");
37
+ const json_utils_1 = require("../../lib-common/json-utils");
35
38
  class NodeInFS {
36
39
  get version() {
37
40
  return this.currentVersion;
@@ -346,8 +349,7 @@ class NodeInFS {
346
349
  if (this.parentId) {
347
350
  const parent = storage.nodes.get(this.parentId);
348
351
  if (parent) {
349
- status.existsInSyncedParent =
350
- await parent.childExistsInSyncedVersion(this.objId);
352
+ status.existsInSyncedParent = await parent.childExistsInSyncedVersion(this.objId);
351
353
  }
352
354
  }
353
355
  return status;
@@ -523,6 +525,46 @@ class NodeInFS {
523
525
  const uploadHeader = await this.crypto.reencryptHeader(localHeader, uploadVersion);
524
526
  return { localHeader, localVersion, uploadHeader, uploadVersion };
525
527
  }
528
+ diffWithArchivedRemote(isCurrentLocal) {
529
+ return {
530
+ currentVersion: this.version,
531
+ isCurrentLocal,
532
+ isRemoteRemoved: true,
533
+ };
534
+ }
535
+ commonDiffWithRemote(isCurrentLocal, remoteVersion, remote, syncedVersion, synced) {
536
+ const { ctime, mtime } = diffAttrs(this.attrs, remote.attrs, synced.attrs);
537
+ return {
538
+ currentVersion: this.version,
539
+ isCurrentLocal,
540
+ isRemoteRemoved: false,
541
+ remoteVersion,
542
+ syncedVersion,
543
+ ctime,
544
+ mtime,
545
+ xattrs: diffXAttrs(this.xattrs, remote.xattrs, synced.xattrs)
546
+ };
547
+ }
548
+ async getRemoteVersionToDiff(remoteVersion) {
549
+ const { state, remote, synced } = await this.syncStatus();
550
+ let isCurrentLocal;
551
+ if (state === 'behind') {
552
+ isCurrentLocal = false;
553
+ }
554
+ else if (state === 'conflicting') {
555
+ isCurrentLocal = true;
556
+ }
557
+ else {
558
+ return;
559
+ }
560
+ remoteVersion = versionFromRemoteBranch(remote, remoteVersion);
561
+ if (remoteVersion) {
562
+ return { rm: false, isCurrentLocal, remoteVersion, syncedVersion: synced === null || synced === void 0 ? void 0 : synced.latest };
563
+ }
564
+ else {
565
+ return { rm: true, rmDiff: this.diffWithArchivedRemote(isCurrentLocal) };
566
+ }
567
+ }
526
568
  }
527
569
  exports.NodeInFS = NodeInFS;
528
570
  Object.freeze(NodeInFS.prototype);
@@ -558,4 +600,131 @@ function shouldReadCurrentVersion(flags) {
558
600
  }
559
601
  return true;
560
602
  }
603
+ function diffAttrs(current, remote, synced) {
604
+ return {
605
+ ctime: {
606
+ current: new Date(current.ctime),
607
+ remote: new Date(remote.ctime),
608
+ synced: new Date(synced.ctime)
609
+ },
610
+ mtime: {
611
+ current: new Date(current.mtime),
612
+ remote: new Date(remote.mtime),
613
+ synced: new Date(synced.mtime)
614
+ }
615
+ };
616
+ }
617
+ function diffXAttrs(current, remote, synced) {
618
+ const diff = {};
619
+ const checkedNames = new Set();
620
+ if (current) {
621
+ for (const name of current.list()) {
622
+ const localValue = current.get(name);
623
+ const syncedValue = synced === null || synced === void 0 ? void 0 : synced.get(name);
624
+ const remoteValue = remote === null || remote === void 0 ? void 0 : remote.get(name);
625
+ if (remoteValue === undefined) {
626
+ if (syncedValue === undefined) {
627
+ diff[name] = { addedIn: 'l' };
628
+ }
629
+ else if (areXAttrValuesEqual(localValue, syncedValue)) {
630
+ diff[name] = { removedIn: 'r' };
631
+ }
632
+ else {
633
+ diff[name] = { removedIn: 'r', changedIn: 'l' };
634
+ }
635
+ }
636
+ else if (areXAttrValuesEqual(localValue, remoteValue)) {
637
+ // no differences between local and remote
638
+ }
639
+ else {
640
+ if (syncedValue === undefined) {
641
+ diff[name] = { addedIn: 'l&r', changedIn: 'l&r' };
642
+ }
643
+ else if (areXAttrValuesEqual(localValue, syncedValue)) {
644
+ diff[name] = { changedIn: 'r' };
645
+ }
646
+ else {
647
+ diff[name] = { changedIn: 'l&r' };
648
+ }
649
+ }
650
+ checkedNames.add(name);
651
+ }
652
+ }
653
+ if (remote) {
654
+ for (const name of remote.list()) {
655
+ if (checkedNames.has(name)) {
656
+ continue;
657
+ }
658
+ const remoteValue = remote.get(name);
659
+ const syncedValue = synced === null || synced === void 0 ? void 0 : synced.get(name);
660
+ if (syncedValue === undefined) {
661
+ diff[name] = { addedIn: 'r' };
662
+ }
663
+ else if (areXAttrValuesEqual(remoteValue, syncedValue)) {
664
+ diff[name] = { removedIn: 'l' };
665
+ }
666
+ else {
667
+ diff[name] = { removedIn: 'l', changedIn: 'r' };
668
+ }
669
+ }
670
+ }
671
+ return ((Object.keys(diff).length > 0) ? diff : undefined);
672
+ }
673
+ function areXAttrValuesEqual(v1, v2) {
674
+ if (Buffer.isBuffer(v1) || ArrayBuffer.isView(v1)) {
675
+ if (Buffer.isBuffer(v2) || ArrayBuffer.isView(v2)) {
676
+ return areBytesEqual(v1, v2);
677
+ }
678
+ {
679
+ return false;
680
+ }
681
+ }
682
+ else if (typeof v1 === 'string') {
683
+ if (typeof v2 === 'string') {
684
+ return (v1 === v2);
685
+ }
686
+ else {
687
+ return false;
688
+ }
689
+ }
690
+ else {
691
+ if (Buffer.isBuffer(v2) || ArrayBuffer.isView(v2)
692
+ || (typeof v2 === 'string')) {
693
+ return false;
694
+ }
695
+ else {
696
+ return (0, json_utils_1.deepEqual)(v1, v2);
697
+ }
698
+ }
699
+ }
700
+ function areBytesEqual(b1, b2) {
701
+ if (b1.length !== b2.length) {
702
+ return false;
703
+ }
704
+ for (let i = 0; i < b1.length; i += 1) {
705
+ if (b1[i] !== b2[i]) {
706
+ return false;
707
+ }
708
+ }
709
+ return true;
710
+ }
711
+ function versionFromRemoteBranch(remote, remoteVersion) {
712
+ var _a;
713
+ if (remote.isArchived) {
714
+ return;
715
+ }
716
+ if (remoteVersion) {
717
+ if ((remoteVersion !== remote.latest)
718
+ && !((_a = remote.archived) === null || _a === void 0 ? void 0 : _a.includes(remoteVersion))) {
719
+ throw (0, exceptions_1.makeFSSyncException)(this.name, {
720
+ versionMismatch: true,
721
+ message: `Unknown remote version ${remoteVersion}`
722
+ });
723
+ }
724
+ return remoteVersion;
725
+ }
726
+ else {
727
+ return remote.latest;
728
+ }
729
+ }
561
730
  Object.freeze(exports);
@@ -6,4 +6,4 @@ export declare function makeFileException(flag: keyof FileExceptionFlag, path: s
6
6
  export declare function maskPathInExc(pathPrefixMaskLen: number, exc: any): FileException;
7
7
  export declare function ensureCorrectFS(fs: web3n.files.FS, type: web3n.files.FSType, writable: boolean): void;
8
8
  export declare function makeNoAttrsExc(path: string): FileException;
9
- export declare function makeVersionMismatchExc(path: string): FileException;
9
+ export declare function makeVersionMismatchExc(path: string, message?: string): FileException;
@@ -98,13 +98,14 @@ function makeNoAttrsExc(path) {
98
98
  attrsNotEnabledInFS: true
99
99
  };
100
100
  }
101
- function makeVersionMismatchExc(path) {
101
+ function makeVersionMismatchExc(path, message) {
102
102
  return {
103
103
  runtimeException: true,
104
104
  type: 'file',
105
105
  code: undefined,
106
106
  path,
107
- versionMismatch: true
107
+ versionMismatch: true,
108
+ message
108
109
  };
109
110
  }
110
111
  Object.freeze(exports);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2016 - 2017, 2025 3NSoft Inc.
3
+ Copyright (C) 2016 - 2017, 2025 - 2026 3NSoft Inc.
4
4
 
5
5
  This program is free software: you can redistribute it and/or modify it under
6
6
  the terms of the GNU General Public License as published by the Free Software
@@ -504,6 +504,7 @@ class EventsReceivingSide extends RequestingSide {
504
504
  * @param err
505
505
  */
506
506
  completeEvent(ipcChannel, err) {
507
+ var _a, _b, _c, _d;
507
508
  const listeners = this.listeners.get(ipcChannel);
508
509
  if (!listeners) {
509
510
  return;
@@ -512,14 +513,10 @@ class EventsReceivingSide extends RequestingSide {
512
513
  for (const listener of listeners) {
513
514
  try {
514
515
  if (err === undefined) {
515
- if (listener.observer.complete) {
516
- listener.observer.complete();
517
- }
516
+ (_b = (_a = listener.observer).complete) === null || _b === void 0 ? void 0 : _b.call(_a);
518
517
  }
519
518
  else {
520
- if (listener.observer.error) {
521
- listener.observer.error(err);
522
- }
519
+ (_d = (_c = listener.observer).error) === null || _d === void 0 ? void 0 : _d.call(_c, err);
523
520
  }
524
521
  }
525
522
  catch (err2) {
@@ -528,7 +525,24 @@ class EventsReceivingSide extends RequestingSide {
528
525
  }
529
526
  }
530
527
  handleCompletion(err) {
528
+ var _a, _b, _c, _d, _e;
531
529
  super.handleCompletion(err);
530
+ for (const ipcChannel of this.listeners.keys()) {
531
+ const listeners = (_a = this.listeners.get(ipcChannel)) !== null && _a !== void 0 ? _a : [];
532
+ for (const listener of listeners) {
533
+ try {
534
+ if (err === undefined) {
535
+ (_c = (_b = listener.observer).complete) === null || _c === void 0 ? void 0 : _c.call(_b);
536
+ }
537
+ else {
538
+ (_e = (_d = listener.observer).error) === null || _e === void 0 ? void 0 : _e.call(_d, err);
539
+ }
540
+ }
541
+ catch (err2) {
542
+ console.error(err2);
543
+ }
544
+ }
545
+ }
532
546
  this.listeners.clear();
533
547
  }
534
548
  subscribe(channel, observer) {
@@ -635,37 +649,31 @@ class MultiObserverWrap {
635
649
  return () => { this.obs.delete(obs); };
636
650
  }
637
651
  next(o) {
652
+ var _a;
638
653
  if (this.isDone) {
639
654
  return;
640
655
  }
641
656
  for (const obs of this.obs) {
642
- if (!obs.next) {
643
- continue;
644
- }
645
- obs.next(o);
657
+ (_a = obs.next) === null || _a === void 0 ? void 0 : _a.call(obs, o);
646
658
  }
647
659
  }
648
660
  error(err) {
661
+ var _a;
649
662
  if (this.isDone) {
650
663
  return;
651
664
  }
652
665
  for (const obs of this.obs) {
653
- if (!obs.error) {
654
- continue;
655
- }
656
- obs.error(err);
666
+ (_a = obs.error) === null || _a === void 0 ? void 0 : _a.call(obs, err);
657
667
  }
658
668
  this.setDone();
659
669
  }
660
670
  complete() {
671
+ var _a;
661
672
  if (this.isDone) {
662
673
  return;
663
674
  }
664
675
  for (const obs of this.obs) {
665
- if (!obs.complete) {
666
- continue;
667
- }
668
- obs.complete();
676
+ (_a = obs.complete) === null || _a === void 0 ? void 0 : _a.call(obs);
669
677
  }
670
678
  this.setDone();
671
679
  }
@@ -697,29 +705,26 @@ class SingleObserverWrap {
697
705
  this.obs = obs;
698
706
  }
699
707
  next(o) {
708
+ var _a, _b;
700
709
  if (this.isDone || !this.obs) {
701
710
  return;
702
711
  }
703
- if (this.obs.next) {
704
- this.obs.next(o);
705
- }
712
+ (_b = (_a = this.obs) === null || _a === void 0 ? void 0 : _a.next) === null || _b === void 0 ? void 0 : _b.call(_a, o);
706
713
  }
707
714
  error(err) {
715
+ var _a, _b;
708
716
  if (this.isDone) {
709
717
  return;
710
718
  }
711
- if (this.obs && this.obs.error) {
712
- this.obs.error(err);
713
- }
719
+ (_b = (_a = this.obs) === null || _a === void 0 ? void 0 : _a.error) === null || _b === void 0 ? void 0 : _b.call(_a, err);
714
720
  this.setDone();
715
721
  }
716
722
  complete() {
723
+ var _a, _b;
717
724
  if (this.isDone) {
718
725
  return;
719
726
  }
720
- if (this.obs && this.obs.complete) {
721
- this.obs.complete();
722
- }
727
+ (_b = (_a = this.obs) === null || _a === void 0 ? void 0 : _a.complete) === null || _b === void 0 ? void 0 : _b.call(_a);
723
728
  this.setDone();
724
729
  }
725
730
  setDone() {
@@ -10,6 +10,7 @@ export interface WSException extends web3n.RuntimeException {
10
10
  }
11
11
  export declare function makeWSException(params: Partial<WSException>, flags?: Partial<WSException>): WSException;
12
12
  export interface ConnectionStatus {
13
+ type: 'heartbeat' | 'heartbeat-skip' | 'disconnected' | 'connected';
13
14
  url: string;
14
15
  /**
15
16
  * ping number is a number of millisecond between previous and current data receiving from server.
@@ -19,6 +20,7 @@ export interface ConnectionStatus {
19
20
  * This mirrors a "slow socket" exception, thrown to data sending process.
20
21
  */
21
22
  slowSocket?: true;
23
+ missingPongsFromServer?: number;
22
24
  socketClosed?: true;
23
25
  error?: any;
24
26
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2017, 2025 3NSoft Inc.
3
+ Copyright (C) 2017, 2025 - 2026 3NSoft Inc.
4
4
 
5
5
  This program is free software: you can redistribute it and/or modify it under
6
6
  the terms of the GNU General Public License as published by the Free Software
@@ -13,7 +13,8 @@
13
13
  See the GNU General Public License for more details.
14
14
 
15
15
  You should have received a copy of the GNU General Public License along with
16
- this program. If not, see <http://www.gnu.org/licenses/>. */
16
+ this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
17
18
  Object.defineProperty(exports, "__esModule", { value: true });
18
19
  exports.WebSocketListening = exports.makeEventfulServer = void 0;
19
20
  exports.makeWSException = makeWSException;
@@ -30,6 +31,7 @@ function makeWSException(params, flags) {
30
31
  return (0, runtime_1.makeRuntimeException)('websocket', params, flags !== null && flags !== void 0 ? flags : {});
31
32
  }
32
33
  const MAX_TXT_BUFFER = 64 * 1024;
34
+ const CLIENT_SIDE_PING_PERIOD = 10 * 1000;
33
35
  /**
34
36
  * This creates a json communication point on a given web socket.
35
37
  * Point may have many listeners, allowing for single parsing of incoming
@@ -39,6 +41,21 @@ const MAX_TXT_BUFFER = 64 * 1024;
39
41
  function makeJsonCommPoint(ws) {
40
42
  const observers = new generic_ipc_1.MultiObserverWrap();
41
43
  const { heartbeat, healthyBeat, otherBeat } = makeHeartbeat(ws.url);
44
+ let outstandingPongs = 0;
45
+ let closedByPingProc = false;
46
+ const pingRepeat = setInterval(() => {
47
+ if (outstandingPongs >= 2) {
48
+ ws.close();
49
+ closedByPingProc = true;
50
+ clearInterval(pingRepeat);
51
+ return;
52
+ }
53
+ if (outstandingPongs > 0) {
54
+ otherBeat('heartbeat-skip', { missingPongsFromServer: outstandingPongs });
55
+ }
56
+ ws.ping();
57
+ outstandingPongs += 1;
58
+ }, CLIENT_SIDE_PING_PERIOD);
42
59
  ws.on('message', data => {
43
60
  if (observers.done) {
44
61
  return;
@@ -49,7 +66,8 @@ function makeJsonCommPoint(ws) {
49
66
  }
50
67
  catch (err) {
51
68
  ws.close();
52
- otherBeat(err, true);
69
+ clearInterval(pingRepeat);
70
+ otherBeat('disconnected', err, true);
53
71
  observers.error(err);
54
72
  return;
55
73
  }
@@ -57,12 +75,13 @@ function makeJsonCommPoint(ws) {
57
75
  healthyBeat();
58
76
  });
59
77
  ws.on('close', (code, reason) => {
78
+ clearInterval(pingRepeat);
60
79
  if (code === 1000) {
61
- otherBeat({ socketClosed: true }, true);
80
+ otherBeat('disconnected', { socketClosed: true }, true);
62
81
  observers.complete();
63
82
  }
64
83
  else {
65
- otherBeat({ error: { code, reason } }, true);
84
+ otherBeat('disconnected', { error: { code, reason } }, true);
66
85
  observers.error(makeWSException({
67
86
  socketClosed: true,
68
87
  cause: { code, reason }
@@ -70,24 +89,30 @@ function makeJsonCommPoint(ws) {
70
89
  }
71
90
  });
72
91
  ws.on('error', (err) => {
73
- otherBeat(err, true);
92
+ otherBeat('disconnected', err, true);
74
93
  observers.error(makeWSException({ cause: err }));
94
+ clearInterval(pingRepeat);
75
95
  ws.close();
76
96
  });
77
97
  ws.on('ping', () => {
78
98
  ws.pong();
79
99
  healthyBeat();
80
100
  });
101
+ ws.on('pong', () => {
102
+ healthyBeat();
103
+ outstandingPongs = 0;
104
+ });
81
105
  const comm = {
82
106
  subscribe: obs => observers.add(obs),
83
107
  postMessage(env) {
84
108
  if (ws.bufferedAmount > MAX_TXT_BUFFER) {
85
- otherBeat({ slowSocket: true });
109
+ otherBeat('heartbeat', { slowSocket: true });
86
110
  throw makeWSException({ socketSlow: true });
87
111
  }
88
112
  ws.send(JSON.stringify(env));
89
113
  }
90
114
  };
115
+ otherBeat('connected', {});
91
116
  return { comm, heartbeat };
92
117
  }
93
118
  function makeHeartbeat(url) {
@@ -96,13 +121,15 @@ function makeHeartbeat(url) {
96
121
  function healthyBeat() {
97
122
  const now = Date.now();
98
123
  status.next({
124
+ type: 'heartbeat',
99
125
  url,
100
126
  ping: now - lastInfo
101
127
  });
102
128
  lastInfo = now;
103
129
  }
104
- function otherBeat(params, end = false) {
130
+ function otherBeat(type, params, end = false) {
105
131
  status.next({
132
+ type,
106
133
  url,
107
134
  ...params
108
135
  });
@@ -6,4 +6,5 @@ export declare class MapOfSets<TKey, TValue> {
6
6
  remove(key: TKey, value: TValue): void;
7
7
  removeAll(key: TKey): void;
8
8
  clear(): void;
9
+ keys(): MapIterator<TKey>;
9
10
  }
@@ -48,6 +48,9 @@ class MapOfSets {
48
48
  clear() {
49
49
  this.map.clear();
50
50
  }
51
+ keys() {
52
+ return this.map.keys();
53
+ }
51
54
  }
52
55
  exports.MapOfSets = MapOfSets;
53
56
  Object.freeze(MapOfSets.prototype);