core-3nweb-client-lib 0.50.6 → 0.52.0
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 +29 -4
- package/build/api-defs/files.d.ts +6 -3
- package/build/core/asmail/asmail-cap-ipc.js +24 -3
- package/build/core/asmail/delivery/common.d.ts +3 -0
- package/build/core/asmail/delivery/common.js +5 -0
- package/build/core/asmail/delivery/index.js +6 -6
- package/build/core/asmail/delivery/msg.js +1 -6
- package/build/core/asmail/delivery/per-recipient-wip.js +3 -5
- package/build/core/asmail/filtering.d.ts +11 -0
- package/build/core/asmail/filtering.js +219 -0
- package/build/core/asmail/inbox/index.d.ts +1 -0
- package/build/core/asmail/inbox/index.js +3 -0
- package/build/core/asmail/index.d.ts +3 -0
- package/build/core/asmail/index.js +32 -4
- package/build/core/asmail/sending-params/index.d.ts +2 -0
- package/build/core/asmail/sending-params/index.js +11 -1
- package/build/core/asmail/sending-params/own-params.d.ts +2 -0
- package/build/core/asmail/sending-params/own-params.js +27 -5
- package/build/core/asmail/sending-params/params-from-others.d.ts +2 -0
- package/build/core/asmail/sending-params/params-from-others.js +28 -3
- package/build/core/keyring/index.d.ts +2 -0
- package/build/core/keyring/index.js +4 -2
- package/build/core/keyring/peer-keys/dataset/v2/logs.d.ts +13 -1
- package/build/core/keyring/peer-keys/dataset/v2/logs.js +17 -1
- package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.d.ts +2 -0
- package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.js +31 -1
- package/build/core/keyring/peer-keys/index.d.ts +2 -0
- package/build/core/keyring/peer-keys/index.js +11 -1
- package/build/core/storage/synced/upsyncer.js +2 -1
- package/build/lib-client/3nstorage/storage-owner.js +1 -2
- package/build/lib-client/asmail/sender.d.ts +0 -6
- package/build/lib-client/asmail/sender.js +20 -64
- package/build/lib-client/service-locator.d.ts +2 -3
- package/build/lib-client/service-locator.js +8 -3
- package/build/lib-common/canonical-address.js +3 -0
- package/build/tests/caps-api/asmail.js +42 -1
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright (C) 2016, 2020, 2022, 2024 -
|
|
2
|
+
Copyright (C) 2016, 2020, 2022, 2024 - 2026 3NSoft Inc.
|
|
3
3
|
|
|
4
4
|
This program is free software: you can redistribute it and/or modify it under
|
|
5
5
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -34,6 +34,10 @@ declare namespace web3n.asmail {
|
|
|
34
34
|
|
|
35
35
|
config: ASMailConfigService;
|
|
36
36
|
|
|
37
|
+
getReportAddressForDomain(domain: string): Promise<string>;
|
|
38
|
+
|
|
39
|
+
filter: ASMailFilter;
|
|
40
|
+
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
interface PreFlightOnlyService {
|
|
@@ -307,6 +311,7 @@ declare namespace web3n.asmail {
|
|
|
307
311
|
interface ASMailSendException extends RuntimeException {
|
|
308
312
|
type: 'asmail-delivery';
|
|
309
313
|
address?: string;
|
|
314
|
+
id?: string;
|
|
310
315
|
|
|
311
316
|
// errors that are due to remote side,
|
|
312
317
|
// these will be placed into ProgressDelivery object
|
|
@@ -319,10 +324,11 @@ declare namespace web3n.asmail {
|
|
|
319
324
|
allowedSize?: number;
|
|
320
325
|
recipientHasNoPubKey?: true;
|
|
321
326
|
recipientPubKeyFailsValidation?: true;
|
|
322
|
-
msgNotFound?: true;
|
|
323
327
|
|
|
324
328
|
// errors that are due to this side
|
|
325
329
|
msgCancelled?: true;
|
|
330
|
+
recipientBlocked?: true;
|
|
331
|
+
msgNotFound?: true;
|
|
326
332
|
}
|
|
327
333
|
|
|
328
334
|
interface ASMailConfigService {
|
|
@@ -378,7 +384,7 @@ declare namespace web3n.asmail {
|
|
|
378
384
|
*/
|
|
379
385
|
interface AddressesList {
|
|
380
386
|
[address: string]: number;
|
|
381
|
-
}
|
|
387
|
+
}
|
|
382
388
|
|
|
383
389
|
/**
|
|
384
390
|
* Invites list is a map from invites to respective maximum message size in
|
|
@@ -386,6 +392,25 @@ declare namespace web3n.asmail {
|
|
|
386
392
|
*/
|
|
387
393
|
interface InvitesList {
|
|
388
394
|
[invite: string]: number;
|
|
389
|
-
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
interface ASMailFilter {
|
|
398
|
+
listRules(): Promise<FilteringRule[]>;
|
|
399
|
+
addRule(rule: FilteringRule): Promise<{ ruleIndex: number; rule: FilteringRule; }>;
|
|
400
|
+
removeRule(ruleIndex: number, rule: FilteringRule): Promise<boolean>;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
interface BlockAddressRule {
|
|
404
|
+
ruleType: 'block-address';
|
|
405
|
+
domain: string;
|
|
406
|
+
username: string;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
interface BlockDomainRule {
|
|
410
|
+
ruleType: 'block-domain';
|
|
411
|
+
domain: string;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
type FilteringRule = BlockAddressRule | BlockDomainRule;
|
|
390
415
|
|
|
391
416
|
}
|
|
@@ -70,6 +70,7 @@ declare namespace web3n.files {
|
|
|
70
70
|
type: 'fs-sync';
|
|
71
71
|
path: string;
|
|
72
72
|
localVersion?: number;
|
|
73
|
+
uploadVersion?: number;
|
|
73
74
|
remoteVersion?: number;
|
|
74
75
|
alreadyUploading?: true;
|
|
75
76
|
uploadTaskId?: number;
|
|
@@ -1681,8 +1682,8 @@ declare namespace web3n.files {
|
|
|
1681
1682
|
upload(path: string, opts?: OptionsToUploadLocal): Promise<number|undefined>;
|
|
1682
1683
|
|
|
1683
1684
|
/**
|
|
1684
|
-
* This method is for resolving conflicts on folders.
|
|
1685
|
-
* It adopts given folder item, that is present in remote version and is missing in local version.
|
|
1685
|
+
* This method is for resolving conflicts on folders, an action item by item.
|
|
1686
|
+
* It adopts only given folder item, that is present in remote version and is missing in local version.
|
|
1686
1687
|
* Returns new local version.
|
|
1687
1688
|
* @param path
|
|
1688
1689
|
* @param remoteItemName
|
|
@@ -1694,7 +1695,9 @@ declare namespace web3n.files {
|
|
|
1694
1695
|
|
|
1695
1696
|
/**
|
|
1696
1697
|
* This method is for resolving conflicts on folders.
|
|
1697
|
-
* It absorbs folder changes done in remote version.
|
|
1698
|
+
* It completely absorbs folder changes done in remote version.
|
|
1699
|
+
* Such wholesome may or may not be appriate in all circumstances, so use `adoptRemoteFolderItem`
|
|
1700
|
+
* for a more nuanced item-by-item operations.
|
|
1698
1701
|
* Returns new local version, if there were remote items to adopt and their were added to local state.
|
|
1699
1702
|
* @param path
|
|
1700
1703
|
* @param opts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
Copyright (C) 2020 - 2023, 2025 3NSoft Inc.
|
|
3
|
+
Copyright (C) 2020 - 2023, 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
|
|
@@ -58,7 +58,9 @@ function exposeASMailCAP(cap, expServices) {
|
|
|
58
58
|
removeMsg: removeMsg.wrapService(box.removeMsg),
|
|
59
59
|
subscribe: inboxSubscribe.wrapService(box.subscribe, expServices)
|
|
60
60
|
},
|
|
61
|
-
config: exposeConfigCAP(cap.config)
|
|
61
|
+
config: exposeConfigCAP(cap.config),
|
|
62
|
+
getReportAddressForDomain: (0, service_side_wrap_1.wrapReqReplyFunc)(cap, cap.getReportAddressForDomain),
|
|
63
|
+
filter: exposeFilterCAP(cap.filter)
|
|
62
64
|
};
|
|
63
65
|
}
|
|
64
66
|
}
|
|
@@ -83,7 +85,9 @@ function makeASMailBasedOnListing(caller, objPath, capLists) {
|
|
|
83
85
|
removeMsg: removeMsg.makeCaller(caller, inboxPath),
|
|
84
86
|
subscribe: inboxSubscribe.makeCaller(caller, inboxPath)
|
|
85
87
|
},
|
|
86
|
-
config: makeConfigCaller(caller, objPath.concat('config'))
|
|
88
|
+
config: makeConfigCaller(caller, objPath.concat('config')),
|
|
89
|
+
getReportAddressForDomain: (0, caller_side_wrap_1.makeReqRepFuncCaller)(caller, objPath.concat('getReportAddressForDomain')),
|
|
90
|
+
filter: makeFilterCaller(caller, objPath.concat('filter'))
|
|
87
91
|
};
|
|
88
92
|
}
|
|
89
93
|
else if (capLists === 'preflight') {
|
|
@@ -612,4 +616,21 @@ function makeConfigCaller(caller, objPath) {
|
|
|
612
616
|
setOnServer: callConfig(caller, objPath, 'setOnServer')
|
|
613
617
|
};
|
|
614
618
|
}
|
|
619
|
+
function exposeFilterCAP(cap) {
|
|
620
|
+
return {
|
|
621
|
+
addRule: (0, service_side_wrap_1.wrapReqReplySrvMethod)(cap, 'addRule'),
|
|
622
|
+
listRules: (0, service_side_wrap_1.wrapReqReplySrvMethod)(cap, 'listRules'),
|
|
623
|
+
removeRule: (0, service_side_wrap_1.wrapReqReplySrvMethod)(cap, 'removeRule')
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function callFilder(caller, objPath, method) {
|
|
627
|
+
return (0, caller_side_wrap_1.makeReqRepObjCaller)(caller, objPath, method);
|
|
628
|
+
}
|
|
629
|
+
function makeFilterCaller(caller, objPath) {
|
|
630
|
+
return {
|
|
631
|
+
addRule: callFilder(caller, objPath, 'addRule'),
|
|
632
|
+
listRules: callFilder(caller, objPath, 'listRules'),
|
|
633
|
+
removeRule: callFilder(caller, objPath, 'removeRule')
|
|
634
|
+
};
|
|
635
|
+
}
|
|
615
636
|
Object.freeze(exports);
|
|
@@ -14,6 +14,7 @@ type AttachmentsContainer = web3n.asmail.AttachmentsContainer;
|
|
|
14
14
|
type FS = web3n.files.FS;
|
|
15
15
|
type WritableFS = web3n.files.WritableFS;
|
|
16
16
|
type JsonKey = web3n.keys.JsonKey;
|
|
17
|
+
type ASMailSendException = web3n.asmail.ASMailSendException;
|
|
17
18
|
export declare class Attachments {
|
|
18
19
|
container: AttachmentsContainer | undefined;
|
|
19
20
|
fs: FS | undefined;
|
|
@@ -66,6 +67,7 @@ export interface ResourcesForSending {
|
|
|
66
67
|
* @param address
|
|
67
68
|
*/
|
|
68
69
|
newParamsForSendingReplies: (address: string) => Promise<SendingParams | undefined>;
|
|
70
|
+
isAddressBlocked(address: string): boolean;
|
|
69
71
|
};
|
|
70
72
|
cryptor: AsyncSBoxCryptor;
|
|
71
73
|
random: AsyncRNG;
|
|
@@ -79,4 +81,5 @@ export interface SavedMsgToSend {
|
|
|
79
81
|
recipients: string[];
|
|
80
82
|
retryOpts: DeliveryOptions['retryRecipient'];
|
|
81
83
|
}
|
|
84
|
+
export declare function makeDeliveryException(params: Partial<ASMailSendException>): ASMailSendException;
|
|
82
85
|
export {};
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
19
|
exports.SEG_SIZE_IN_K_QUATS = exports.Attachments = void 0;
|
|
20
20
|
exports.estimatePackedSizeOf = estimatePackedSizeOf;
|
|
21
|
+
exports.makeDeliveryException = makeDeliveryException;
|
|
21
22
|
const attachments_container_1 = require("../msg/attachments-container");
|
|
22
23
|
const buffer_utils_1 = require("../../../lib-common/buffer-utils");
|
|
24
|
+
const runtime_1 = require("../../../lib-common/exceptions/runtime");
|
|
23
25
|
const ATTACHMENTS_NAME = 'attachments';
|
|
24
26
|
class Attachments {
|
|
25
27
|
constructor(container, fs) {
|
|
@@ -138,4 +140,7 @@ function estimatePackedSizeOf(size) {
|
|
|
138
140
|
}
|
|
139
141
|
return 148 + numOfCompleteSegs * exports.SEG_SIZE_IN_K_QUATS * 256 + lastSegSize;
|
|
140
142
|
}
|
|
143
|
+
function makeDeliveryException(params) {
|
|
144
|
+
return (0, runtime_1.makeRuntimeException)('asmail-delivery', params, {});
|
|
145
|
+
}
|
|
141
146
|
Object.freeze(exports);
|
|
@@ -104,12 +104,7 @@ class Delivery {
|
|
|
104
104
|
const msg = this.msgs.get(id);
|
|
105
105
|
if (!msg) {
|
|
106
106
|
if (observer.error) {
|
|
107
|
-
|
|
108
|
-
runtimeException: true,
|
|
109
|
-
type: 'asmail-delivery',
|
|
110
|
-
msgNotFound: true
|
|
111
|
-
};
|
|
112
|
-
observer.error(exc);
|
|
107
|
+
observer.error((0, common_1.makeDeliveryException)({ id, msgNotFound: true }));
|
|
113
108
|
}
|
|
114
109
|
return () => { };
|
|
115
110
|
}
|
|
@@ -149,6 +144,11 @@ class Delivery {
|
|
|
149
144
|
if (!Array.isArray(recipients) || (recipients.length === 0)) {
|
|
150
145
|
throw new Error(`Given invalid recipients: ${recipients} for message ${id}`);
|
|
151
146
|
}
|
|
147
|
+
for (const recipient of recipients) {
|
|
148
|
+
if (this.r.correspondents.isAddressBlocked(recipient)) {
|
|
149
|
+
throw (0, common_1.makeDeliveryException)({ address: recipient, recipientBlocked: true });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
152
|
if (this.msgs.has(id)) {
|
|
153
153
|
throw new Error(`Message with id ${id} has already been added for delivery`);
|
|
154
154
|
}
|
|
@@ -235,12 +235,7 @@ class Msg {
|
|
|
235
235
|
return;
|
|
236
236
|
}
|
|
237
237
|
await filesProc.catch(() => { });
|
|
238
|
-
|
|
239
|
-
runtimeException: true,
|
|
240
|
-
type: 'asmail-delivery',
|
|
241
|
-
msgCancelled: true
|
|
242
|
-
};
|
|
243
|
-
this.progressPublisher.error(exc);
|
|
238
|
+
this.progressPublisher.error((0, common_1.makeDeliveryException)({ id: this.id, msgCancelled: true }));
|
|
244
239
|
}
|
|
245
240
|
/**
|
|
246
241
|
* This starts sending a message to all recipients in parallel, and should be
|
|
@@ -21,6 +21,7 @@ const buffer_utils_1 = require("../../../lib-common/buffer-utils");
|
|
|
21
21
|
const sender_1 = require("../../../lib-client/asmail/sender");
|
|
22
22
|
const synced_1 = require("../../../lib-common/processes/synced");
|
|
23
23
|
const key_verification_1 = require("../key-verification");
|
|
24
|
+
const common_1 = require("./common");
|
|
24
25
|
const MSG_SIZE_FOR_STATE_RECORDING = 1024 * 1024;
|
|
25
26
|
const EMPTY_BYTE_ARR = new Uint8Array(0);
|
|
26
27
|
/**
|
|
@@ -209,14 +210,11 @@ class WIP {
|
|
|
209
210
|
}
|
|
210
211
|
const certs = await this.sender.getRecipientsInitPubKey();
|
|
211
212
|
return (0, key_verification_1.checkAndExtractPKey)(this.sender.net, this.msg.r.midResolver, recipient, certs).catch(err => {
|
|
212
|
-
|
|
213
|
-
runtimeException: true,
|
|
214
|
-
type: 'asmail-delivery',
|
|
213
|
+
throw (0, common_1.makeDeliveryException)({
|
|
215
214
|
address: recipient,
|
|
216
215
|
recipientPubKeyFailsValidation: true,
|
|
217
216
|
cause: err
|
|
218
|
-
};
|
|
219
|
-
throw exc;
|
|
217
|
+
});
|
|
220
218
|
});
|
|
221
219
|
}
|
|
222
220
|
/**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { LogError } from "../../lib-client/logging/log-to-file";
|
|
2
|
+
type WritableFS = web3n.files.WritableFS;
|
|
3
|
+
type ASMailFilter = web3n.asmail.ASMailFilter;
|
|
4
|
+
type FilteringRule = web3n.asmail.FilteringRule;
|
|
5
|
+
export declare function makeAddressFilter(fs: WritableFS, logError: LogError, onRuleAddition: (r: FilteringRule) => Promise<void>): Promise<{
|
|
6
|
+
makeCAP: () => ASMailFilter;
|
|
7
|
+
isAddressBlocked: (address: string) => boolean;
|
|
8
|
+
close: () => void;
|
|
9
|
+
}>;
|
|
10
|
+
export type AddressFilter = Awaited<ReturnType<typeof makeAddressFilter>>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,219 @@
|
|
|
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.makeAddressFilter = makeAddressFilter;
|
|
20
|
+
const canonical_address_1 = require("../../lib-common/canonical-address");
|
|
21
|
+
const single_file_1 = require("../../lib-common/dataset-sync/single-file");
|
|
22
|
+
const json_utils_1 = require("../../lib-common/json-utils");
|
|
23
|
+
const RULES_FNAME = 'rules.json';
|
|
24
|
+
async function makeAddressFilter(fs, logError, onRuleAddition) {
|
|
25
|
+
const { file: rulesFile, changeProc } = await (0, single_file_1.initSyncedFile)(fs, RULES_FNAME, newFile => newFile.writeJSON([]));
|
|
26
|
+
let rules = await prepareRulesReadFromFile(await rulesFile.readJSON());
|
|
27
|
+
async function saveRulesAndTriggerSync() {
|
|
28
|
+
await changeProc.startOrChain(() => rulesFile.writeJSON(rules));
|
|
29
|
+
triggerUpload();
|
|
30
|
+
}
|
|
31
|
+
const { stopFileWatching, triggerUpload } = (0, single_file_1.watchAndApplyChangesFromOtherDevices)(rulesFile, changeProc, onChangeFromOtherDevices, doOnConflict);
|
|
32
|
+
async function onChangeFromOtherDevices(newVersion) {
|
|
33
|
+
changeProc.startOrChain(async () => {
|
|
34
|
+
const { version, json } = await rulesFile.v.readJSON();
|
|
35
|
+
if (version === newVersion) {
|
|
36
|
+
rules = await prepareRulesReadFromFile(json);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function doOnConflict() {
|
|
41
|
+
return changeProc.startOrChain(async () => {
|
|
42
|
+
const { remote, synced } = await rulesFile.v.sync.status(true);
|
|
43
|
+
const syncedRules = await prepareRulesReadFromFile((await rulesFile.v.readJSON({ remoteVersion: synced === null || synced === void 0 ? void 0 : synced.latest })).json);
|
|
44
|
+
const remoteRules = await prepareRulesReadFromFile((await rulesFile.v.readJSON({ remoteVersion: remote === null || remote === void 0 ? void 0 : remote.latest })).json);
|
|
45
|
+
const localRules = rules.concat();
|
|
46
|
+
const mergedRules = [];
|
|
47
|
+
while (remoteRules.length > 0) {
|
|
48
|
+
const r = remoteRules.shift();
|
|
49
|
+
const si = syncedRules.findIndex(sr => (0, json_utils_1.deepEqual)(sr, r));
|
|
50
|
+
const li = localRules.findIndex(lr => (0, json_utils_1.deepEqual)(lr, r));
|
|
51
|
+
if (si >= 0) {
|
|
52
|
+
if (li >= 0) {
|
|
53
|
+
// rule stays as is
|
|
54
|
+
mergedRules.push(r);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// rule removed in local
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
if (li >= 0) {
|
|
62
|
+
// rule added in local and remote
|
|
63
|
+
mergedRules.push(r);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// rule added in remote
|
|
67
|
+
mergedRules.push(r);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (si > 0) {
|
|
71
|
+
syncedRules.splice(si, 1);
|
|
72
|
+
}
|
|
73
|
+
if (li > 0) {
|
|
74
|
+
localRules.splice(li, 1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
while (localRules.length > 0) {
|
|
78
|
+
const l = localRules.shift();
|
|
79
|
+
const si = syncedRules.findIndex(sr => (0, json_utils_1.deepEqual)(sr, l));
|
|
80
|
+
if (si >= 0) {
|
|
81
|
+
// rule was removed in remote
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// rule was added in local
|
|
85
|
+
mergedRules.push(l);
|
|
86
|
+
}
|
|
87
|
+
if (si > 0) {
|
|
88
|
+
syncedRules.splice(si, 1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
rules = mergedRules;
|
|
92
|
+
await rulesFile.writeJSON(rules);
|
|
93
|
+
await rulesFile.v.sync.upload({ uploadVersion: remote.latest + 1 });
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async function prepareRulesReadFromFile(rulesFromFile) {
|
|
97
|
+
if (!Array.isArray(rulesFromFile)) {
|
|
98
|
+
await logError(`Expected array with rules`, `Reading of ASMail filter rules produced unexpected data`);
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const rules = [];
|
|
102
|
+
const errors = [];
|
|
103
|
+
for (const r of rulesFromFile) {
|
|
104
|
+
try {
|
|
105
|
+
rules.push(prepareRuleFrom(r));
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
errors.push(err);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (errors.length > 0) {
|
|
112
|
+
await logError(errors[0], `Reading of ASMail filter rules produced produced ${errors.length} errors, the first one is here`);
|
|
113
|
+
}
|
|
114
|
+
return rules;
|
|
115
|
+
}
|
|
116
|
+
function groupRulesByDomains(rules) {
|
|
117
|
+
const m = new Map();
|
|
118
|
+
for (const rule of rules) {
|
|
119
|
+
const domain = rule.domain;
|
|
120
|
+
let domainRules = m.get(domain);
|
|
121
|
+
if (!domainRules) {
|
|
122
|
+
domainRules = [];
|
|
123
|
+
m.set(domain, domainRules);
|
|
124
|
+
}
|
|
125
|
+
domainRules.push(rule);
|
|
126
|
+
}
|
|
127
|
+
return m;
|
|
128
|
+
}
|
|
129
|
+
let rulesByDomain = groupRulesByDomains(rules);
|
|
130
|
+
async function addRule(ruleToAdd) {
|
|
131
|
+
const rule = prepareRuleFrom(ruleToAdd);
|
|
132
|
+
const ruleIndex = rules.push(rule) - 1;
|
|
133
|
+
rulesByDomain = groupRulesByDomains(rules);
|
|
134
|
+
await saveRulesAndTriggerSync();
|
|
135
|
+
await onRuleAddition(rule);
|
|
136
|
+
return { rule, ruleIndex };
|
|
137
|
+
}
|
|
138
|
+
async function removeRule(ruleIndex, rule) {
|
|
139
|
+
if ((ruleIndex < 0) || (ruleIndex >= rules.length) || !(0, json_utils_1.deepEqual)(rules[ruleIndex], rule)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
rules.splice(ruleIndex, 1);
|
|
143
|
+
await saveRulesAndTriggerSync();
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
async function listRules() {
|
|
147
|
+
return rules;
|
|
148
|
+
}
|
|
149
|
+
function isAddressBlocked(address) {
|
|
150
|
+
const cAddr = (0, canonical_address_1.checkAndTransformAddress)(address);
|
|
151
|
+
if (!cAddr) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
const { uName, dName } = cAddrIntoParts(cAddr);
|
|
155
|
+
const domainRules = rulesByDomain.get(dName);
|
|
156
|
+
if (!domainRules) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
for (const rule of domainRules) {
|
|
160
|
+
if (rule.ruleType === 'block-domain') {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
else if (rule.ruleType === 'block-address') {
|
|
164
|
+
if (uName == rule.username) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
function makeCAP() {
|
|
172
|
+
return {
|
|
173
|
+
addRule,
|
|
174
|
+
removeRule,
|
|
175
|
+
listRules
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function close() {
|
|
179
|
+
stopFileWatching();
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
makeCAP,
|
|
183
|
+
isAddressBlocked,
|
|
184
|
+
close
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function cAddrIntoParts(cAddr) {
|
|
188
|
+
const atInd = cAddr.lastIndexOf('@');
|
|
189
|
+
if (atInd < 0) {
|
|
190
|
+
throw TypeError(`No @ in given canonical address: ${cAddr}`);
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
uName: cAddr.substring(0, atInd),
|
|
194
|
+
dName: cAddr.substring(atInd + 1)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function prepareRuleFrom(r) {
|
|
198
|
+
switch (r.ruleType) {
|
|
199
|
+
case 'block-address': {
|
|
200
|
+
const cAddr = (0, canonical_address_1.toCanonicalAddress)(`${r.username}@${r.domain}`);
|
|
201
|
+
const { uName, dName } = cAddrIntoParts(cAddr);
|
|
202
|
+
return {
|
|
203
|
+
ruleType: 'block-address',
|
|
204
|
+
domain: dName, username: uName
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
case 'block-domain': {
|
|
208
|
+
const cAddr = (0, canonical_address_1.toCanonicalAddress)(`@${r.domain}`);
|
|
209
|
+
const { dName } = cAddrIntoParts(cAddr);
|
|
210
|
+
return {
|
|
211
|
+
ruleType: 'block-domain',
|
|
212
|
+
domain: dName
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
default:
|
|
216
|
+
throw TypeError(`Unknown rule type: ${r.ruleType}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
Object.freeze(exports);
|
|
@@ -116,6 +116,9 @@ async function makeInboxOnServer(cachePath, syncedFS, localFS, r) {
|
|
|
116
116
|
const decrOut = await r.correspondents.msgDecryptor(meta.extMeta, getMainObjHeader, getOpenedMsg, checkerOfMidKeyCerts(msgOnDisk.deliveryTS));
|
|
117
117
|
if (decrOut) {
|
|
118
118
|
const { decrInfo, openedMsg } = decrOut;
|
|
119
|
+
if (r.correspondents.isAddressBlocked(decrInfo.correspondent)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
119
122
|
openedMsg.setMsgKeyRole(decrInfo.keyStatus);
|
|
120
123
|
// XXX we have never used this part of protocol, should it be removed at all from ASMail protocol?
|
|
121
124
|
checkServerAuthIfPresent(meta, decrInfo);
|
|
@@ -23,6 +23,8 @@ export declare class ASMail {
|
|
|
23
23
|
private delivery;
|
|
24
24
|
private config;
|
|
25
25
|
private sendingParams;
|
|
26
|
+
private getReportAddressForDomain;
|
|
27
|
+
private filter;
|
|
26
28
|
constructor(cryptor: AsyncSBoxCryptor, random: AsyncRNG, makeNet: MakeNet, inboxPathForUser: InboxPathForUser, logger: Logger);
|
|
27
29
|
init(address: string, getSigner: GetSigner, syncedFS: WritableFS, localFS: WritableFS, getStorages: StorageGetter, makeResolver: ServiceLocatorMaker, config: ConfigOfASMailServer, keyring: KeyringForASMail): Promise<void>;
|
|
28
30
|
private setupSendingParams;
|
|
@@ -34,5 +36,6 @@ export declare class ASMail {
|
|
|
34
36
|
suspendNetworkActivity(): void;
|
|
35
37
|
resumeNetworkActivity(): void;
|
|
36
38
|
get connectivityEvent$(): import("rxjs").Observable<import("./inbox/inbox-events").InboxConnectionStatus>;
|
|
39
|
+
private applyNewFilteringRuleToPeersInfo;
|
|
37
40
|
}
|
|
38
41
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
Copyright (C) 2015 - 2018, 2020 - 2022, 2025 3NSoft Inc.
|
|
3
|
+
Copyright (C) 2015 - 2018, 2020 - 2022, 2025 - 2026 3NSoft Inc.
|
|
4
4
|
|
|
5
5
|
This program is free software: you can redistribute it and/or modify it under
|
|
6
6
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -22,9 +22,11 @@ const error_1 = require("../../lib-common/exceptions/error");
|
|
|
22
22
|
const delivery_1 = require("./delivery");
|
|
23
23
|
const sending_params_1 = require("./sending-params");
|
|
24
24
|
const fs_sync_utils_1 = require("../../lib-client/fs-utils/fs-sync-utils");
|
|
25
|
+
const filtering_1 = require("./filtering");
|
|
25
26
|
const INBOX_DATA_FOLDER = 'inbox';
|
|
26
27
|
const DELIVERY_DATA_FOLDER = 'delivery';
|
|
27
28
|
const SEND_PARAMS_DATA_FOLDER = 'sending-params';
|
|
29
|
+
const FILTERS_DATA_FOLDER = 'filtering';
|
|
28
30
|
class ASMail {
|
|
29
31
|
constructor(cryptor, random, makeNet, inboxPathForUser, logger) {
|
|
30
32
|
this.cryptor = cryptor;
|
|
@@ -38,16 +40,20 @@ class ASMail {
|
|
|
38
40
|
this.delivery = undefined;
|
|
39
41
|
this.config = undefined;
|
|
40
42
|
this.sendingParams = undefined;
|
|
43
|
+
this.getReportAddressForDomain = undefined;
|
|
44
|
+
this.filter = undefined;
|
|
41
45
|
Object.seal(this);
|
|
42
46
|
}
|
|
43
47
|
async init(address, getSigner, syncedFS, localFS, getStorages, makeResolver, config, keyring) {
|
|
44
48
|
try {
|
|
45
49
|
this.address = address;
|
|
50
|
+
this.getReportAddressForDomain = makeResolver('report', this.logger.logError);
|
|
46
51
|
// XXX this should be part of proper syncing logic
|
|
47
52
|
// await getRemoteFolderChanges(syncedFS);
|
|
48
53
|
this.config = config;
|
|
49
54
|
this.keyring = keyring;
|
|
50
55
|
await this.setupSendingParams(syncedFS);
|
|
56
|
+
this.filter = await (0, filtering_1.makeAddressFilter)(await (0, fs_sync_utils_1.getOrMakeDirOnInit)(syncedFS, FILTERS_DATA_FOLDER), this.logger.logError, this.applyNewFilteringRuleToPeersInfo.bind(this));
|
|
51
57
|
await Promise.all([
|
|
52
58
|
this.setupInbox(syncedFS, localFS, getSigner, getStorages, makeResolver),
|
|
53
59
|
this.setupDelivery(localFS, getSigner, makeResolver)
|
|
@@ -78,7 +84,8 @@ class ASMail {
|
|
|
78
84
|
generateIntroKeysToSendMsg: this.keyring.generateIntroKeysToSendMsg,
|
|
79
85
|
getEstablishedKeysToSendMsg: this.keyring.getEstablishedKeysToSendMsg,
|
|
80
86
|
paramsForSendingTo: this.sendingParams.otherSides.get,
|
|
81
|
-
newParamsForSendingReplies: this.sendingParams.thisSide.getUpdated
|
|
87
|
+
newParamsForSendingReplies: this.sendingParams.thisSide.getUpdated,
|
|
88
|
+
isAddressBlocked: this.filter.isAddressBlocked
|
|
82
89
|
},
|
|
83
90
|
notifyMsgProgress: () => { throw new Error(`Method not set`); },
|
|
84
91
|
makeNet: this.makeNet,
|
|
@@ -101,7 +108,8 @@ class ASMail {
|
|
|
101
108
|
msgDecryptor: this.keyring.decrypt,
|
|
102
109
|
markOwnSendingParamsAsUsed: this.sendingParams.thisSide.setAsUsed,
|
|
103
110
|
saveParamsForSendingTo: this.sendingParams.otherSides.set,
|
|
104
|
-
midResolver: makeResolver('mailerid', this.logger.logError)
|
|
111
|
+
midResolver: makeResolver('mailerid', this.logger.logError),
|
|
112
|
+
isAddressBlocked: this.filter.isAddressBlocked
|
|
105
113
|
},
|
|
106
114
|
makeNet: this.makeNet,
|
|
107
115
|
logError: this.logger.logError
|
|
@@ -112,7 +120,9 @@ class ASMail {
|
|
|
112
120
|
getUserId: async () => this.address,
|
|
113
121
|
delivery: this.delivery.makeCAP(),
|
|
114
122
|
inbox: this.inbox.makeCAP(),
|
|
115
|
-
config: this.config.makeCAP()
|
|
123
|
+
config: this.config.makeCAP(),
|
|
124
|
+
getReportAddressForDomain: this.getReportAddressForDomain,
|
|
125
|
+
filter: this.filter.makeCAP()
|
|
116
126
|
};
|
|
117
127
|
return Object.freeze(w);
|
|
118
128
|
}
|
|
@@ -133,6 +143,7 @@ class ASMail {
|
|
|
133
143
|
await this.delivery.close();
|
|
134
144
|
await this.keyring.close();
|
|
135
145
|
await this.sendingParams.close();
|
|
146
|
+
this.filter.close();
|
|
136
147
|
}
|
|
137
148
|
suspendNetworkActivity() {
|
|
138
149
|
this.inbox.suspendNetworkActivity();
|
|
@@ -143,6 +154,23 @@ class ASMail {
|
|
|
143
154
|
get connectivityEvent$() {
|
|
144
155
|
return this.inbox.connectivityEvent$;
|
|
145
156
|
}
|
|
157
|
+
async applyNewFilteringRuleToPeersInfo(r) {
|
|
158
|
+
switch (r.ruleType) {
|
|
159
|
+
case 'block-address': {
|
|
160
|
+
const { username, domain } = r;
|
|
161
|
+
const cAddr = `${username}@${domain}`;
|
|
162
|
+
await this.keyring.forgetCorrespondentAt(cAddr);
|
|
163
|
+
await this.sendingParams.forgetCorrespondentAt(cAddr);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'block-domain': {
|
|
167
|
+
const { domain } = r;
|
|
168
|
+
await this.keyring.forgetCorrespondentAt(domain);
|
|
169
|
+
await this.sendingParams.forgetCorrespondentAt(domain);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
146
174
|
}
|
|
147
175
|
exports.ASMail = ASMail;
|
|
148
176
|
Object.freeze(ASMail.prototype);
|
|
@@ -18,5 +18,7 @@ export interface SendingParamsHolder {
|
|
|
18
18
|
thisSide: SendingParamsThisSide;
|
|
19
19
|
otherSides: SendingParamsOtherSides;
|
|
20
20
|
close: () => Promise<void>;
|
|
21
|
+
forgetCorrespondentAt(cAddr: string): Promise<void>;
|
|
22
|
+
forgetAllCorrespondentsAtDomain(domain: string): Promise<void>;
|
|
21
23
|
}
|
|
22
24
|
export declare function makeSendingParamsHolder(fs: WritableFS, anonInvitesOnServer: ParamOnServer<'anon-sender/invites'>, random: AsyncRNG): Promise<SendingParamsHolder>;
|
|
@@ -32,10 +32,20 @@ async function makeSendingParamsHolder(fs, anonInvitesOnServer, random) {
|
|
|
32
32
|
await thisSide.close();
|
|
33
33
|
await otherSides.close();
|
|
34
34
|
}
|
|
35
|
+
async function forgetCorrespondentAt(cAddr) {
|
|
36
|
+
await otherSides.forgetCorrespondentAt(cAddr);
|
|
37
|
+
await thisSide.forgetCorrespondentAt(cAddr);
|
|
38
|
+
}
|
|
39
|
+
async function forgetAllCorrespondentsAtDomain(domain) {
|
|
40
|
+
await otherSides.forgetAllCorrespondentsAtDomain(domain);
|
|
41
|
+
await thisSide.forgetAllCorrespondentsAtDomain(domain);
|
|
42
|
+
}
|
|
35
43
|
return {
|
|
36
44
|
thisSide,
|
|
37
45
|
otherSides,
|
|
38
|
-
close
|
|
46
|
+
close,
|
|
47
|
+
forgetCorrespondentAt,
|
|
48
|
+
forgetAllCorrespondentsAtDomain
|
|
39
49
|
};
|
|
40
50
|
}
|
|
41
51
|
Object.freeze(exports);
|
|
@@ -4,5 +4,7 @@ type WritableFS = web3n.files.WritableFS;
|
|
|
4
4
|
export type OwnSendingParams = Awaited<ReturnType<typeof makeOwnSendingParams>>;
|
|
5
5
|
export declare function makeOwnSendingParams(fs: WritableFS, fileName: string, anonInvites: AnonymousInvites): Promise<SendingParamsHolder['thisSide'] & {
|
|
6
6
|
close: () => Promise<void>;
|
|
7
|
+
forgetCorrespondentAt(cAddr: string): Promise<void>;
|
|
8
|
+
forgetAllCorrespondentsAtDomain(domain: string): Promise<void>;
|
|
7
9
|
}>;
|
|
8
10
|
export {};
|