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.
- package/build/api-defs/asmail.d.ts +1 -0
- package/build/core/asmail/inbox/index.d.ts +8 -31
- package/build/core/asmail/inbox/index.js +115 -164
- package/build/core/asmail/inbox/msg-on-disk.d.ts +0 -5
- package/build/core/asmail/inbox/msg-on-disk.js +1 -22
- package/build/core/asmail/index.js +1 -1
- package/build/core/keyring/index.js +1 -2
- package/build/core/keyring/introductory-keys/published-intro-key.d.ts +2 -2
- package/build/core/keyring/introductory-keys/published-intro-key.js +72 -72
- package/build/lib-client/asmail/recipient.d.ts +1 -0
- package/build/lib-client/asmail/recipient.js +11 -0
- package/build/lib-client/xsp-fs/folder-node.js +13 -3
- package/build/lib-common/dataset-sync/per-device-json-array-logs.js +2 -2
- package/build/lib-common/dataset-sync/single-file.d.ts +10 -0
- package/build/lib-common/dataset-sync/single-file.js +77 -0
- package/package.json +1 -1
- package/build/lib-client/xsp-fs/util/for-arrays.d.ts +0 -1
- package/build/lib-client/xsp-fs/util/for-arrays.js +0 -31
|
@@ -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
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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.
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
81
|
-
listMsgs
|
|
82
|
-
removeMsg
|
|
83
|
-
subscribe:
|
|
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 =
|
|
62
|
+
const promise = procs.latestTaskAtThisMoment(procId);
|
|
91
63
|
if (promise) {
|
|
92
64
|
return promise;
|
|
93
65
|
}
|
|
94
66
|
// start removal process
|
|
95
|
-
return
|
|
67
|
+
return procs.start(procId, (async () => {
|
|
96
68
|
await Promise.all([
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
76
|
+
cache.deleteMsg(msgId),
|
|
105
77
|
// XXX the following is suspicious, as it swallows other exceptions too
|
|
106
|
-
|
|
78
|
+
msgReceiver.removeMsg(msgId).catch(() => { })
|
|
107
79
|
]);
|
|
108
80
|
}
|
|
109
|
-
async msgFromDiskOrDownload(msgId) {
|
|
110
|
-
const msgOnDisk = await
|
|
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
|
|
115
|
-
return await
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
129
|
+
await index.add(msgInfo, decrInfo);
|
|
153
130
|
await Promise.all([
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
161
|
+
await r.correspondents.saveParamsForSendingTo(address, sendingParams);
|
|
191
162
|
}
|
|
192
|
-
|
|
193
|
-
const
|
|
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
|
-
|
|
204
|
-
|
|
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
|
|
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
|
|
186
|
+
return index.listMsgs(fromTS);
|
|
216
187
|
}
|
|
217
|
-
const indexedMsgs = await
|
|
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 =>
|
|
198
|
+
await Promise.all(msgIds.map(msgId => startCachingAndAddKeyToIndex(msgId)
|
|
228
199
|
.catch(async (exc) => {
|
|
229
|
-
await
|
|
200
|
+
await logError(exc, `Failed to start caching message ${msgId}`);
|
|
230
201
|
})));
|
|
231
|
-
return
|
|
202
|
+
return index.listMsgs(fromTS);
|
|
232
203
|
});
|
|
233
204
|
}
|
|
234
|
-
async listNewMsgs(fromTS) {
|
|
235
|
-
const msgIds = await
|
|
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
|
|
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 =>
|
|
219
|
+
await Promise.all(msgIds.map(msgId => startCachingAndAddKeyToIndex(msgId)));
|
|
249
220
|
// get info's from index, focusing on specific messages only
|
|
250
|
-
return (await
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
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
|
-
|
|
236
|
+
let msgKey = await index.getKeyFor(msgId, meta.deliveryCompletion);
|
|
287
237
|
if (!msgKey) {
|
|
288
|
-
|
|
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,
|
|
246
|
+
msg = await (0, opener_1.openMsg)(msgId, mainObjId, mainObj, msgKey.mainObjHeaderOfs, msgKey.msgKey, cryptor);
|
|
291
247
|
msg.setMsgKeyRole(msgKey.msgKeyRole);
|
|
292
|
-
return
|
|
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,
|
|
276
|
+
m.attachments = (0, fs_1.fsForAttachments)(msgOnDisk, attachments, getStorages, cryptor, random, logError);
|
|
321
277
|
}
|
|
322
278
|
return m;
|
|
323
279
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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.
|
|
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)(
|
|
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
|
|
5
|
+
type WritableFS = web3n.files.WritableFS;
|
|
6
6
|
type IntroKeyCAP = web3n.keys.Keyrings['introKeyOnASMailServer'];
|
|
7
|
-
export declare function makeHolderOfPublishedIntroKey(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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
|
-
|
|
84
|
-
await
|
|
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 (!
|
|
66
|
+
if (!keys.current) {
|
|
91
67
|
return;
|
|
92
68
|
}
|
|
93
|
-
const current =
|
|
69
|
+
const current = keys.current;
|
|
94
70
|
current.keyPair.retiredAt = retiredAt;
|
|
95
|
-
|
|
96
|
-
|
|
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 (
|
|
112
|
-
&& (
|
|
122
|
+
if (keys.current
|
|
123
|
+
&& (keys.current.keyPair.skey.kid === kid)) {
|
|
113
124
|
return {
|
|
114
125
|
role: 'published_intro',
|
|
115
|
-
pair:
|
|
126
|
+
pair: keys.current.keyPair
|
|
116
127
|
};
|
|
117
128
|
}
|
|
118
129
|
// check previous key
|
|
119
|
-
const pair =
|
|
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 =
|
|
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 (!
|
|
155
|
+
if (!keys.current) {
|
|
145
156
|
return;
|
|
146
157
|
}
|
|
147
|
-
return
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
if (!published.current) {
|
|
165
|
+
function updateIfCurrentExpired() {
|
|
166
|
+
if (!keys.current) {
|
|
155
167
|
return;
|
|
156
168
|
}
|
|
157
|
-
const { pkeyCert } =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 +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);
|