core-3nweb-client-lib 0.49.6 → 0.49.7
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/files.d.ts +11 -5
- package/build/core/asmail/delivery/common.d.ts +8 -28
- package/build/core/asmail/delivery/common.js +1 -47
- package/build/core/asmail/delivery/index.js +3 -3
- package/build/core/asmail/delivery/per-recipient-wip.js +3 -2
- package/build/core/asmail/inbox/index.d.ts +1 -2
- package/build/core/asmail/inbox/index.js +14 -15
- package/build/core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.d.ts +11 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.js +25 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.d.ts +33 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.js +72 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.d.ts +28 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.js +477 -0
- package/build/core/asmail/inbox/msg-indexing/index.d.ts +12 -26
- package/build/core/asmail/inbox/msg-indexing/index.js +47 -68
- package/build/core/asmail/inbox/msg-on-disk.js +1 -2
- package/build/core/asmail/index.js +8 -7
- package/build/core/asmail/msg/common.d.ts +0 -1
- package/build/core/asmail/msg/common.js +1 -1
- package/build/core/index.js +7 -4
- package/build/core/keyring/common.d.ts +1 -0
- package/build/core/keyring/common.js +2 -1
- package/build/core/keyring/index.d.ts +7 -44
- package/build/core/keyring/index.js +89 -222
- package/build/core/keyring/introductory-keys/published-intro-key.d.ts +17 -0
- package/build/core/keyring/introductory-keys/published-intro-key.js +190 -0
- package/build/core/keyring/peer-keys/dataset/v1/json-files.d.ts +105 -0
- package/build/core/keyring/peer-keys/dataset/v1/json-files.js +89 -0
- package/build/core/keyring/peer-keys/dataset/v2/logs.d.ts +43 -0
- package/build/core/keyring/peer-keys/dataset/v2/logs.js +116 -0
- package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.d.ts +73 -0
- package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.js +543 -0
- package/build/core/keyring/peer-keys/index.d.ts +24 -0
- package/build/core/keyring/peer-keys/index.js +208 -0
- package/build/core/storage/synced/obj-files.js +10 -3
- package/build/core/storage/synced/obj-status.d.ts +1 -1
- package/build/core/storage/synced/obj-status.js +8 -2
- package/build/core/storage/synced/storage.d.ts +4 -4
- package/build/core/storage/system-folders/apps-data.d.ts +0 -1
- package/build/core/storage/system-folders/apps-data.js +2 -6
- package/build/lib-client/fs-utils/fs-sync-utils.d.ts +1 -1
- package/build/lib-client/fs-utils/fs-sync-utils.js +70 -13
- package/build/lib-client/xsp-fs/fs.js +3 -1
- package/build/lib-client/xsp-fs/node-in-fs.d.ts +1 -0
- package/build/lib-client/xsp-fs/node-in-fs.js +7 -2
- package/build/lib-client/xsp-fs/node-persistence.js +1 -3
- package/build/lib-client/xsp-fs/xsp-payload-v2.js +1 -2
- package/build/lib-common/dataset-sync/per-device-json-array-logs.d.ts +58 -0
- package/build/lib-common/dataset-sync/per-device-json-array-logs.js +432 -0
- package/build/lib-common/number-line.d.ts +10 -0
- package/build/lib-common/number-line.js +66 -0
- package/build/lib-common/objs-on-disk/obj-version-file.d.ts +1 -1
- package/build/lib-common/objs-on-disk/obj-version-file.js +3 -3
- package/build/lib-common/rng-def.d.ts +1 -0
- package/build/lib-common/rng-def.js +8 -0
- package/build/lib-sqlite-on-3nstorage/for-sqlite.d.ts +38 -0
- package/build/lib-sqlite-on-3nstorage/for-sqlite.js +162 -0
- package/build/lib-sqlite-on-3nstorage/index.d.ts +18 -5
- package/build/lib-sqlite-on-3nstorage/index.js +49 -26
- package/build/tests/caps-api/file-sink-checks/splice.js +72 -1
- package/build/tests/caps-api/fs-checks/sync-on-one-dev/upload.js +27 -1
- package/build/tests/caps-api/fs-checks/sync-with-two-devs/update-propagation.js +12 -3
- package/build/tests/computer.3nweb.core/inbox/msg-indexing.js +16 -12
- package/build/tests/computer.3nweb.core/keyrings.js +113 -28
- package/build/tests/jasmine.js +2 -2
- package/build/tests/units/number-line.js +9 -9
- package/package.json +2 -1
- package/build/core/asmail/inbox/msg-indexing/logs-n-entries.d.ts +0 -52
- package/build/core/asmail/inbox/msg-indexing/logs-n-entries.js +0 -333
- package/build/core/asmail/inbox/msg-indexing/sql-indexing.d.ts +0 -4
- package/build/core/asmail/inbox/msg-indexing/sql-indexing.js +0 -319
- package/build/core/keyring/correspondent-keys.d.ts +0 -149
- package/build/core/keyring/correspondent-keys.js +0 -403
- package/build/core/keyring/id-to-email-map.d.ts +0 -38
- package/build/core/keyring/id-to-email-map.js +0 -105
- package/build/core/keyring/keyring-storage.d.ts +0 -9
- package/build/core/keyring/keyring-storage.js +0 -54
- package/build/core/keyring/published-intro-key.d.ts +0 -45
- package/build/core/keyring/published-intro-key.js +0 -200
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
Copyright 2025 3NSoft Inc.
|
|
3
|
+
Copyright 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
|
|
@@ -16,49 +16,134 @@
|
|
|
16
16
|
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
const peer_keys_1 = require("../../core/keyring/peer-keys");
|
|
20
|
+
const fs_sync_utils_1 = require("../../lib-client/fs-utils/fs-sync-utils");
|
|
19
21
|
const jasmine_utils_1 = require("../libs-for-tests/jasmine-utils");
|
|
20
22
|
const setups_1 = require("../libs-for-tests/setups");
|
|
21
23
|
const test_utils_1 = require("./test-utils");
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const random_node_1 = require("../../lib-common-on-node/random-node");
|
|
25
|
+
const inspector_1 = require("inspector");
|
|
26
|
+
const common_1 = require("../../core/keyring/common");
|
|
27
|
+
const canonical_address_1 = require("../../lib-common/canonical-address");
|
|
28
|
+
const bytes_equal_1 = require("../libs-for-tests/bytes-equal");
|
|
29
|
+
const logger = {
|
|
30
|
+
appLog: async () => { },
|
|
31
|
+
logError: async (err, msg) => {
|
|
32
|
+
inspector_1.console.error(`\n --- test logError called with ---\nMessage: ${msg}\nError:`, err);
|
|
33
|
+
},
|
|
34
|
+
logWarning: async (msg, err) => {
|
|
35
|
+
inspector_1.console.error(`\n --- test logWarning called with ---\nMessage: ${msg}\nError:`, err);
|
|
36
|
+
},
|
|
37
|
+
recordUnhandledRejectionsInProcess: () => { }
|
|
28
38
|
};
|
|
39
|
+
async function logErrorInTest(err, msg) {
|
|
40
|
+
inspector_1.console.error(`\n --- test logError callled with ---\nMessage: ${msg}\nError:`, err);
|
|
41
|
+
}
|
|
29
42
|
describe('ASMail keyring', () => {
|
|
30
43
|
const baseSetup = (0, setups_1.setupWithUsers)();
|
|
31
44
|
const testFolder = `keyring-test`;
|
|
32
|
-
let keyring;
|
|
33
45
|
const { fsSetup: setup, setupDevsAndFSs } = (0, test_utils_1.makeSetupWithTwoDevsFSs)(testFolder);
|
|
34
46
|
(0, jasmine_utils_1.beforeAllWithTimeoutLog)(async () => {
|
|
35
47
|
await setupDevsAndFSs(baseSetup);
|
|
36
48
|
}, 20000);
|
|
37
|
-
beforeEach(async () => {
|
|
38
|
-
const fs = setup.dev1FS();
|
|
39
|
-
// keyring = await makeAndKeyRing(cryptor, fs, {
|
|
40
|
-
// find: kid => { throw Error(`publishedKeys.find() mock`); },
|
|
41
|
-
// update: async () => { throw Error(`publishedKeys.update() mock`); }
|
|
42
|
-
// });
|
|
43
|
-
});
|
|
44
49
|
(0, jasmine_utils_1.afterEachCond)(async () => {
|
|
45
50
|
if (!setup.isUp) {
|
|
46
51
|
return;
|
|
47
52
|
}
|
|
48
53
|
await setup.resetFS();
|
|
49
54
|
});
|
|
50
|
-
(0, jasmine_utils_1.itCond)(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
(0, jasmine_utils_1.itCond)(`peers keyring supports key pair operations for a full messaging chain from intro`, async () => {
|
|
56
|
+
const dev1FS = setup.dev1FS();
|
|
57
|
+
const dev1LocalFS = setup.dev1LocalFS();
|
|
58
|
+
// run different peers' keyrings from different folders
|
|
59
|
+
const peer1 = `keyring tester 1 @rock.cafe`;
|
|
60
|
+
const peer2 = `keyring tester 2 @rock.cafe`;
|
|
61
|
+
const p1FS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(dev1FS, peer1);
|
|
62
|
+
const p1LocalFS = await dev1LocalFS.writableSubRoot(`local fs for ${peer1}`);
|
|
63
|
+
const p2FS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(dev1FS, peer2);
|
|
64
|
+
const p2LocalFS = await dev1LocalFS.writableSubRoot(`local fs for ${peer2}`);
|
|
65
|
+
const peerKeys1 = await (0, peer_keys_1.makePeersKeyring)(p1FS, p1LocalFS, random_node_1.bytes, logger);
|
|
66
|
+
const peerKeys2 = await (0, peer_keys_1.makePeersKeyring)(p2FS, p2LocalFS, random_node_1.bytes, logger);
|
|
67
|
+
try {
|
|
68
|
+
// -----------------------------------
|
|
69
|
+
// Message 1: peer1 to peer2, peers initially don't know each other (don't have established key pairs)
|
|
70
|
+
// -----------------------------------
|
|
71
|
+
// peer1 somehow gets intro key for peer2
|
|
72
|
+
const p2IntroPair = await (0, common_1.generateKeyPair)(random_node_1.bytes);
|
|
73
|
+
// peer1, to send first message with introductory pair, generates and save suggested crypto
|
|
74
|
+
let suggestedCrypto = await peerKeys1.suggestPairForPeerIntroKey(peer2, p2IntroPair.pkey);
|
|
75
|
+
expect(peerKeys1.needIntroKeyFor(peer2)).toBeTrue();
|
|
76
|
+
expect(suggestedCrypto.senderKid).toBe(p2IntroPair.pkey.kid);
|
|
77
|
+
// peer2 saves suggested crypto from decrypted message, crypto based on introductory key
|
|
78
|
+
expect(peerKeys2.needIntroKeyFor(peer1)).toBeTrue();
|
|
79
|
+
await peerKeys2.absorbNextPairSuggestedByPeer(peer1, suggestedCrypto, p2IntroPair);
|
|
80
|
+
const estPair1 = suggestedCrypto;
|
|
81
|
+
expect(peerKeys2.needIntroKeyFor(peer1)).toBeFalse();
|
|
82
|
+
// -----------------------------------
|
|
83
|
+
// Message 2: peer2 to peer1
|
|
84
|
+
// -----------------------------------
|
|
85
|
+
// peer2, to send reply, generates and saves suggested crypto
|
|
86
|
+
let forSending = await peerKeys2.getSendingCryptoWithinEstablishedPair((0, canonical_address_1.toCanonicalAddress)(peer1));
|
|
87
|
+
expect(estPair1.pids).toContain(forSending.currentPair.pid);
|
|
88
|
+
expect(forSending.nextMsgCrypto).withContext(`next crypto is generated asap to jump off introductory key`).toBeDefined();
|
|
89
|
+
// peer1, when message is received with pair id (pid), finds keys to decrypt message
|
|
90
|
+
let decr = peerKeys1.findEstablishedReceptionPairs(forSending.currentPair.pid);
|
|
91
|
+
expect(decr).toBeDefined();
|
|
92
|
+
expect(decr[0].peerCAddr).toBe((0, canonical_address_1.toCanonicalAddress)(peer2));
|
|
93
|
+
expect(decr[0].ratchetStage).toBe('suggested');
|
|
94
|
+
expect((0, bytes_equal_1.bytesEqual)(forSending.msgMasterKey, decr[0].msgMasterKey)).toBeTrue();
|
|
95
|
+
// peer1 marks found crypto pair as being in use
|
|
96
|
+
await peerKeys1.markPairAsInUse(decr[0].peerCAddr, decr[0].peerKId, decr[0].recipientKId);
|
|
97
|
+
// peer1 saves next crypto from decrypted message
|
|
98
|
+
expect(peerKeys1.needIntroKeyFor(peer2)).toBeTrue();
|
|
99
|
+
await peerKeys1.absorbNextPairSuggestedByPeer(peer2, forSending.nextMsgCrypto);
|
|
100
|
+
const estPair2 = forSending.nextMsgCrypto;
|
|
101
|
+
expect(peerKeys1.needIntroKeyFor(peer2)).toBeFalse();
|
|
102
|
+
// -----------------------------------
|
|
103
|
+
// Message 3: peer1 to peer2
|
|
104
|
+
// -----------------------------------
|
|
105
|
+
// peer1 gets crypto to send to peer2
|
|
106
|
+
forSending = await peerKeys1.getSendingCryptoWithinEstablishedPair((0, canonical_address_1.toCanonicalAddress)(peer2));
|
|
107
|
+
expect(estPair2.pids).toContain(forSending.currentPair.pid);
|
|
108
|
+
expect(forSending.nextMsgCrypto).withContext(`attempt to rotate pairs will come after some period of time`).toBeUndefined();
|
|
109
|
+
// peer2 finds pair to decrypt message
|
|
110
|
+
decr = peerKeys2.findEstablishedReceptionPairs(forSending.currentPair.pid);
|
|
111
|
+
expect(decr).toBeDefined();
|
|
112
|
+
expect(decr[0].peerCAddr).toBe((0, canonical_address_1.toCanonicalAddress)(peer1));
|
|
113
|
+
expect(decr[0].ratchetStage).toBe('suggested');
|
|
114
|
+
expect((0, bytes_equal_1.bytesEqual)(forSending.msgMasterKey, decr[0].msgMasterKey)).toBeTrue();
|
|
115
|
+
// peer2 marks found crypto pair as being in use
|
|
116
|
+
await peerKeys2.markPairAsInUse(decr[0].peerCAddr, decr[0].peerKId, decr[0].recipientKId);
|
|
117
|
+
// -----------------------------------
|
|
118
|
+
// Message 4: peer2 to peer1
|
|
119
|
+
// -----------------------------------
|
|
120
|
+
// peer2 gets crypto to send to peer1
|
|
121
|
+
forSending = await peerKeys2.getSendingCryptoWithinEstablishedPair((0, canonical_address_1.toCanonicalAddress)(peer1));
|
|
122
|
+
expect(estPair1.pids).toContain(forSending.currentPair.pid);
|
|
123
|
+
expect(forSending.nextMsgCrypto).withContext(`attempt to rotate pairs will come after some period of time`).toBeUndefined();
|
|
124
|
+
// peer1, when message is received with pair id (pid), finds keys to decrypt message
|
|
125
|
+
decr = peerKeys1.findEstablishedReceptionPairs(forSending.currentPair.pid);
|
|
126
|
+
expect(decr).toBeDefined();
|
|
127
|
+
expect(decr[0].peerCAddr).toBe((0, canonical_address_1.toCanonicalAddress)(peer2));
|
|
128
|
+
expect(decr[0].ratchetStage).toBe('in_use');
|
|
129
|
+
expect((0, bytes_equal_1.bytesEqual)(forSending.msgMasterKey, decr[0].msgMasterKey)).toBeTrue();
|
|
130
|
+
// -----------------------------------
|
|
131
|
+
// Message 5: peer1 to peer2
|
|
132
|
+
// -----------------------------------
|
|
133
|
+
// peer1 gets crypto to send to peer2
|
|
134
|
+
forSending = await peerKeys1.getSendingCryptoWithinEstablishedPair((0, canonical_address_1.toCanonicalAddress)(peer2));
|
|
135
|
+
expect(estPair2.pids).toContain(forSending.currentPair.pid);
|
|
136
|
+
expect(forSending.nextMsgCrypto).withContext(`attempt to rotate pairs will come after some period of time`).toBeUndefined();
|
|
137
|
+
// peer2 finds pair to decrypt message
|
|
138
|
+
decr = peerKeys2.findEstablishedReceptionPairs(forSending.currentPair.pid);
|
|
139
|
+
expect(decr).toBeDefined();
|
|
140
|
+
expect(decr[0].peerCAddr).toBe((0, canonical_address_1.toCanonicalAddress)(peer1));
|
|
141
|
+
expect(decr[0].ratchetStage).toBe('in_use');
|
|
142
|
+
expect((0, bytes_equal_1.bytesEqual)(forSending.msgMasterKey, decr[0].msgMasterKey)).toBeTrue();
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
await peerKeys1.close().catch(exc => logErrorInTest(exc));
|
|
146
|
+
await peerKeys2.close().catch(exc => logErrorInTest(exc));
|
|
147
|
+
}
|
|
63
148
|
}, undefined, setup);
|
|
64
149
|
});
|
package/build/tests/jasmine.js
CHANGED
|
@@ -34,9 +34,9 @@ const ALL_SPECS = [
|
|
|
34
34
|
'caps-api/mailerid.js',
|
|
35
35
|
'caps-api/storage.js',
|
|
36
36
|
'caps-api/asmail.js',
|
|
37
|
-
|
|
37
|
+
'computer.3nweb.core/keyrings.js',
|
|
38
38
|
'computer.3nweb.core/id-manager.js',
|
|
39
|
-
|
|
39
|
+
'computer.3nweb.core/inbox/*.js'
|
|
40
40
|
];
|
|
41
41
|
jas.loadConfig({
|
|
42
42
|
spec_dir: 'build/tests',
|
|
@@ -16,48 +16,48 @@
|
|
|
16
16
|
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
const common_1 = require("../../core/asmail/delivery/common");
|
|
20
19
|
const json_utils_1 = require("../../lib-common/json-utils");
|
|
20
|
+
const number_line_1 = require("../../lib-common/number-line");
|
|
21
21
|
describe(`Function addToNumberLineSegments`, () => {
|
|
22
22
|
it(`inserts number to empty segments array`, () => {
|
|
23
23
|
const segments = [];
|
|
24
24
|
const n = 1;
|
|
25
|
-
(0,
|
|
25
|
+
(0, number_line_1.addToNumberLineSegments)(segments, n);
|
|
26
26
|
expect((0, json_utils_1.deepEqual)(segments, [[n, n]])).toBeTruthy();
|
|
27
27
|
});
|
|
28
28
|
it(`expands existing segment on lower side`, () => {
|
|
29
29
|
let segments = [[1, 1], [4, 4]];
|
|
30
|
-
(0,
|
|
30
|
+
(0, number_line_1.addToNumberLineSegments)(segments, 3);
|
|
31
31
|
expect((0, json_utils_1.deepEqual)(segments, [[1, 1], [3, 4]])).toBeTruthy();
|
|
32
32
|
});
|
|
33
33
|
it(`expands existing segment on higher side`, () => {
|
|
34
34
|
const segments = [[1, 1], [4, 4]];
|
|
35
|
-
(0,
|
|
35
|
+
(0, number_line_1.addToNumberLineSegments)(segments, 2);
|
|
36
36
|
expect((0, json_utils_1.deepEqual)(segments, [[1, 2], [4, 4]])).toBeTruthy();
|
|
37
37
|
});
|
|
38
38
|
it(`expands existing segment and merges it with an overlapping one`, () => {
|
|
39
39
|
const segments = [[1, 2], [4, 4]];
|
|
40
|
-
(0,
|
|
40
|
+
(0, number_line_1.addToNumberLineSegments)(segments, 3);
|
|
41
41
|
expect((0, json_utils_1.deepEqual)(segments, [[1, 4]])).toBeTruthy();
|
|
42
42
|
});
|
|
43
43
|
it(`changes nothing when number is already in an existing segment`, () => {
|
|
44
44
|
const segments = [[1, 2], [4, 4]];
|
|
45
|
-
(0,
|
|
45
|
+
(0, number_line_1.addToNumberLineSegments)(segments, 2);
|
|
46
46
|
expect((0, json_utils_1.deepEqual)(segments, [[1, 2], [4, 4]])).toBeTruthy();
|
|
47
47
|
});
|
|
48
48
|
it(`adds new segment on a lower side`, () => {
|
|
49
49
|
const segments = [[4, 6], [10, 12]];
|
|
50
|
-
(0,
|
|
50
|
+
(0, number_line_1.addToNumberLineSegments)(segments, 2);
|
|
51
51
|
expect((0, json_utils_1.deepEqual)(segments, [[2, 2], [4, 6], [10, 12]])).toBeTruthy();
|
|
52
52
|
});
|
|
53
53
|
it(`adds new segment in the middle`, () => {
|
|
54
54
|
const segments = [[4, 6], [10, 12]];
|
|
55
|
-
(0,
|
|
55
|
+
(0, number_line_1.addToNumberLineSegments)(segments, 8);
|
|
56
56
|
expect((0, json_utils_1.deepEqual)(segments, [[4, 6], [8, 8], [10, 12]])).toBeTruthy();
|
|
57
57
|
});
|
|
58
58
|
it(`adds new segment on a higher side`, () => {
|
|
59
59
|
const segments = [[4, 6], [10, 12]];
|
|
60
|
-
(0,
|
|
60
|
+
(0, number_line_1.addToNumberLineSegments)(segments, 15);
|
|
61
61
|
expect((0, json_utils_1.deepEqual)(segments, [[4, 6], [10, 12], [15, 15]])).toBeTruthy();
|
|
62
62
|
});
|
|
63
63
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core-3nweb-client-lib",
|
|
3
|
-
"version": "0.49.
|
|
3
|
+
"version": "0.49.7",
|
|
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",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"@types/punycode": "^2.1.1",
|
|
49
49
|
"@types/ws": "^8.18.1",
|
|
50
50
|
"jasmine": "^5.13.0",
|
|
51
|
+
"lodash": "^4.18.1",
|
|
51
52
|
"napi-nacl": "^1.2.1",
|
|
52
53
|
"protobufjs-cli": "^1.0.2",
|
|
53
54
|
"spec-3nweb-server": "^1.8.6",
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { MsgKeyInfo, MsgKeyRole } from '../../../keyring';
|
|
2
|
-
type WritableFS = web3n.files.WritableFS;
|
|
3
|
-
type MsgInfo = web3n.asmail.MsgInfo;
|
|
4
|
-
interface MsgOpenedLog {
|
|
5
|
-
msgState: 'opened';
|
|
6
|
-
msgId: string;
|
|
7
|
-
msgType: string;
|
|
8
|
-
deliveryTS: number;
|
|
9
|
-
keyB64: string;
|
|
10
|
-
keyStatus: MsgKeyRole;
|
|
11
|
-
mainObjHeaderOfs: number;
|
|
12
|
-
removeAfter?: number;
|
|
13
|
-
}
|
|
14
|
-
interface MsgRemovedLog {
|
|
15
|
-
msgState: 'removed';
|
|
16
|
-
msgId: string;
|
|
17
|
-
deliveryTS: number;
|
|
18
|
-
}
|
|
19
|
-
type MsgLog = MsgOpenedLog | MsgRemovedLog;
|
|
20
|
-
export interface MsgKey {
|
|
21
|
-
msgKey: Uint8Array;
|
|
22
|
-
msgKeyRole: MsgKeyRole;
|
|
23
|
-
mainObjHeaderOfs: number;
|
|
24
|
-
}
|
|
25
|
-
export interface IndexedRecords {
|
|
26
|
-
add(msgInfo: MsgInfo, decrInfo: MsgKeyInfo, removeAfter: number): Promise<void>;
|
|
27
|
-
remove(msgId: string): Promise<number | undefined>;
|
|
28
|
-
listMsgs(fromTS: number | undefined): Promise<MsgInfo[]>;
|
|
29
|
-
getKeyFor(msgId: string, deliveryTS: number): Promise<MsgKey | undefined>;
|
|
30
|
-
msgExists(msgInfo: MsgInfo): Promise<boolean>;
|
|
31
|
-
stopSyncing(): void;
|
|
32
|
-
}
|
|
33
|
-
export declare function makeJsonBasedIndexedRecords(logs: MsgLogs): IndexedRecords;
|
|
34
|
-
export declare class MsgLogs {
|
|
35
|
-
private readonly fs;
|
|
36
|
-
private latest;
|
|
37
|
-
private fileTSs;
|
|
38
|
-
private readonly changeProc;
|
|
39
|
-
private constructor();
|
|
40
|
-
static makeAndStartSyncing(logsFS: WritableFS): Promise<MsgLogs>;
|
|
41
|
-
stopSyncing(): void;
|
|
42
|
-
add(msgInfo: MsgInfo, decrInfo: MsgKeyInfo, removeAfter: number | undefined): Promise<void>;
|
|
43
|
-
private updateLogsFile;
|
|
44
|
-
getLogsFromFile(fileTS: number | undefined): Promise<MsgLog[] | undefined>;
|
|
45
|
-
private fileTSforMsgTS;
|
|
46
|
-
remove(msgId: string, deliveryTS: number): Promise<void>;
|
|
47
|
-
private scheduleRemovalOfMsgOpenedLog;
|
|
48
|
-
getMsgOpenedLog(msgId: string, deliveryTS: number): Promise<MsgOpenedLog | undefined>;
|
|
49
|
-
getLogFileTSs(): number[];
|
|
50
|
-
getLatestMsgLogs(): MsgLog[];
|
|
51
|
-
}
|
|
52
|
-
export {};
|
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
Copyright (C) 2016 - 2020, 2023 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.MsgLogs = void 0;
|
|
20
|
-
exports.makeJsonBasedIndexedRecords = makeJsonBasedIndexedRecords;
|
|
21
|
-
const synced_1 = require("../../../../lib-common/processes/synced");
|
|
22
|
-
const buffer_utils_1 = require("../../../../lib-common/buffer-utils");
|
|
23
|
-
const timed_cache_1 = require("../../../../lib-common/timed-cache");
|
|
24
|
-
const error_1 = require("../../../../lib-common/exceptions/error");
|
|
25
|
-
const LIMIT_RECORDS_PER_FILE = 200;
|
|
26
|
-
const LATEST_LOG = 'latest.json';
|
|
27
|
-
const LOG_EXT = '.json';
|
|
28
|
-
const INDEX_FNAME_REGEXP = /^\d+\.json$/;
|
|
29
|
-
function logsFileName(fileTS) {
|
|
30
|
-
return (fileTS ? LATEST_LOG : `${fileTS}${LOG_EXT}`);
|
|
31
|
-
}
|
|
32
|
-
function fileTSOrderComparator(a, b) {
|
|
33
|
-
return (a - b);
|
|
34
|
-
}
|
|
35
|
-
function insertInto(records, rec) {
|
|
36
|
-
if (records.length === 0) {
|
|
37
|
-
records.push(rec);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const ts = rec.deliveryTS;
|
|
41
|
-
for (let i = (records.length - 1); i >= 0; i -= 1) {
|
|
42
|
-
if (records[i].deliveryTS <= ts) {
|
|
43
|
-
records.splice(i + 1, 0, rec);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
records.splice(0, 0, rec);
|
|
48
|
-
}
|
|
49
|
-
function removeMsgOpenedLogFrom(logs, msgId) {
|
|
50
|
-
const indToRm = logs.findIndex(log => ((log.msgId === msgId) && (log.msgState === 'opened')));
|
|
51
|
-
if (indToRm > -1) {
|
|
52
|
-
logs.splice(indToRm, 1);
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* This is a catch callback, which returns undefined on file(folder) not found
|
|
61
|
-
* exception, and re-throws all other exceptions/errors.
|
|
62
|
-
*/
|
|
63
|
-
function notFoundOrReThrow(exc) {
|
|
64
|
-
if (!exc.notFound) {
|
|
65
|
-
throw exc;
|
|
66
|
-
}
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
function makeJsonBasedIndexedRecords(logs) {
|
|
70
|
-
return new JsonBasedIndexedRecords(logs);
|
|
71
|
-
}
|
|
72
|
-
function toMsgOpenedLog(msgInfo, decrInfo, removeAfter) {
|
|
73
|
-
if (!decrInfo.key) {
|
|
74
|
-
throw new Error(`Given message decryption info doesn't have a key for message ${msgInfo.msgId}`);
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
msgState: 'opened',
|
|
78
|
-
msgType: msgInfo.msgType,
|
|
79
|
-
msgId: msgInfo.msgId,
|
|
80
|
-
deliveryTS: msgInfo.deliveryTS,
|
|
81
|
-
keyB64: buffer_utils_1.base64.pack(decrInfo.key),
|
|
82
|
-
keyStatus: decrInfo.keyStatus,
|
|
83
|
-
mainObjHeaderOfs: decrInfo.msgKeyPackLen,
|
|
84
|
-
removeAfter
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
function keyInfoFrom(log) {
|
|
88
|
-
return {
|
|
89
|
-
msgKey: buffer_utils_1.base64.open(log.keyB64),
|
|
90
|
-
msgKeyRole: log.keyStatus,
|
|
91
|
-
mainObjHeaderOfs: log.mainObjHeaderOfs
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
function addOnlyNonRemovedMsgs(dst, ignore, src) {
|
|
95
|
-
for (const log of src) {
|
|
96
|
-
if (log.msgState === 'removed') {
|
|
97
|
-
ignore.add(log.msgId);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
for (const log of src) {
|
|
101
|
-
if ((log.msgState === 'opened') && !ignore.has(log.msgId)) {
|
|
102
|
-
dst.push(log);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function cutEarlierMsgs(msgs, fromTS) {
|
|
107
|
-
let timedEnd = msgs.length;
|
|
108
|
-
for (let i = (msgs.length - 1); i >= 0; i -= 1) {
|
|
109
|
-
const msg = msgs[i];
|
|
110
|
-
if (msg.deliveryTS < fromTS) {
|
|
111
|
-
timedEnd = i;
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (timedEnd < msgs.length) {
|
|
118
|
-
msgs.splice(timedEnd, (msgs.length - timedEnd));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
class JsonBasedIndexedRecords {
|
|
122
|
-
constructor(logs) {
|
|
123
|
-
this.logs = logs;
|
|
124
|
-
this.cachedLogs = (0, timed_cache_1.makeTimedCache)(10 * 60 * 1000);
|
|
125
|
-
Object.seal(this);
|
|
126
|
-
}
|
|
127
|
-
async add(msgInfo, decrInfo, removeAfter) {
|
|
128
|
-
const msg = toMsgOpenedLog(msgInfo, decrInfo, removeAfter);
|
|
129
|
-
this.cachedLogs.set(msg.msgId, msg);
|
|
130
|
-
}
|
|
131
|
-
async remove(msgId) {
|
|
132
|
-
let foundLog = this.cachedLogs.get(msgId);
|
|
133
|
-
if (foundLog) {
|
|
134
|
-
this.cachedLogs.delete(msgId);
|
|
135
|
-
return foundLog.deliveryTS;
|
|
136
|
-
}
|
|
137
|
-
for (const fileTS of [undefined, ...this.logs.getLogFileTSs()]) {
|
|
138
|
-
let logs;
|
|
139
|
-
if ((fileTS === undefined)) {
|
|
140
|
-
logs = this.logs.getLatestMsgLogs();
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
const logsFromFile = await this.logs.getLogsFromFile(fileTS);
|
|
144
|
-
if (!logsFromFile) {
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
logs = logsFromFile;
|
|
148
|
-
}
|
|
149
|
-
for (const log of logs) {
|
|
150
|
-
if (log.msgId === msgId) {
|
|
151
|
-
return log.deliveryTS;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return undefined;
|
|
156
|
-
}
|
|
157
|
-
async listMsgs(fromTS) {
|
|
158
|
-
const ignore = new Set();
|
|
159
|
-
const msgs = [];
|
|
160
|
-
addOnlyNonRemovedMsgs(msgs, ignore, this.logs.getLatestMsgLogs());
|
|
161
|
-
const fileTSs = this.logs.getLogFileTSs();
|
|
162
|
-
if (fromTS === undefined) {
|
|
163
|
-
for (const fileTS of fileTSs) {
|
|
164
|
-
const logs = await this.logs.getLogsFromFile(fileTS);
|
|
165
|
-
if (!logs) {
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
addOnlyNonRemovedMsgs(msgs, ignore, logs);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
for (const fileTS of fileTSs) {
|
|
173
|
-
if (fromTS > fileTS) {
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
const logs = await this.logs.getLogsFromFile(fileTS);
|
|
177
|
-
if (!logs) {
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
addOnlyNonRemovedMsgs(msgs, ignore, logs);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// note sorting later messages to array's head
|
|
184
|
-
msgs.sort((m1, m2) => (m2.deliveryTS - m1.deliveryTS));
|
|
185
|
-
if (fromTS !== undefined) {
|
|
186
|
-
cutEarlierMsgs(msgs, fromTS);
|
|
187
|
-
}
|
|
188
|
-
for (const log of msgs) {
|
|
189
|
-
this.cachedLogs.set(log.msgId, log);
|
|
190
|
-
}
|
|
191
|
-
return msgs.map(log => ({
|
|
192
|
-
msgId: log.msgId,
|
|
193
|
-
msgType: log.msgType,
|
|
194
|
-
deliveryTS: log.deliveryTS
|
|
195
|
-
}));
|
|
196
|
-
}
|
|
197
|
-
async getKeyFor(msgId, deliveryTS) {
|
|
198
|
-
let log = this.cachedLogs.get(msgId);
|
|
199
|
-
if (log) {
|
|
200
|
-
return keyInfoFrom(log);
|
|
201
|
-
}
|
|
202
|
-
log = await this.logs.getMsgOpenedLog(msgId, deliveryTS);
|
|
203
|
-
if (!log) {
|
|
204
|
-
return undefined;
|
|
205
|
-
}
|
|
206
|
-
this.cachedLogs.set(log.msgId, log);
|
|
207
|
-
return keyInfoFrom(log);
|
|
208
|
-
}
|
|
209
|
-
async msgExists(msgInfo) {
|
|
210
|
-
const foundLog = this.cachedLogs.get(msgInfo.msgId);
|
|
211
|
-
return (!!foundLog && (foundLog.deliveryTS === msgInfo.deliveryTS));
|
|
212
|
-
}
|
|
213
|
-
stopSyncing() { }
|
|
214
|
-
}
|
|
215
|
-
Object.freeze(JsonBasedIndexedRecords.prototype);
|
|
216
|
-
Object.freeze(JsonBasedIndexedRecords);
|
|
217
|
-
class MsgLogs {
|
|
218
|
-
constructor(fs, latest, fileTSs) {
|
|
219
|
-
this.fs = fs;
|
|
220
|
-
this.latest = latest;
|
|
221
|
-
this.fileTSs = fileTSs;
|
|
222
|
-
this.changeProc = new synced_1.SingleProc();
|
|
223
|
-
Object.seal(this);
|
|
224
|
-
}
|
|
225
|
-
static async makeAndStartSyncing(logsFS) {
|
|
226
|
-
const fName = logsFileName(undefined);
|
|
227
|
-
let latest = await logsFS.readJSONFile(fName)
|
|
228
|
-
.catch(notFoundOrReThrow);
|
|
229
|
-
if (!latest) {
|
|
230
|
-
latest = [];
|
|
231
|
-
await logsFS.writeJSONFile(fName, latest, { create: true, exclusive: true });
|
|
232
|
-
}
|
|
233
|
-
const fileTSs = (await logsFS.listFolder('.'))
|
|
234
|
-
.map(f => f.name)
|
|
235
|
-
.filter(fName => fName.match(INDEX_FNAME_REGEXP))
|
|
236
|
-
.map(fName => parseInt(fName.substring(0, fName.length - LOG_EXT.length)))
|
|
237
|
-
.filter(fileTS => !isNaN(fileTS))
|
|
238
|
-
.sort(fileTSOrderComparator);
|
|
239
|
-
const logs = new MsgLogs(logsFS, latest, fileTSs);
|
|
240
|
-
return logs;
|
|
241
|
-
}
|
|
242
|
-
stopSyncing() {
|
|
243
|
-
// XXX fill this with content
|
|
244
|
-
}
|
|
245
|
-
async add(msgInfo, decrInfo, removeAfter) {
|
|
246
|
-
const msg = toMsgOpenedLog(msgInfo, decrInfo, removeAfter);
|
|
247
|
-
return this.changeProc.startOrChain(async () => {
|
|
248
|
-
const fileTS = this.fileTSforMsgTS(msg.deliveryTS);
|
|
249
|
-
const logs = ((fileTS === undefined) ?
|
|
250
|
-
this.latest : await this.getLogsFromFile(fileTS));
|
|
251
|
-
if (!logs) {
|
|
252
|
-
throw (0, error_1.errWithCause)(`${logsFileName(fileTS)} not found`, `Expectation fail: there should be some message records.`);
|
|
253
|
-
}
|
|
254
|
-
insertInto(logs, msg);
|
|
255
|
-
await this.updateLogsFile(fileTS, logs);
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
async updateLogsFile(fileTS, logs) {
|
|
259
|
-
await this.fs.writeJSONFile(logsFileName(fileTS), logs, { create: false });
|
|
260
|
-
}
|
|
261
|
-
async getLogsFromFile(fileTS) {
|
|
262
|
-
const fName = logsFileName(undefined);
|
|
263
|
-
const logs = await this.fs.readJSONFile(fName)
|
|
264
|
-
.catch(notFoundOrReThrow);
|
|
265
|
-
return logs;
|
|
266
|
-
}
|
|
267
|
-
fileTSforMsgTS(deliveryTS) {
|
|
268
|
-
if ((this.fileTSs.length === 0)
|
|
269
|
-
|| (deliveryTS >= this.fileTSs[this.fileTSs.length - 1])) {
|
|
270
|
-
return undefined;
|
|
271
|
-
}
|
|
272
|
-
let fileTS = this.fileTSs[this.fileTSs.length - 1];
|
|
273
|
-
for (let i = (this.fileTSs.length - 2); i <= 0; i -= 1) {
|
|
274
|
-
if (deliveryTS >= this.fileTSs[i]) {
|
|
275
|
-
break;
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
fileTS = this.fileTSs[i];
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return fileTS;
|
|
282
|
-
}
|
|
283
|
-
async remove(msgId, deliveryTS) {
|
|
284
|
-
const msgRmLog = {
|
|
285
|
-
msgState: 'removed',
|
|
286
|
-
msgId,
|
|
287
|
-
deliveryTS
|
|
288
|
-
};
|
|
289
|
-
return this.changeProc.startOrChain(async () => {
|
|
290
|
-
this.latest.push(msgRmLog);
|
|
291
|
-
const fileTS = this.fileTSforMsgTS(deliveryTS);
|
|
292
|
-
if (fileTS === undefined) {
|
|
293
|
-
removeMsgOpenedLogFrom(this.latest, msgId);
|
|
294
|
-
}
|
|
295
|
-
await this.updateLogsFile(undefined, this.latest);
|
|
296
|
-
if (fileTS !== undefined) {
|
|
297
|
-
this.scheduleRemovalOfMsgOpenedLog(fileTS, msgId);
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
scheduleRemovalOfMsgOpenedLog(fileTS, msgId) {
|
|
302
|
-
this.changeProc.startOrChain(async () => {
|
|
303
|
-
const logs = await this.getLogsFromFile(fileTS);
|
|
304
|
-
if (!logs) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
const changed = removeMsgOpenedLogFrom(logs, msgId);
|
|
308
|
-
if (!changed) {
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
await this.updateLogsFile(fileTS, logs);
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
async getMsgOpenedLog(msgId, deliveryTS) {
|
|
315
|
-
const fileTS = this.fileTSforMsgTS(deliveryTS);
|
|
316
|
-
const logs = ((fileTS === undefined) ?
|
|
317
|
-
this.latest : await this.getLogsFromFile(fileTS));
|
|
318
|
-
if (!logs) {
|
|
319
|
-
return undefined;
|
|
320
|
-
}
|
|
321
|
-
return logs.find(log => ((log.msgId === msgId) && (log.msgState === 'opened')));
|
|
322
|
-
}
|
|
323
|
-
getLogFileTSs() {
|
|
324
|
-
return this.fileTSs.concat([]);
|
|
325
|
-
}
|
|
326
|
-
getLatestMsgLogs() {
|
|
327
|
-
return this.latest.concat([]);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
exports.MsgLogs = MsgLogs;
|
|
331
|
-
Object.freeze(MsgLogs.prototype);
|
|
332
|
-
Object.freeze(MsgLogs);
|
|
333
|
-
Object.freeze(exports);
|