core-3nweb-client-lib 0.44.0 → 0.44.2

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.
@@ -46,21 +46,16 @@ function openSocket(url, sessionId) {
46
46
  ws.on('error', initOnError);
47
47
  ws.on('unexpected-response', onNonOkReply);
48
48
  ws.once('open', () => {
49
- const okReply = {
49
+ opening === null || opening === void 0 ? void 0 : opening.resolve({
50
50
  url,
51
51
  method: 'GET',
52
52
  status: 200,
53
53
  data: ws
54
- };
55
- opening === null || opening === void 0 ? void 0 : opening.resolve(okReply);
54
+ });
56
55
  opening = undefined;
57
56
  ws.removeListener('error', initOnError);
58
57
  ws.removeListener('unexpected-response', onNonOkReply);
59
58
  });
60
- ws.on('error', err => {
61
- // XXX we need
62
- console.error(`Error in ${ws.url} connection`, err);
63
- });
64
59
  return opening.promise;
65
60
  }
66
61
  Object.freeze(exports);
@@ -2,6 +2,7 @@ import type { ScryptGenParams } from '../key-derivation';
2
2
  import type { AsyncSBoxCryptor, Subscribe, ObjSource } from 'xsp-files';
3
3
  import type { Observable } from 'rxjs';
4
4
  import type { LogError } from '../logging/log-to-file';
5
+ import { StorageConnectionStatus } from '../../core/storage/synced/remote-events';
5
6
  export type { AsyncSBoxCryptor } from 'xsp-files';
6
7
  export type { FolderInJSON } from './folder-node';
7
8
  type StorageType = web3n.files.FSType;
@@ -119,6 +120,7 @@ export interface SyncedStorage extends Storage {
119
120
  getNumOfBytesNeedingDownload(objId: ObjId, version: number): Promise<number | 'unknown'>;
120
121
  suspendNetworkActivity(): void;
121
122
  resumeNetworkActivity(): void;
123
+ connectionEvent$: Observable<StorageConnectionStatus>;
122
124
  }
123
125
  export interface SyncedObjStatus extends LocalObjStatus {
124
126
  syncStatus(): SyncStatus;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2015 - 2017, 2019 - 2020, 2022, 2025 3NSoft Inc.
3
+ Copyright (C) 2015 - 2017, 2019 - 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
@@ -126,6 +126,7 @@ function wrapSyncStorageImplementation(impl) {
126
126
  wrap.suspendNetworkActivity = impl.suspendNetworkActivity.bind(impl);
127
127
  wrap.resumeNetworkActivity = impl.resumeNetworkActivity.bind(impl);
128
128
  wrap.getNumOfBytesNeedingDownload = impl.getNumOfBytesNeedingDownload.bind(impl);
129
+ wrap.connectionEvent$ = impl.connectionEvent$;
129
130
  return Object.freeze(wrap);
130
131
  }
131
132
  function isSyncedStorage(storage) {
@@ -1096,13 +1096,11 @@ class FolderNode extends node_in_fs_1.NodeInFS {
1096
1096
  const nodes = state.nodes;
1097
1097
  const remoteNodes = remote.folderInfo.nodes;
1098
1098
  const events = [];
1099
- if (removed) {
1100
- for (const { name, removedIn } of removed) {
1101
- if (removedIn === 'r') {
1102
- const node = nodes[name];
1103
- delete nodes[name];
1104
- events.push(this.makeEntryRemovalEvent(newVersion, 'sync', node));
1105
- }
1099
+ if (removed === null || removed === void 0 ? void 0 : removed.inRemote) {
1100
+ for (const name of removed.inRemote) {
1101
+ const node = nodes[name];
1102
+ delete nodes[name];
1103
+ events.push(this.makeEntryRemovalEvent(newVersion, 'sync', node));
1106
1104
  }
1107
1105
  }
1108
1106
  if (renamed) {
@@ -1122,19 +1120,17 @@ class FolderNode extends node_in_fs_1.NodeInFS {
1122
1120
  }
1123
1121
  }
1124
1122
  }
1125
- if (added) {
1126
- for (const { name, addedIn } of added) {
1127
- if (addedIn === 'r') {
1128
- const node = remoteNodes[name];
1129
- (0, assert_1.assert)(!!node);
1130
- if (nameOverlaps === null || nameOverlaps === void 0 ? void 0 : nameOverlaps.includes(name)) {
1131
- while (nodes[node.name]) {
1132
- node.name = `${node.name}${opts === null || opts === void 0 ? void 0 : opts.postfixForNameOverlaps}`;
1133
- }
1123
+ if (added === null || added === void 0 ? void 0 : added.inRemote) {
1124
+ for (const name of added.inRemote) {
1125
+ const node = remoteNodes[name];
1126
+ (0, assert_1.assert)(!!node);
1127
+ if (nameOverlaps === null || nameOverlaps === void 0 ? void 0 : nameOverlaps.includes(name)) {
1128
+ while (nodes[node.name]) {
1129
+ node.name = `${node.name}${opts === null || opts === void 0 ? void 0 : opts.postfixForNameOverlaps}`;
1134
1130
  }
1135
- nodes[node.name] = node;
1136
- events.push(this.makeEntryAdditionEvent(newVersion, 'sync', node));
1137
1131
  }
1132
+ nodes[node.name] = node;
1133
+ events.push(this.makeEntryAdditionEvent(newVersion, 'sync', node));
1138
1134
  }
1139
1135
  }
1140
1136
  return events;
@@ -1188,9 +1184,9 @@ function addToTransitionState(state, f, key) {
1188
1184
  function diffNodes(current, remote, synced) {
1189
1185
  const currentNodesById = orderByIds(current.nodes);
1190
1186
  const syncedNodesById = orderByIds(synced.nodes);
1191
- const removed = [];
1187
+ const removed = { inLocal: [], inRemote: [] };
1192
1188
  const renamed = [];
1193
- const added = [];
1189
+ const added = { inLocal: [], inRemote: [] };
1194
1190
  const rekeyed = [];
1195
1191
  const nameOverlaps = [];
1196
1192
  for (const remoteNode of Object.values(remote.nodes)) {
@@ -1229,10 +1225,10 @@ function diffNodes(current, remote, synced) {
1229
1225
  else {
1230
1226
  const syncedNode = syncedNodesById.get(remoteNode.objId);
1231
1227
  if (syncedNode) {
1232
- removed.push({ name: remoteNode.name, removedIn: 'l' });
1228
+ removed.inLocal.push(remoteNode.name);
1233
1229
  }
1234
1230
  else {
1235
- added.push({ name: remoteNode.name, addedIn: 'r' });
1231
+ added.inRemote.push(remoteNode.name);
1236
1232
  }
1237
1233
  if (current.nodes[remoteNode.name]) {
1238
1234
  nameOverlaps.push(remoteNode.name);
@@ -1242,13 +1238,13 @@ function diffNodes(current, remote, synced) {
1242
1238
  for (const localNode of currentNodesById.values()) {
1243
1239
  const syncedNode = syncedNodesById.get(localNode.objId);
1244
1240
  if (!syncedNode) {
1245
- added.push({ name: localNode.name, addedIn: 'l' });
1241
+ added.inLocal.push(localNode.name);
1246
1242
  }
1247
1243
  }
1248
1244
  return {
1249
- removed: ((removed.length > 0) ? removed : undefined),
1245
+ removed: reduceEmptyIn(removed),
1250
1246
  renamed: ((renamed.length > 0) ? renamed : undefined),
1251
- added: ((added.length > 0) ? added : undefined),
1247
+ added: reduceEmptyIn(added),
1252
1248
  rekeyed: ((rekeyed.length > 0) ? rekeyed : undefined),
1253
1249
  nameOverlaps: ((nameOverlaps.length > 0) ? nameOverlaps : undefined),
1254
1250
  };
@@ -1291,4 +1287,22 @@ function identifyChanges(originalNodes, newNodes) {
1291
1287
  }
1292
1288
  return { addedNodes, removedNodes, renamedNodes };
1293
1289
  }
1290
+ function reduceEmptyIn(c) {
1291
+ if (c.inLocal.length > 0) {
1292
+ if (c.inRemote.length > 0) {
1293
+ return c;
1294
+ }
1295
+ else {
1296
+ return { inLocal: c.inLocal };
1297
+ }
1298
+ }
1299
+ else {
1300
+ if (c.inRemote.length > 0) {
1301
+ return { inRemote: c.inRemote };
1302
+ }
1303
+ else {
1304
+ return;
1305
+ }
1306
+ }
1307
+ }
1294
1308
  Object.freeze(exports);
@@ -859,24 +859,9 @@ class S {
859
859
  throw (0, file_1.makeFileException)('notDirectory', `${path}/${remoteItemName}`);
860
860
  }
861
861
  }
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
862
  async mergeFolderCurrentAndRemoteVersions(path, opts) {
869
863
  const folderNode = await this.getFolderNode(path);
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;
864
+ return await folderNode.mergeCurrentAndRemoteVersions(opts);
880
865
  }
881
866
  }
882
867
  Object.freeze(S.prototype);
@@ -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);