core-3nweb-client-lib 0.49.7 → 0.49.8

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.
@@ -299,6 +299,7 @@ declare namespace web3n.asmail {
299
299
  objNotFound?: true;
300
300
  objId?: string;
301
301
  msgIsBroken?: true;
302
+ failToDecrypt?: true;
302
303
  }
303
304
 
304
305
  interface ASMailSendException extends RuntimeException {
@@ -72,35 +72,12 @@ export interface ResourcesForReceiving {
72
72
  * messages have not been removed via direct action or due to expiry.
73
73
  * This object is also responsible for expiring messages on the server.
74
74
  */
75
- export declare class InboxOnServer {
76
- private readonly r;
77
- private readonly msgReceiver;
78
- private readonly storages;
79
- private readonly cryptor;
80
- private readonly random;
81
- private readonly downloader;
82
- private readonly cache;
83
- private readonly index;
84
- private readonly logError;
85
- private readonly inboxEvents;
86
- private readonly procs;
87
- private readonly recentlyOpenedMsgs;
88
- private constructor();
89
- static makeAndStart(cachePath: string, syncedFS: WritableFS, localFS: WritableFS, r: ResourcesForReceiving): Promise<InboxOnServer>;
90
- close(): Promise<void>;
91
- makeCAP(): InboxService;
92
- private removeMsg;
93
- private removeMsgFromServerAndCache;
94
- private msgFromDiskOrDownload;
95
- private startCachingAndAddKeyToIndex;
96
- private markOwnParams;
97
- private absorbSendingParams;
98
- private listMsgs;
99
- private listNewMsgs;
100
- private getMsg;
101
- private msgToUIForm;
102
- suspendNetworkActivity(): void;
103
- resumeNetworkActivity(): void;
104
- get connectivityEvent$(): import("rxjs").Observable<import("./inbox-events").InboxConnectionStatus>;
105
- }
75
+ export type InboxOnServer = Awaited<ReturnType<typeof makeInboxOnServer>>;
76
+ export declare function makeInboxOnServer(cachePath: string, syncedFS: WritableFS, localFS: WritableFS, r: ResourcesForReceiving): Promise<{
77
+ connectivityEvent$: import("rxjs").Observable<import("./inbox-events").InboxConnectionStatus>;
78
+ resumeNetworkActivity: () => void;
79
+ suspendNetworkActivity: () => void;
80
+ close: () => Promise<void>;
81
+ makeCAP: () => InboxService;
82
+ }>;
106
83
  export {};
@@ -16,8 +16,7 @@
16
16
  this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.InboxOnServer = void 0;
20
- const error_1 = require("../../../lib-common/exceptions/error");
19
+ exports.makeInboxOnServer = makeInboxOnServer;
21
20
  const synced_1 = require("../../../lib-common/processes/synced");
22
21
  const recipient_1 = require("../../../lib-client/asmail/recipient");
23
22
  const opener_1 = require("../msg/opener");
@@ -32,189 +31,161 @@ const cached_msgs_1 = require("./cached-msgs");
32
31
  const timed_cache_1 = require("../../../lib-common/timed-cache");
33
32
  const fs_sync_utils_1 = require("../../../lib-client/fs-utils/fs-sync-utils");
34
33
  const MSG_INDEX_FOLDER = 'msg-index';
35
- /**
36
- * Instance of this class represents inbox-on-mail-server.
37
- * It uses api to manage messages on a ASMail server, caching and recording
38
- * some information to allow faster response, and to keep message keys, while
39
- * messages have not been removed via direct action or due to expiry.
40
- * This object is also responsible for expiring messages on the server.
41
- */
42
- class InboxOnServer {
43
- constructor(r, msgReceiver, storages, cryptor, random, downloader, cache, index, logError) {
44
- this.r = r;
45
- this.msgReceiver = msgReceiver;
46
- this.storages = storages;
47
- this.cryptor = cryptor;
48
- this.random = random;
49
- this.downloader = downloader;
50
- this.cache = cache;
51
- this.index = index;
52
- this.logError = logError;
53
- this.procs = new synced_1.NamedProcs();
54
- this.recentlyOpenedMsgs = (0, timed_cache_1.makeTimedCache)(60 * 1000);
55
- this.inboxEvents = new inbox_events_1.InboxEvents(this.msgReceiver, this.getMsg.bind(this), this.listNewMsgs.bind(this), this.removeMsg.bind(this), this.logError);
56
- Object.seal(this);
34
+ async function makeInboxOnServer(cachePath, syncedFS, localFS, r) {
35
+ const procs = new synced_1.NamedProcs();
36
+ const recentlyOpenedMsgs = (0, timed_cache_1.makeTimedCache)(60 * 1000);
37
+ const { getStorages, cryptor, random, logError, } = r;
38
+ (0, file_1.ensureCorrectFS)(syncedFS, 'synced', true);
39
+ const indexSyncedFS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(syncedFS, MSG_INDEX_FOLDER);
40
+ const indexLocalFS = await localFS.writableSubRoot(MSG_INDEX_FOLDER);
41
+ const msgReceiver = new recipient_1.MailRecipient(r.address, r.getSigner, () => r.asmailResolver(r.address), r.makeNet());
42
+ const downloader = new msg_downloader_1.MsgDownloader(msgReceiver);
43
+ const cache = await cached_msgs_1.CachedMessages.makeFor(cachePath, downloader, r.logError);
44
+ const index = await (0, msg_indexing_1.makeMsgIndex)(indexSyncedFS, indexLocalFS, r.logError);
45
+ const inboxEvents = new inbox_events_1.InboxEvents(msgReceiver, getMsg, listNewMsgs, removeMsg, r.logError);
46
+ async function close() {
47
+ index.stopSyncing();
48
+ inboxEvents.close();
57
49
  }
58
- static async makeAndStart(cachePath, syncedFS, localFS, r) {
59
- try {
60
- (0, file_1.ensureCorrectFS)(syncedFS, 'synced', true);
61
- const msgReceiver = new recipient_1.MailRecipient(r.address, r.getSigner, () => r.asmailResolver(r.address), r.makeNet());
62
- const downloader = new msg_downloader_1.MsgDownloader(msgReceiver);
63
- const cache = await cached_msgs_1.CachedMessages.makeFor(cachePath, downloader, r.logError);
64
- const indexSyncedFS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(syncedFS, MSG_INDEX_FOLDER);
65
- const indexLocalFS = await localFS.writableSubRoot(MSG_INDEX_FOLDER);
66
- const index = await (0, msg_indexing_1.makeMsgIndex)(indexSyncedFS, indexLocalFS, r.logError);
67
- const inbox = new InboxOnServer(r.correspondents, msgReceiver, r.getStorages, r.cryptor, r.random, downloader, cache, index, r.logError);
68
- return inbox;
69
- }
70
- catch (err) {
71
- throw (0, error_1.errWithCause)(err, 'Failed to initialize Inbox');
72
- }
73
- }
74
- async close() {
75
- this.index.stopSyncing();
76
- this.inboxEvents.close();
77
- }
78
- makeCAP() {
50
+ function makeCAP() {
79
51
  const service = {
80
- getMsg: this.getMsg.bind(this),
81
- listMsgs: this.listMsgs.bind(this),
82
- removeMsg: this.removeMsg.bind(this),
83
- subscribe: this.inboxEvents.subscribe.bind(this.inboxEvents)
52
+ getMsg,
53
+ listMsgs,
54
+ removeMsg,
55
+ subscribe: inboxEvents.subscribe.bind(inboxEvents)
84
56
  };
85
57
  return Object.freeze(service);
86
58
  }
87
- async removeMsg(msgId) {
59
+ async function removeMsg(msgId) {
88
60
  // check for an already started process
89
61
  const procId = 'removal of ' + msgId;
90
- const promise = this.procs.latestTaskAtThisMoment(procId);
62
+ const promise = procs.latestTaskAtThisMoment(procId);
91
63
  if (promise) {
92
64
  return promise;
93
65
  }
94
66
  // start removal process
95
- return this.procs.start(procId, (async () => {
67
+ return procs.start(procId, (async () => {
96
68
  await Promise.all([
97
- this.index.remove(msgId),
98
- this.removeMsgFromServerAndCache(msgId)
69
+ index.remove(msgId),
70
+ removeMsgFromServerAndCache(msgId)
99
71
  ]);
100
72
  }));
101
73
  }
102
- async removeMsgFromServerAndCache(msgId) {
74
+ async function removeMsgFromServerAndCache(msgId) {
103
75
  await Promise.all([
104
- this.cache.deleteMsg(msgId),
76
+ cache.deleteMsg(msgId),
105
77
  // XXX the following is suspicious, as it swallows other exceptions too
106
- this.msgReceiver.removeMsg(msgId).catch(() => { })
78
+ msgReceiver.removeMsg(msgId).catch(() => { })
107
79
  ]);
108
80
  }
109
- async msgFromDiskOrDownload(msgId) {
110
- const msgOnDisk = await this.cache.findMsg(msgId);
81
+ async function msgFromDiskOrDownload(msgId) {
82
+ const msgOnDisk = await cache.findMsg(msgId);
111
83
  if (msgOnDisk) {
112
84
  return msgOnDisk;
113
85
  }
114
- const meta = await this.downloader.getMsgMeta(msgId);
115
- return await this.cache.addMsg(msgId, meta);
86
+ const meta = await downloader.getMsgMeta(msgId);
87
+ return await cache.addMsg(msgId, meta);
88
+ }
89
+ function checkerOfMidKeyCerts(deliveryTS) {
90
+ return (certs) => (0, key_verification_1.checkAndExtractPKeyWithAddress)(msgReceiver.getNet(), r.correspondents.midResolver, certs, Math.round(deliveryTS / 1000));
91
+ }
92
+ function msgReadingAndOpening(msgId, mainObjId, msgOnDisk) {
93
+ // lazy main obj source
94
+ let mainObjSrc = undefined;
95
+ async function getMainObjHeader() {
96
+ if (!mainObjSrc) {
97
+ mainObjSrc = await msgOnDisk.getMsgObj(mainObjId);
98
+ }
99
+ return mainObjSrc.readHeader();
100
+ }
101
+ async function getOpenedMsg(mainObjFileKey, msgKeyPackLen) {
102
+ if (!mainObjSrc) {
103
+ mainObjSrc = await msgOnDisk.getMsgObj(mainObjId);
104
+ }
105
+ return await (0, opener_1.openMsg)(msgId, mainObjId, mainObjSrc, msgKeyPackLen, mainObjFileKey, cryptor);
106
+ }
107
+ return {
108
+ getMainObjHeader, getOpenedMsg
109
+ };
116
110
  }
117
- async startCachingAndAddKeyToIndex(msgId) {
118
- const msgOnDisk = await this.msgFromDiskOrDownload(msgId);
111
+ async function startCachingAndAddKeyToIndex(msgId) {
112
+ const msgOnDisk = await msgFromDiskOrDownload(msgId);
119
113
  const meta = await msgOnDisk.getMsgMeta();
114
+ const { getMainObjHeader, getOpenedMsg } = msgReadingAndOpening(msgId, meta.extMeta.objIds[0], msgOnDisk);
120
115
  try {
121
- // setup closures, some memoized, for use by keyring
122
- let mainObjSrc = undefined;
123
- const mainObjId = meta.extMeta.objIds[0];
124
- const getMainObjHeader = async () => {
125
- if (!mainObjSrc) {
126
- mainObjSrc = await msgOnDisk.getMsgObj(mainObjId);
127
- }
128
- return mainObjSrc.readHeader();
129
- };
130
- const getOpenedMsg = async (mainObjFileKey, msgKeyPackLen) => {
131
- if (!mainObjSrc) {
132
- mainObjSrc = await msgOnDisk.getMsgObj(mainObjId);
133
- }
134
- const openedMsg = await (0, opener_1.openMsg)(msgId, mainObjId, mainObjSrc, msgKeyPackLen, mainObjFileKey, this.cryptor);
135
- return openedMsg;
136
- };
137
- const checkMidKeyCerts = (certs) => {
138
- return (0, key_verification_1.checkAndExtractPKeyWithAddress)(this.msgReceiver.getNet(), this.r.midResolver, certs, Math.round(msgOnDisk.deliveryTS / 1000));
139
- };
140
- const decrOut = await this.r.msgDecryptor(meta.extMeta, getMainObjHeader, getOpenedMsg, checkMidKeyCerts);
116
+ const decrOut = await r.correspondents.msgDecryptor(meta.extMeta, getMainObjHeader, getOpenedMsg, checkerOfMidKeyCerts(msgOnDisk.deliveryTS));
141
117
  if (decrOut) {
142
118
  const { decrInfo, openedMsg } = decrOut;
143
119
  openedMsg.setMsgKeyRole(decrInfo.keyStatus);
120
+ // XXX we have never used this part of protocol, should it be removed at all from ASMail protocol?
144
121
  checkServerAuthIfPresent(meta, decrInfo);
145
122
  // add records cache and to index
146
- this.recentlyOpenedMsgs.set(msgId, openedMsg);
123
+ recentlyOpenedMsgs.set(msgId, openedMsg);
147
124
  const msgInfo = {
148
125
  msgType: openedMsg.getSection('Msg Type'),
149
126
  msgId,
150
127
  deliveryTS: msgOnDisk.deliveryTS
151
128
  };
152
- await this.index.add(msgInfo, decrInfo);
129
+ await index.add(msgInfo, decrInfo);
153
130
  await Promise.all([
154
- this.absorbSendingParams(openedMsg),
155
- this.markOwnParams(meta, openedMsg.sender)
131
+ absorbSendingParams(openedMsg),
132
+ markOwnParams(meta, openedMsg.sender)
156
133
  ]);
157
134
  }
158
135
  else {
159
136
  // check, if msg has already been indexed
160
- const knownDecr = await this.index.getKeyFor(msgId, msgOnDisk.deliveryTS);
137
+ const knownDecr = await index.getKeyFor(msgId, msgOnDisk.deliveryTS);
161
138
  if (!knownDecr) {
162
- await msgOnDisk.updateMsgKeyStatus('not-found');
163
139
  return false;
164
140
  }
165
- // TODO try to open main message, just as a check
166
141
  }
167
- await msgOnDisk.updateMsgKeyStatus('ok');
168
142
  return true;
169
143
  }
170
144
  catch (exc) {
171
- await this.logError(exc, `Problem with opening message ${msgId}`);
172
- if (msgOnDisk.keyStatus !== 'ok') {
173
- await msgOnDisk.updateMsgKeyStatus('fail');
174
- }
145
+ await logError(exc, `Problem with opening message ${msgId}`);
175
146
  return false;
176
147
  }
177
148
  }
178
- async markOwnParams(meta, sender) {
149
+ async function markOwnParams(meta, sender) {
179
150
  if (!meta.invite) {
180
151
  return;
181
152
  }
182
- await this.r.markOwnSendingParamsAsUsed(sender, meta.invite);
153
+ await r.correspondents.markOwnSendingParamsAsUsed(sender, meta.invite);
183
154
  }
184
- async absorbSendingParams(openedMsg) {
155
+ async function absorbSendingParams(openedMsg) {
185
156
  const sendingParams = openedMsg.nextSendingParams;
186
157
  if (!sendingParams) {
187
158
  return;
188
159
  }
189
160
  const address = openedMsg.sender;
190
- await this.r.saveParamsForSendingTo(address, sendingParams);
161
+ await r.correspondents.saveParamsForSendingTo(address, sendingParams);
191
162
  }
192
- async listMsgs(fromTS) {
193
- const checkServer = true; // XXX in future this will be an option from req
194
- if (!checkServer) {
195
- return this.index.listMsgs(fromTS);
196
- }
197
- // check for an already started process
198
- const procId = 'listing msgs';
199
- const promise = this.procs.latestTaskAtThisMoment(procId);
163
+ function debouncingProc(procId, action) {
164
+ const promise = procs.latestTaskAtThisMoment(procId);
200
165
  if (promise) {
201
166
  return promise;
202
167
  }
203
- // start message listing process
204
- return this.procs.start(procId, async () => {
168
+ return procs.start(procId, action);
169
+ }
170
+ async function listMsgs(fromTS) {
171
+ const checkServer = true; // XXX in future this will be an option from req
172
+ if (!checkServer) {
173
+ return index.listMsgs(fromTS);
174
+ }
175
+ return debouncingProc('listing msgs', async () => {
205
176
  // message listing info is located in index, yet, process involves
206
177
  // getting and caching messages' metadata
207
178
  let msgIds;
208
179
  try {
209
- msgIds = await this.msgReceiver.listMsgs(fromTS);
180
+ msgIds = await msgReceiver.listMsgs(fromTS);
210
181
  }
211
182
  catch (exc) {
212
183
  if (exc.type !== 'connect') {
213
184
  throw exc;
214
185
  }
215
- return this.index.listMsgs(fromTS);
186
+ return index.listMsgs(fromTS);
216
187
  }
217
- const indexedMsgs = await this.index.listMsgs(fromTS);
188
+ const indexedMsgs = await index.listMsgs(fromTS);
218
189
  for (const info of indexedMsgs) {
219
190
  const ind = msgIds.indexOf(info.msgId);
220
191
  if (ind >= 0) {
@@ -224,17 +195,17 @@ class InboxOnServer {
224
195
  if (msgIds.length === 0) {
225
196
  return indexedMsgs;
226
197
  }
227
- await Promise.all(msgIds.map(msgId => this.startCachingAndAddKeyToIndex(msgId)
198
+ await Promise.all(msgIds.map(msgId => startCachingAndAddKeyToIndex(msgId)
228
199
  .catch(async (exc) => {
229
- await this.logError(exc, `Failed to start caching message ${msgId}`);
200
+ await logError(exc, `Failed to start caching message ${msgId}`);
230
201
  })));
231
- return this.index.listMsgs(fromTS);
202
+ return index.listMsgs(fromTS);
232
203
  });
233
204
  }
234
- async listNewMsgs(fromTS) {
235
- const msgIds = await this.msgReceiver.listMsgs(fromTS);
205
+ async function listNewMsgs(fromTS) {
206
+ const msgIds = await msgReceiver.listMsgs(fromTS);
236
207
  // remove from listing messages already in index
237
- const indexedMsgs = await this.index.listMsgs(fromTS);
208
+ const indexedMsgs = await index.listMsgs(fromTS);
238
209
  for (const info of indexedMsgs) {
239
210
  const ind = msgIds.indexOf(info.msgId);
240
211
  if (ind >= 0) {
@@ -245,54 +216,39 @@ class InboxOnServer {
245
216
  return [];
246
217
  }
247
218
  // cache and index these
248
- await Promise.all(msgIds.map(msgId => this.startCachingAndAddKeyToIndex(msgId)));
219
+ await Promise.all(msgIds.map(msgId => startCachingAndAddKeyToIndex(msgId)));
249
220
  // get info's from index, focusing on specific messages only
250
- return (await this.index.listMsgs(fromTS)).filter(({ msgId }) => msgIds.includes(msgId));
221
+ return (await index.listMsgs(fromTS)).filter(({ msgId }) => msgIds.includes(msgId));
251
222
  }
252
- async getMsg(msgId) {
223
+ async function getMsg(msgId) {
253
224
  if (!msgId || (typeof msgId !== 'string')) {
254
225
  throw `Given message id is not a non-empty string`;
255
226
  }
256
- const procId = `get msg #${msgId}`;
257
- const promise = this.procs.latestTaskAtThisMoment(procId);
258
- if (promise) {
259
- return promise;
260
- }
261
- return this.procs.start(procId, async () => {
262
- const msgOnDisk = await this.msgFromDiskOrDownload(msgId);
263
- let msg = this.recentlyOpenedMsgs.get(msgId);
227
+ return debouncingProc(`get msg #${msgId}`, async () => {
228
+ const msgOnDisk = await msgFromDiskOrDownload(msgId);
229
+ let msg = recentlyOpenedMsgs.get(msgId);
264
230
  if (msg) {
265
- return this.msgToUIForm(msg, msgOnDisk.deliveryTS, msgOnDisk);
266
- }
267
- if ((msgOnDisk.keyStatus === 'not-found')
268
- || (msgOnDisk.keyStatus === 'fail')) {
269
- // XXX
270
- // message cannot be opened, and should be removed
271
- // await Promise.all([
272
- // this.index.remove(msgInfo),
273
- // this.removeMsgFromServerAndCache(msgId)
274
- // ]);
275
- throw (0, recipient_1.makeMsgNotFoundException)(msgId);
276
- }
277
- else if (msgOnDisk.keyStatus === 'not-checked') {
278
- await this.startCachingAndAddKeyToIndex(msgId);
279
- }
280
- else if (msgOnDisk.keyStatus !== 'ok') {
281
- throw new Error(`Unknown message key status ${msgOnDisk.keyStatus}`);
231
+ return msgToUIForm(msg, msgOnDisk.deliveryTS, msgOnDisk);
282
232
  }
283
233
  const meta = await msgOnDisk.getMsgMeta();
284
234
  const mainObjId = meta.extMeta.objIds[0];
285
235
  const mainObj = await msgOnDisk.getMsgObj(mainObjId);
286
- const msgKey = await this.index.getKeyFor(msgId, meta.deliveryCompletion);
236
+ let msgKey = await index.getKeyFor(msgId, meta.deliveryCompletion);
287
237
  if (!msgKey) {
288
- throw (0, recipient_1.makeMsgNotFoundException)(msgId);
238
+ if (!(await startCachingAndAddKeyToIndex(msgId))) {
239
+ throw (0, recipient_1.makeFailToDecryptMsgException)(msgId);
240
+ }
241
+ msgKey = await index.getKeyFor(msgId, meta.deliveryCompletion);
242
+ if (!msgKey) {
243
+ throw (0, recipient_1.makeFailToDecryptMsgException)(msgId);
244
+ }
289
245
  }
290
- msg = await (0, opener_1.openMsg)(msgId, mainObjId, mainObj, msgKey.mainObjHeaderOfs, msgKey.msgKey, this.cryptor);
246
+ msg = await (0, opener_1.openMsg)(msgId, mainObjId, mainObj, msgKey.mainObjHeaderOfs, msgKey.msgKey, cryptor);
291
247
  msg.setMsgKeyRole(msgKey.msgKeyRole);
292
- return this.msgToUIForm(msg, msgOnDisk.deliveryTS, msgOnDisk);
248
+ return msgToUIForm(msg, msgOnDisk.deliveryTS, msgOnDisk);
293
249
  });
294
250
  }
295
- msgToUIForm(msg, deliveryTS, msgOnDisk) {
251
+ function msgToUIForm(msg, deliveryTS, msgOnDisk) {
296
252
  const m = {
297
253
  sender: msg.sender,
298
254
  establishedSenderKeyChain: msg.establishedKeyChain,
@@ -317,23 +273,18 @@ class InboxOnServer {
317
273
  }
318
274
  const attachments = msg.attachmentsJSON;
319
275
  if (attachments) {
320
- m.attachments = (0, fs_1.fsForAttachments)(msgOnDisk, attachments, this.storages, this.cryptor, this.random, this.logError);
276
+ m.attachments = (0, fs_1.fsForAttachments)(msgOnDisk, attachments, getStorages, cryptor, random, logError);
321
277
  }
322
278
  return m;
323
279
  }
324
- suspendNetworkActivity() {
325
- this.inboxEvents.suspendNetworkActivity();
326
- }
327
- resumeNetworkActivity() {
328
- this.inboxEvents.resumeNetworkActivity();
329
- }
330
- get connectivityEvent$() {
331
- return this.inboxEvents.connectionEvent$;
332
- }
280
+ return {
281
+ connectivityEvent$: inboxEvents.connectionEvent$,
282
+ resumeNetworkActivity: inboxEvents.resumeNetworkActivity.bind(inboxEvents),
283
+ suspendNetworkActivity: inboxEvents.suspendNetworkActivity.bind(inboxEvents),
284
+ close,
285
+ makeCAP
286
+ };
333
287
  }
334
- exports.InboxOnServer = InboxOnServer;
335
- Object.freeze(InboxOnServer.prototype);
336
- Object.freeze(InboxOnServer);
337
288
  function checkServerAuthIfPresent(meta, { correspondent }) {
338
289
  // if sender authenticated to server, check that it matches address,
339
290
  // recovered from message decryption
@@ -2,14 +2,12 @@ import { ObjOnDisk } from '../../../lib-client/objs-on-disk/obj-on-disk';
2
2
  import { MsgMeta } from '../../../lib-common/service-api/asmail/retrieval';
3
3
  import { ObjSource } from 'xsp-files';
4
4
  import { MsgDownloader } from './msg-downloader';
5
- export type MsgKeyStatus = 'not-checked' | 'not-found' | 'fail' | 'ok';
6
5
  export interface ObjSize {
7
6
  header: number;
8
7
  segments: number;
9
8
  }
10
9
  export interface MsgStatus {
11
10
  msgId: string;
12
- keyStatus: MsgKeyStatus;
13
11
  onDisk: boolean;
14
12
  mainObjId: string;
15
13
  deliveryTS: number;
@@ -31,7 +29,4 @@ export declare class MsgOnDisk {
31
29
  getObjFile(objId: string): Promise<ObjOnDisk>;
32
30
  getMsgObj(objId: string): Promise<ObjSource>;
33
31
  get deliveryTS(): number;
34
- get keyStatus(): MsgKeyStatus;
35
- updateMsgKeyStatus(newStatus: Exclude<MsgKeyStatus, 'not-checked'>): Promise<void>;
36
- private saveStatusToDisk;
37
32
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2016 - 2019 3NSoft Inc.
3
+ Copyright (C) 2016 - 2019, 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
@@ -27,7 +27,6 @@ const STATUS_FNAME = 'status.json';
27
27
  function makeInitMsgStatus(msgId, meta) {
28
28
  return {
29
29
  msgId,
30
- keyStatus: 'not-checked',
31
30
  onDisk: false,
32
31
  mainObjId: meta.extMeta.objIds[0],
33
32
  deliveryTS: meta.deliveryStart
@@ -94,26 +93,6 @@ class MsgOnDisk {
94
93
  get deliveryTS() {
95
94
  return this.status.deliveryTS;
96
95
  }
97
- get keyStatus() {
98
- return this.status.keyStatus;
99
- }
100
- updateMsgKeyStatus(newStatus) {
101
- return this.syncProc.startOrChain(async () => {
102
- if (this.status.keyStatus === 'not-checked') {
103
- this.status.keyStatus = newStatus;
104
- }
105
- else if (this.status.keyStatus === newStatus) {
106
- return;
107
- }
108
- else {
109
- throw Error(`Message has key status ${this.status.keyStatus}, and can't be updated to ${newStatus}`);
110
- }
111
- await this.saveStatusToDisk();
112
- });
113
- }
114
- async saveStatusToDisk() {
115
- await writeJSON(this.msgFolderPath, STATUS_FNAME, this.status);
116
- }
117
96
  }
118
97
  exports.MsgOnDisk = MsgOnDisk;
119
98
  Object.freeze(MsgOnDisk.prototype);
@@ -90,7 +90,7 @@ class ASMail {
90
90
  const cachePath = this.inboxPathForUser(this.address);
91
91
  const inboxSyncedFS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(syncedFS, INBOX_DATA_FOLDER);
92
92
  const inboxLocalFS = await localFS.writableSubRoot(INBOX_DATA_FOLDER);
93
- this.inbox = await inbox_1.InboxOnServer.makeAndStart(cachePath, inboxSyncedFS, inboxLocalFS, {
93
+ this.inbox = await (0, inbox_1.makeInboxOnServer)(cachePath, inboxSyncedFS, inboxLocalFS, {
94
94
  address: this.address,
95
95
  cryptor: this.cryptor,
96
96
  random: this.random,
@@ -31,11 +31,10 @@ const buffer_utils_1 = require("../../lib-common/buffer-utils");
31
31
  var common_2 = require("./common");
32
32
  Object.defineProperty(exports, "KEY_USE", { enumerable: true, get: function () { return common_2.KEY_USE; } });
33
33
  const INTRO_KEYS_FOLDER = 'introductory-keys';
34
- const INTRO_KEY_ON_SERVER_FILE = `published-on-server.json`;
35
34
  async function makeKeyrings(cryptor, random, logger, fs, localFS, getSigner, pkeyOnServer) {
36
35
  const workLabel = cryptor_work_labels_1.cryptoWorkLabels.makeRandom('asmail');
37
36
  const introKeysFS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(fs, INTRO_KEYS_FOLDER);
38
- const publishedKeys = await (0, published_intro_key_1.makeHolderOfPublishedIntroKey)(await introKeysFS.writableFile(INTRO_KEY_ON_SERVER_FILE), getSigner, random, pkeyOnServer);
37
+ const publishedKeys = await (0, published_intro_key_1.makeHolderOfPublishedIntroKey)(introKeysFS, getSigner, random, pkeyOnServer);
39
38
  const peerKeys = await (0, peer_keys_1.makePeersKeyring)(fs, localFS, random, logger);
40
39
  async function generateIntroKeysToSendMsg(address, introPKeyFromServer) {
41
40
  if (introPKeyFromServer.alg !== ecma_nacl_1.box.JWK_ALG_NAME) {
@@ -2,9 +2,9 @@ import { ParamOnServer } from '../../../lib-client/asmail/service-config';
2
2
  import { AsyncRNG } from '../../../lib-common/rng-def';
3
3
  import { GetSigner } from '../../id-manager';
4
4
  import { JWKeyPair, MsgKeyRole } from '../common';
5
- type WritableFile = web3n.files.WritableFile;
5
+ type WritableFS = web3n.files.WritableFS;
6
6
  type IntroKeyCAP = web3n.keys.Keyrings['introKeyOnASMailServer'];
7
- export declare function makeHolderOfPublishedIntroKey(file: WritableFile, getSigner: GetSigner, random: AsyncRNG, pkeyOnServer: ParamOnServer<'init-pub-key'>): Promise<{
7
+ export declare function makeHolderOfPublishedIntroKey(introKeysFS: WritableFS, getSigner: GetSigner, random: AsyncRNG, pkeyOnServer: ParamOnServer<'init-pub-key'>): Promise<{
8
8
  startExpiryCheckProcess: () => void;
9
9
  close: () => Promise<void>;
10
10
  makeIntroKeyCAP: () => IntroKeyCAP;
@@ -17,53 +17,28 @@
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.makeHolderOfPublishedIntroKey = makeHolderOfPublishedIntroKey;
20
- const file_based_json_1 = require("../../../lib-client/xsp-fs/util/file-based-json");
20
+ const single_file_1 = require("../../../lib-common/dataset-sync/single-file");
21
21
  const jwkeys_1 = require("../../../lib-common/jwkeys");
22
22
  const common_1 = require("../common");
23
23
  const INTRO_KEY_VALIDITY = 31 * 24 * 60 * 60;
24
24
  const UPDATE_BEFORE_EXPIRY = 7 * 24 * 60 * 60;
25
- async function makeHolderOfPublishedIntroKey(file, getSigner, random, pkeyOnServer) {
25
+ const MAX_NUM_OF_PREV = 3;
26
+ const INTRO_KEY_ON_SERVER_FILE = `published-on-server.json`;
27
+ async function makeHolderOfPublishedIntroKey(introKeysFS, getSigner, random, pkeyOnServer) {
26
28
  var _a;
27
- let published = {
28
- previous: []
29
- };
30
- // XXX JsonFileProc is too little, in comparison to operations syn, and watching file
31
- const fileProc = new file_based_json_1.JsonFileProc(onFileEvent);
32
- async function onFileEvent(ev) {
33
- if (ev.src === 'local') {
34
- return;
35
- }
36
- switch (ev.type) {
37
- case 'file-change':
38
- await fileProc.order.startOrChain(() => absorbRemoteChanges());
39
- break;
40
- case 'removed':
41
- throw new Error(`Unexpected removal of file with parameter "anon-sender/invites"`);
42
- default:
43
- return;
44
- }
29
+ const keysFile = await (0, single_file_1.initSyncedFile)(introKeysFS, INTRO_KEY_ON_SERVER_FILE, newFile => newFile.writeJSON({ previous: [] }));
30
+ let keys;
31
+ async function setKeysFromFile() {
32
+ keys = await keysFile.readJSON();
45
33
  }
46
- function setFromJSON(json) {
47
- if (!Array.isArray(json.previous)) { // migration part
48
- json.previous = (json.previous ? [json.previous] : []);
49
- }
50
- published = json;
51
- }
52
- function toFileJSON() {
53
- return published;
54
- }
55
- // private toServerParam(): PKeyCertChain|undefined {
56
- // return published.current?.certs;
57
- // }
58
- async function absorbRemoteChanges() {
59
- // XXX
60
- // - check for changes: what is needed here from fileProc, and what is
61
- // generic in absorbing remote changes to refactor it into JsonFileProc
62
- // - absorb and sync, if needed: what can be in JsonFileProc
63
- // Code from pre-v.sync:
64
- // const { json } = await fileProc.get();
65
- // setFromJSON(json);
34
+ await setKeysFromFile();
35
+ const { changeProc, stopFileWatching, triggerUpload } = (0, single_file_1.watchAndApplyChangesFromOtherDevices)(keysFile, () => changeProc.startOrChain(setKeysFromFile), doOnConflict);
36
+ if (keysFile.isNew) {
37
+ await update();
66
38
  }
39
+ updateIfCurrentExpired();
40
+ let periodicExpiryCheck = setTimeout(updateIfCurrentExpired, UPDATE_BEFORE_EXPIRY * 1000 / 20);
41
+ (_a = periodicExpiryCheck.unref) === null || _a === void 0 ? void 0 : _a.call(periodicExpiryCheck);
67
42
  async function makeNewIntroKey() {
68
43
  const signer = await getSigner();
69
44
  const pair = await (0, common_1.generateKeyPair)(random);
@@ -76,24 +51,60 @@ async function makeHolderOfPublishedIntroKey(file, getSigner, random, pkeyOnServ
76
51
  return { pair, certs };
77
52
  }
78
53
  function update() {
79
- return fileProc.order.startOrChain(async () => {
54
+ return changeProc.startOrChain(async () => {
80
55
  const { certs, pair: keyPair } = await makeNewIntroKey();
81
56
  await pkeyOnServer.setOnServer(certs);
82
57
  retireCurrent(keyPair.createdAt);
83
- published.current = { keyPair, certs };
84
- await fileProc.save(toFileJSON(), false);
58
+ keys.current = { keyPair, certs };
59
+ await keysFile.writeJSON(keys);
60
+ triggerUpload();
85
61
  return certs;
86
62
  });
87
63
  }
88
64
  ;
89
65
  function retireCurrent(retiredAt) {
90
- if (!published.current) {
66
+ if (!keys.current) {
91
67
  return;
92
68
  }
93
- const current = published.current;
69
+ const current = keys.current;
94
70
  current.keyPair.retiredAt = retiredAt;
95
- published.previous.push(current.keyPair);
96
- published.current = undefined;
71
+ keys.previous.push(current.keyPair);
72
+ if (keys.previous.length > MAX_NUM_OF_PREV) {
73
+ keys.previous.splice(0, keys.previous.length - MAX_NUM_OF_PREV);
74
+ }
75
+ keys.current = undefined;
76
+ }
77
+ function doOnConflict() {
78
+ return changeProc.startOrChain(async () => {
79
+ var _a, _b, _c, _d, _e, _f;
80
+ const { remote } = await keysFile.v.sync.status(false);
81
+ if (!remote) {
82
+ return;
83
+ }
84
+ const onRemote = (await keysFile.v.readJSON({ remoteVersion: remote.latest })).json;
85
+ if (((_a = onRemote.current) === null || _a === void 0 ? void 0 : _a.keyPair.pkey.kid) === ((_b = keys.current) === null || _b === void 0 ? void 0 : _b.keyPair.pkey.kid)) {
86
+ await keysFile.v.sync.adoptRemote();
87
+ return;
88
+ }
89
+ const certsOnServer = await pkeyOnServer.getFromServer();
90
+ const currentOnServer = (certsOnServer ? (0, jwkeys_1.getPubKey)(certsOnServer.pkeyCert) : undefined);
91
+ if ((currentOnServer === null || currentOnServer === void 0 ? void 0 : currentOnServer.kid) === ((_c = keys.current) === null || _c === void 0 ? void 0 : _c.keyPair.pkey.kid)) {
92
+ await keysFile.v.sync.upload({ uploadVersion: remote.latest + 1 });
93
+ }
94
+ else if ((currentOnServer === null || currentOnServer === void 0 ? void 0 : currentOnServer.kid) === ((_d = onRemote.current) === null || _d === void 0 ? void 0 : _d.keyPair.pkey.kid)) {
95
+ await keysFile.v.sync.adoptRemote({ remoteVersion: remote.latest });
96
+ }
97
+ else {
98
+ await pkeyOnServer.setOnServer((_f = (_e = keys.current) === null || _e === void 0 ? void 0 : _e.certs) !== null && _f !== void 0 ? _f : null);
99
+ await keysFile.v.sync.upload({ uploadVersion: remote.latest + 1 });
100
+ }
101
+ })
102
+ .catch((exc) => {
103
+ if (exc.type === 'connect') {
104
+ // XXX need to have watcher for connection to complete upload
105
+ // Should this whole try-to-connect method be generic to reuse it?
106
+ }
107
+ });
97
108
  }
98
109
  /**
99
110
  * This looks for a published key with a given key id. If it is found, an
@@ -108,15 +119,15 @@ async function makeHolderOfPublishedIntroKey(file, getSigner, random, pkeyOnServ
108
119
  */
109
120
  function find(kid) {
110
121
  // check current key
111
- if (published.current
112
- && (published.current.keyPair.skey.kid === kid)) {
122
+ if (keys.current
123
+ && (keys.current.keyPair.skey.kid === kid)) {
113
124
  return {
114
125
  role: 'published_intro',
115
- pair: published.current.keyPair
126
+ pair: keys.current.keyPair
116
127
  };
117
128
  }
118
129
  // check previous key
119
- const pair = published.previous.find(({ skey }) => (skey.kid === kid));
130
+ const pair = keys.previous.find(({ skey }) => (skey.kid === kid));
120
131
  if (pair) {
121
132
  return {
122
133
  role: 'prev_published_intro',
@@ -137,24 +148,25 @@ async function makeHolderOfPublishedIntroKey(file, getSigner, random, pkeyOnServ
137
148
  }
138
149
  async function getCurrent() {
139
150
  var _a;
140
- const certs = (_a = published.current) === null || _a === void 0 ? void 0 : _a.certs;
151
+ const certs = (_a = keys.current) === null || _a === void 0 ? void 0 : _a.certs;
141
152
  return (certs ? certs : null);
142
153
  }
143
154
  async function removeCurrent() {
144
- if (!published.current) {
155
+ if (!keys.current) {
145
156
  return;
146
157
  }
147
- return fileProc.order.startOrChain(async () => {
158
+ return changeProc.startOrChain(async () => {
148
159
  await pkeyOnServer.setOnServer(null);
149
160
  retireCurrent(Math.floor(Date.now() / 1000));
161
+ await keysFile.writeJSON(keys);
162
+ triggerUpload();
150
163
  });
151
164
  }
152
- let periodicExpiryCheck = undefined;
153
- function startExpiryCheckProcess() {
154
- if (!published.current) {
165
+ function updateIfCurrentExpired() {
166
+ if (!keys.current) {
155
167
  return;
156
168
  }
157
- const { pkeyCert } = published.current.certs;
169
+ const { pkeyCert } = keys.current.certs;
158
170
  const expiryInSeconds = (0, jwkeys_1.getKeyCert)(pkeyCert).expiresAt;
159
171
  const now = Math.floor(Date.now() / 1000);
160
172
  if (expiryInSeconds < (now + UPDATE_BEFORE_EXPIRY)) {
@@ -162,26 +174,14 @@ async function makeHolderOfPublishedIntroKey(file, getSigner, random, pkeyOnServ
162
174
  }
163
175
  }
164
176
  async function close() {
177
+ stopFileWatching();
165
178
  if (periodicExpiryCheck) {
166
179
  clearTimeout(periodicExpiryCheck);
167
180
  periodicExpiryCheck = undefined;
168
181
  }
169
- await fileProc.close();
170
- }
171
- if (file.isNew) {
172
- await fileProc.start(file, toFileJSON);
173
- await update();
174
- }
175
- else {
176
- await fileProc.start(file, undefined);
177
- setFromJSON((await fileProc.get()).json);
178
- await absorbRemoteChanges();
179
- startExpiryCheckProcess();
180
- periodicExpiryCheck = setTimeout(startExpiryCheckProcess, UPDATE_BEFORE_EXPIRY * 1000 / 20);
181
- (_a = periodicExpiryCheck.unref) === null || _a === void 0 ? void 0 : _a.call(periodicExpiryCheck);
182
182
  }
183
183
  return {
184
- startExpiryCheckProcess,
184
+ startExpiryCheckProcess: updateIfCurrentExpired,
185
185
  close,
186
186
  makeIntroKeyCAP,
187
187
  find
@@ -5,6 +5,7 @@ import { SubscribingClient } from '../../lib-common/ipc/ws-ipc';
5
5
  type InboxException = web3n.asmail.InboxException;
6
6
  type Observer<T> = web3n.Observer<T>;
7
7
  export declare function makeMsgNotFoundException(msgId: string): InboxException;
8
+ export declare function makeFailToDecryptMsgException(msgId: string): InboxException;
8
9
  export declare function makeObjNotFoundException(msgId: string, objId: string): InboxException;
9
10
  export declare function makeMsgIsBrokenException(msgId: string): InboxException;
10
11
  export declare class MailRecipient extends ServiceUser {
@@ -18,6 +18,7 @@
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.MailRecipient = void 0;
20
20
  exports.makeMsgNotFoundException = makeMsgNotFoundException;
21
+ exports.makeFailToDecryptMsgException = makeFailToDecryptMsgException;
21
22
  exports.makeObjNotFoundException = makeObjNotFoundException;
22
23
  exports.makeMsgIsBrokenException = makeMsgIsBrokenException;
23
24
  const request_utils_1 = require("../request-utils");
@@ -37,6 +38,16 @@ function makeMsgNotFoundException(msgId) {
37
38
  };
38
39
  return exc;
39
40
  }
41
+ function makeFailToDecryptMsgException(msgId) {
42
+ const exc = {
43
+ runtimeException: true,
44
+ type: 'inbox',
45
+ msgId,
46
+ failToDecrypt: true,
47
+ stack: (0, runtime_1.getStackHere)(1)
48
+ };
49
+ return exc;
50
+ }
40
51
  function makeObjNotFoundException(msgId, objId) {
41
52
  const exc = {
42
53
  runtimeException: true,
@@ -36,7 +36,6 @@ const folder_node_serialization_1 = require("./folder-node-serialization");
36
36
  const attrs_1 = require("./attrs");
37
37
  const node_persistence_1 = require("./node-persistence");
38
38
  const assert_1 = require("../../lib-common/assert");
39
- const for_arrays_1 = require("./util/for-arrays");
40
39
  function jsonToInfoAndAttrs(json) {
41
40
  const folderInfo = {
42
41
  nodes: (0, json_utils_1.copy)(json.nodes)
@@ -870,7 +869,7 @@ class FolderNode extends node_in_fs_1.NodeInFS {
870
869
  async uploadRemovalOf(removedNodes) {
871
870
  const rmObjs = [];
872
871
  for (const node of removedNodes) {
873
- (0, for_arrays_1.appendArray)(rmObjs, await this.listRemovedInTreeToUploadRm(node));
872
+ appendArray(rmObjs, await this.listRemovedInTreeToUploadRm(node));
874
873
  }
875
874
  await this.uploadRemovalOfObjs(rmObjs);
876
875
  }
@@ -896,7 +895,7 @@ class FolderNode extends node_in_fs_1.NodeInFS {
896
895
  const src = await storage.getObjSrc(node.objId, versionBeforeRm, true);
897
896
  const folderContent = await FolderPersistance.readFolderContent(node.objId, node.key, src, storage.cryptor, storage.random);
898
897
  for (const child of Object.values(folderContent.nodes)) {
899
- (0, for_arrays_1.appendArray)(objs, await this.listRemovedInTreeToUploadRm(child));
898
+ appendArray(objs, await this.listRemovedInTreeToUploadRm(child));
900
899
  }
901
900
  return objs;
902
901
  }
@@ -1136,6 +1135,17 @@ class FolderNode extends node_in_fs_1.NodeInFS {
1136
1135
  exports.FolderNode = FolderNode;
1137
1136
  Object.freeze(FolderNode.prototype);
1138
1137
  Object.freeze(FolderNode);
1138
+ function appendArray(arr, elems) {
1139
+ if (elems !== undefined) {
1140
+ if (Array.isArray(elems)) {
1141
+ arr.push(...elems);
1142
+ }
1143
+ else {
1144
+ arr.push(elems);
1145
+ }
1146
+ }
1147
+ return arr;
1148
+ }
1139
1149
  const EMPTY_ARR = new Uint8Array(0);
1140
1150
  function noop() { }
1141
1151
  function checkFolderInfo(folderJson) {
@@ -403,7 +403,7 @@ function startDatasetSync(periodicCheckAndUploadOfData, millisBeforeNextRun, log
403
403
  // We pass this single process point out to wrap fn's with it, in an attempt to explicitly keeping it at the
404
404
  // boundary of incoming operations, either from fn calls, or from handling changes passed via fs events.
405
405
  const changeProc = new synced_1.SingleProc();
406
- function dbSyncCheckAndAction() {
406
+ function datasetCheckAndSyncAction() {
407
407
  changeProc.startOrChain(periodicCheckAndUploadOfData);
408
408
  }
409
409
  let interval = undefined;
@@ -411,7 +411,7 @@ function startDatasetSync(periodicCheckAndUploadOfData, millisBeforeNextRun, log
411
411
  if (interval !== undefined) {
412
412
  clearInterval(interval);
413
413
  }
414
- interval = setInterval(dbSyncCheckAndAction, millisBeforeNextRun()).unref();
414
+ interval = setInterval(datasetCheckAndSyncAction, millisBeforeNextRun()).unref();
415
415
  }
416
416
  resetSyncInterval();
417
417
  // XXX we should watch for reconnect events and run similar to start checks of possibly missed ops
@@ -0,0 +1,10 @@
1
+ import { SingleProc } from "../processes/synced";
2
+ type WritableFS = web3n.files.WritableFS;
3
+ type WritableFile = web3n.files.WritableFile;
4
+ export declare function initSyncedFile(syncedFS: WritableFS, fName: string, initNewFile: (newFile: WritableFile) => Promise<void>): Promise<WritableFile>;
5
+ export declare function watchAndApplyChangesFromOtherDevices(file: WritableFile, onChangeFromOtherDevices: (changeProc: SingleProc, file: WritableFile) => Promise<void>, onConflictingChangeFromOtherDevices: (changeProc: SingleProc, file: WritableFile) => Promise<void>): {
6
+ changeProc: SingleProc;
7
+ stopFileWatching: () => void;
8
+ triggerUpload: () => void;
9
+ };
10
+ export {};
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (C) 2026 3NSoft Inc.
4
+
5
+ This program is free software: you can redistribute it and/or modify it under
6
+ the terms of the GNU General Public License as published by the Free Software
7
+ Foundation, either version 3 of the License, or (at your option) any later
8
+ version.
9
+
10
+ This program is distributed in the hope that it will be useful, but
11
+ WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
+ See the GNU General Public License for more details.
14
+
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/>.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.initSyncedFile = initSyncedFile;
20
+ exports.watchAndApplyChangesFromOtherDevices = watchAndApplyChangesFromOtherDevices;
21
+ const synced_1 = require("../processes/synced");
22
+ async function initSyncedFile(syncedFS, fName, initNewFile) {
23
+ const file = await syncedFS.writableFile(fName);
24
+ if (file.isNew) {
25
+ await initNewFile(file);
26
+ await file.v.sync.upload();
27
+ await syncedFS.v.sync.upload('.');
28
+ }
29
+ // XXX add initial sync no-connection handling
30
+ return file;
31
+ }
32
+ function watchAndApplyChangesFromOtherDevices(file, onChangeFromOtherDevices, onConflictingChangeFromOtherDevices) {
33
+ const changeProc = new synced_1.SingleProc();
34
+ const stopFileWatching = file.watch({
35
+ next: ev => {
36
+ if (ev.type === 'remote-change') {
37
+ changeProc.startOrChain(async () => {
38
+ await file.v.sync.adoptRemote({ remoteVersion: ev.newRemoteVersion })
39
+ .catch((exc) => {
40
+ if (exc.type === 'connect') {
41
+ // XXX need to have watcher for connection to continue processing
42
+ }
43
+ });
44
+ });
45
+ }
46
+ else if ((ev.type === 'file-change') && (ev.src === 'sync')) {
47
+ onChangeFromOtherDevices(changeProc, file);
48
+ }
49
+ }
50
+ });
51
+ function triggerUpload() {
52
+ changeProc.startOrChain(async () => file.v.sync.upload())
53
+ .catch((exc) => {
54
+ if (exc.type === 'connect') {
55
+ // XXX need to have watcher for connection to complete upload
56
+ // Should this whole try-to-connect method be generic to reuse it?
57
+ }
58
+ });
59
+ }
60
+ file.v.sync.status()
61
+ .then(async ({ state }) => {
62
+ if (state === 'behind') {
63
+ await changeProc.startOrChain(() => file.v.sync.adoptRemote());
64
+ }
65
+ else if (state === 'conflicting') {
66
+ await onConflictingChangeFromOtherDevices(changeProc, file);
67
+ }
68
+ else if (state === 'unsynced') {
69
+ triggerUpload();
70
+ }
71
+ })
72
+ .catch(exc => {
73
+ // XXX need to have watcher for connection to continue processing
74
+ });
75
+ return { changeProc, stopFileWatching, triggerUpload };
76
+ }
77
+ Object.freeze(exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "core-3nweb-client-lib",
3
- "version": "0.49.7",
3
+ "version": "0.49.8",
4
4
  "description": "3NWeb client core library, embeddable into different environments",
5
5
  "main": "build/lib-index.js",
6
6
  "types": "build/lib-index.d.ts",
@@ -1 +0,0 @@
1
- export declare function appendArray<T>(arr: T[], elems: T[] | T | undefined): T[];
@@ -1,31 +0,0 @@
1
- "use strict";
2
- /*
3
- Copyright (C) 2022 3NSoft Inc.
4
-
5
- This program is free software: you can redistribute it and/or modify it under
6
- the terms of the GNU General Public License as published by the Free Software
7
- Foundation, either version 3 of the License, or (at your option) any later
8
- version.
9
-
10
- This program is distributed in the hope that it will be useful, but
11
- WITHOUT ANY WARRANTY; without even the implied warranty of
12
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
- See the GNU General Public License for more details.
14
-
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/>.
17
- */
18
- Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.appendArray = appendArray;
20
- function appendArray(arr, elems) {
21
- if (elems !== undefined) {
22
- if (Array.isArray(elems)) {
23
- arr.push(...elems);
24
- }
25
- else {
26
- arr.push(elems);
27
- }
28
- }
29
- return arr;
30
- }
31
- Object.freeze(exports);