core-3nweb-client-lib 0.49.8 → 0.50.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.
Files changed (35) hide show
  1. package/build/api-defs/files.d.ts +1 -1
  2. package/build/core/asmail/delivery/per-recipient-wip.js +4 -0
  3. package/build/core/asmail/inbox/index.js +5 -1
  4. package/build/core/asmail/inbox/msg-indexing/index.js +8 -9
  5. package/build/core/asmail/index.js +3 -3
  6. package/build/core/asmail/sending-params/index.d.ts +13 -14
  7. package/build/core/asmail/sending-params/index.js +15 -35
  8. package/build/core/asmail/sending-params/invitations-anon.d.ts +7 -31
  9. package/build/core/asmail/sending-params/invitations-anon.js +77 -127
  10. package/build/core/asmail/sending-params/own-params.d.ts +5 -25
  11. package/build/core/asmail/sending-params/own-params.js +144 -97
  12. package/build/core/asmail/sending-params/params-from-others.d.ts +6 -16
  13. package/build/core/asmail/sending-params/params-from-others.js +88 -58
  14. package/build/core/index.d.ts +2 -1
  15. package/build/core/index.js +4 -4
  16. package/build/core/keyring/index.js +1 -1
  17. package/build/core/keyring/introductory-keys/published-intro-key.js +15 -15
  18. package/build/core/keyring/peer-keys/index.js +6 -7
  19. package/build/core/storage/index.d.ts +2 -1
  20. package/build/core/storage/index.js +4 -3
  21. package/build/core/storage/system-folders/index.d.ts +1 -2
  22. package/build/core/storage/system-folders/index.js +18 -22
  23. package/build/lib-client/fs-utils/fs-sync-utils.d.ts +1 -1
  24. package/build/lib-client/fs-utils/fs-sync-utils.js +21 -15
  25. package/build/lib-client/xsp-fs/util/file-based-json.js +1 -1
  26. package/build/lib-common/dataset-sync/per-device-json-array-logs.js +42 -27
  27. package/build/lib-common/dataset-sync/single-file.d.ts +4 -2
  28. package/build/lib-common/dataset-sync/single-file.js +27 -31
  29. package/build/lib-common/dataset-sync/utils.d.ts +16 -0
  30. package/build/lib-common/dataset-sync/utils.js +72 -0
  31. package/build/lib-common-on-node/device-fs-places.d.ts +3 -0
  32. package/build/lib-common-on-node/device-fs-places.js +39 -0
  33. package/build/tests/computer.3nweb.core/keyrings.js +2 -2
  34. package/build/tests/libs-for-tests/core-runner.js +2 -1
  35. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2017 - 2018, 2025 3NSoft Inc.
3
+ Copyright (C) 2017 - 2018, 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,126 +16,173 @@
16
16
  this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.OwnSendingParams = void 0;
20
- const file_based_json_1 = require("../../../lib-client/xsp-fs/util/file-based-json");
19
+ exports.makeOwnSendingParams = makeOwnSendingParams;
21
20
  const json_utils_1 = require("../../../lib-common/json-utils");
21
+ const single_file_1 = require("../../../lib-common/dataset-sync/single-file");
22
22
  const DEFAULT_INVITE_LABEL = 'Default';
23
23
  const DEFAULT_INVITE_MAX_MSG_SIZE = 1024 * 1024 * 1024;
24
- /**
25
- * Instance of this class keeps track of sending parameters, which user gives to
26
- * other correspondents for sending messages back. These parameters may have
27
- * invitation tokens, and this class uses config service to register tokens on
28
- * a server.
29
- */
30
- class OwnSendingParams {
31
- constructor(anonInvites) {
32
- this.anonInvites = anonInvites;
33
- this.params = new Map();
34
- this.defaultParams = undefined;
35
- this.getFor = async (address) => {
36
- let p = this.params.get(address);
37
- if (p) {
38
- if (p.suggested) {
39
- return p.suggested;
40
- }
41
- else if (p.inUse) {
42
- return; // undefined, cause params are known to correspondent,
43
- // and there are no params to suggest for future use.
44
- }
45
- }
46
- // XXX Or, instead, should we set defaultParams?
47
- if (!this.defaultParams) {
48
- return;
49
- }
50
- p = {
51
- address,
52
- suggested: (0, json_utils_1.copy)(this.defaultParams)
53
- };
54
- p.suggested.timestamp = Date.now();
55
- this.params.set(p.address, p);
56
- await this.fileProc.order.startOrChain(() => this.persist());
57
- return p.suggested;
58
- };
59
- this.setAsInUse = async (address, invite) => {
60
- await this.fileProc.order.startOrChain(async () => {
61
- const p = this.params.get(address);
62
- if (!p || !p.suggested) {
63
- return;
64
- }
65
- if (p.suggested.invitation !== invite) {
66
- return;
67
- }
68
- p.inUse = p.suggested;
69
- p.suggested = undefined;
70
- await this.persist();
71
- });
72
- };
73
- this.fileProc = new file_based_json_1.JsonFileProc(this.onFileEvent.bind(this));
74
- Object.seal(this);
24
+ async function makeOwnSendingParams(fs, fileName, anonInvites) {
25
+ let params = {};
26
+ let defaultParams = undefined;
27
+ const { file: paramsFile, changeProc } = await (0, single_file_1.initSyncedFile)(fs, fileName, newFile => newFile.writeJSON(currentToFileJSON()));
28
+ if (!paramsFile.isNew) {
29
+ ({ defaultParams, params } = parseFromJSON(await paramsFile.readJSON()));
75
30
  }
76
- static async makeAndInit(file, anonInvites) {
77
- const ownParams = new OwnSendingParams(anonInvites);
78
- await ownParams.fileProc.start(file, () => ownParams.toFileJSON());
79
- await ownParams.absorbChangesFromFile();
80
- if (!ownParams.defaultParams) {
81
- await ownParams.setDefaultParams();
82
- }
83
- return ownParams;
31
+ const { stopFileWatching, triggerUpload } = (0, single_file_1.watchAndApplyChangesFromOtherDevices)(paramsFile, changeProc, onChangeFromOtherDevices, doOnConflict);
32
+ if (paramsFile.isNew) {
33
+ await setDefaultParams();
84
34
  }
85
- async setDefaultParams() {
86
- await this.fileProc.order.startOrChain(async () => {
87
- const invites = this.anonInvites.getAll();
35
+ async function setDefaultParams() {
36
+ await changeProc.startOrChain(async () => {
37
+ const invites = anonInvites.getAll();
88
38
  const defaultInvite = invites.get(DEFAULT_INVITE_LABEL);
89
39
  if (defaultInvite) {
90
- this.defaultParams = {
40
+ defaultParams = {
91
41
  timestamp: 0,
92
42
  invitation: defaultInvite.invite
93
43
  };
94
44
  }
95
45
  else {
96
- const invitation = await this.anonInvites.create(DEFAULT_INVITE_LABEL, DEFAULT_INVITE_MAX_MSG_SIZE);
97
- this.defaultParams = {
46
+ const invitation = await anonInvites.create(DEFAULT_INVITE_LABEL, DEFAULT_INVITE_MAX_MSG_SIZE);
47
+ defaultParams = {
98
48
  timestamp: 0,
99
49
  invitation
100
50
  };
101
51
  }
102
- await this.persist();
52
+ await persist();
103
53
  });
104
54
  }
105
- toFileJSON() {
55
+ function currentToFileJSON() {
106
56
  return {
107
- default: this.defaultParams,
108
- senderSpecific: Array.from(this.params.values())
57
+ default: defaultParams,
58
+ senderSpecific: Object.values(params)
109
59
  };
110
60
  }
111
- async persist() {
112
- await this.fileProc.save(this.toFileJSON(), false);
113
- }
114
- async absorbChangesFromFile() {
115
- const { json } = await this.fileProc.get(false);
116
- // we may add checks to json data
117
- this.params.clear();
118
- json.senderSpecific.forEach(p => this.params.set(p.address, p));
119
- this.defaultParams = json.default;
61
+ async function persist() {
62
+ await paramsFile.writeJSON(currentToFileJSON());
63
+ triggerUpload();
120
64
  }
121
- async onFileEvent(ev) {
122
- if (ev.src === 'local') {
123
- return;
65
+ function parseFromJSON(json) {
66
+ const params = {};
67
+ for (const p of json.senderSpecific) {
68
+ params[p.address] = p;
124
69
  }
125
- if (ev.type === 'removed') {
126
- throw new Error(`Unexpected removal of file with invites info`);
127
- }
128
- if (ev.type !== 'file-change') {
129
- return;
130
- }
131
- await this.fileProc.order.startOrChain(() => this.absorbChangesFromFile());
70
+ return {
71
+ defaultParams: json.default,
72
+ params
73
+ };
74
+ }
75
+ function onChangeFromOtherDevices(newVersion) {
76
+ return changeProc.startOrChain(async () => {
77
+ const { version, json } = await paramsFile.v.readJSON();
78
+ if (version === newVersion) {
79
+ ({ defaultParams, params } = parseFromJSON(json));
80
+ }
81
+ });
82
+ }
83
+ function doOnConflict() {
84
+ return changeProc.startOrChain(async () => {
85
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
86
+ const { remote, state } = await paramsFile.v.sync.status(false);
87
+ if ((state !== 'conflicting') || !(remote === null || remote === void 0 ? void 0 : remote.latest)) {
88
+ return;
89
+ }
90
+ const onRemote = parseFromJSON((await paramsFile.v.readJSON({ remoteVersion: remote.latest })).json);
91
+ let uploadLocal = false;
92
+ if (!onRemote.defaultParams && defaultParams) {
93
+ uploadLocal = true;
94
+ }
95
+ else if (onRemote.defaultParams && !defaultParams) {
96
+ defaultParams = onRemote.defaultParams;
97
+ }
98
+ else if (onRemote.defaultParams && defaultParams) {
99
+ if (onRemote.defaultParams.timestamp >= defaultParams.timestamp) {
100
+ defaultParams = onRemote.defaultParams;
101
+ }
102
+ else {
103
+ uploadLocal = true;
104
+ }
105
+ }
106
+ for (const [addr, p] of Object.entries(params)) {
107
+ const remP = onRemote.params[addr];
108
+ if (!remP) {
109
+ uploadLocal = true;
110
+ continue;
111
+ }
112
+ if ((0, json_utils_1.deepEqual)(p, remP)) {
113
+ continue;
114
+ }
115
+ const tsP = Math.max((_b = (_a = p === null || p === void 0 ? void 0 : p.inUse) === null || _a === void 0 ? void 0 : _a.timestamp) !== null && _b !== void 0 ? _b : 0, (_d = (_c = p === null || p === void 0 ? void 0 : p.suggested) === null || _c === void 0 ? void 0 : _c.timestamp) !== null && _d !== void 0 ? _d : 0);
116
+ const tsRem = Math.max((_f = (_e = remP === null || remP === void 0 ? void 0 : remP.inUse) === null || _e === void 0 ? void 0 : _e.timestamp) !== null && _f !== void 0 ? _f : 0, (_h = (_g = remP === null || remP === void 0 ? void 0 : remP.suggested) === null || _g === void 0 ? void 0 : _g.timestamp) !== null && _h !== void 0 ? _h : 0);
117
+ if (tsP > tsRem) {
118
+ uploadLocal = true;
119
+ }
120
+ else if (tsP < tsRem) {
121
+ params[addr] = remP;
122
+ }
123
+ else if (tsP !== 0) {
124
+ if (((_j = remP.inUse) === null || _j === void 0 ? void 0 : _j.timestamp) === tsP) {
125
+ params[addr] = remP;
126
+ }
127
+ else if (((_k = p.inUse) === null || _k === void 0 ? void 0 : _k.timestamp) === tsP) {
128
+ uploadLocal = true;
129
+ }
130
+ }
131
+ }
132
+ if (uploadLocal) {
133
+ await paramsFile.writeJSON(currentToFileJSON());
134
+ await paramsFile.v.sync.upload({ uploadVersion: remote.latest + 1 });
135
+ }
136
+ else {
137
+ await paramsFile.v.sync.adoptRemote({ remoteVersion: remote.latest });
138
+ }
139
+ });
140
+ }
141
+ async function getUpdated(address) {
142
+ return changeProc.startOrChain(async () => {
143
+ let p = params[address];
144
+ if (p) {
145
+ if (p.suggested) {
146
+ return p.suggested;
147
+ }
148
+ else if (p.inUse) {
149
+ return; // undefined, cause params are known to correspondent,
150
+ // and there are no params to suggest for future use.
151
+ }
152
+ }
153
+ // XXX Or, instead, should we set defaultParams?
154
+ if (!defaultParams) {
155
+ return;
156
+ }
157
+ p = {
158
+ address,
159
+ suggested: (0, json_utils_1.copy)(defaultParams)
160
+ };
161
+ p.suggested.timestamp = Date.now();
162
+ params[p.address] = p;
163
+ await persist();
164
+ return p.suggested;
165
+ });
166
+ }
167
+ function setAsUsed(address, invite) {
168
+ return changeProc.startOrChain(async () => {
169
+ const p = params[address];
170
+ if (!(p === null || p === void 0 ? void 0 : p.suggested) || (p.suggested.invitation !== invite)) {
171
+ return;
172
+ }
173
+ p.inUse = p.suggested;
174
+ p.suggested = undefined;
175
+ await persist();
176
+ });
132
177
  }
133
- async close() {
134
- await this.anonInvites.close();
135
- await this.fileProc.close();
178
+ async function close() {
179
+ anonInvites.close();
180
+ stopFileWatching();
136
181
  }
182
+ return {
183
+ close,
184
+ getUpdated,
185
+ setAsUsed
186
+ };
137
187
  }
138
- exports.OwnSendingParams = OwnSendingParams;
139
- Object.freeze(OwnSendingParams.prototype);
140
- Object.freeze(OwnSendingParams);
141
188
  Object.freeze(exports);
@@ -1,17 +1,7 @@
1
1
  import { SendingParamsHolder } from '../sending-params';
2
- export { SendingParams } from '../msg/common';
3
- type ExposedFuncs = SendingParamsHolder['otherSides'];
4
- type WritableFile = web3n.files.WritableFile;
5
- type FileEvent = web3n.files.FileEvent;
6
- export declare class ParamsFromOthers {
7
- private readonly params;
8
- private readonly fileProc;
9
- private constructor();
10
- static makeAndInit(file: WritableFile): Promise<ParamsFromOthers>;
11
- private absorbRemoteChanges;
12
- protected onFileEvent(ev: FileEvent): Promise<void>;
13
- getFor: ExposedFuncs['get'];
14
- setFor: ExposedFuncs['set'];
15
- private persist;
16
- close(): Promise<void>;
17
- }
2
+ export type { SendingParams } from '../msg/common';
3
+ type WritableFS = web3n.files.WritableFS;
4
+ export type ParamsFromOthers = Awaited<ReturnType<typeof makeParamsFromOthers>>;
5
+ export declare function makeParamsFromOthers(fs: WritableFS, fileName: string): Promise<SendingParamsHolder['otherSides'] & {
6
+ close: () => Promise<void>;
7
+ }>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2017 - 2018, 2025 3NSoft Inc.
3
+ Copyright (C) 2017 - 2018, 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,73 +16,103 @@
16
16
  this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.ParamsFromOthers = void 0;
20
- const file_based_json_1 = require("../../../lib-client/xsp-fs/util/file-based-json");
21
- class ParamsFromOthers {
22
- constructor() {
23
- this.params = new Map();
24
- this.getFor = (address) => {
25
- const p = this.params.get(address);
26
- if (!p) {
27
- return;
19
+ exports.makeParamsFromOthers = makeParamsFromOthers;
20
+ const single_file_1 = require("../../../lib-common/dataset-sync/single-file");
21
+ async function makeParamsFromOthers(fs, fileName) {
22
+ const { file: paramsFile, changeProc } = await (0, single_file_1.initSyncedFile)(fs, fileName, newFile => newFile.writeJSON([]));
23
+ function parseParamsFromJSON(json) {
24
+ if (Array.isArray(json)) {
25
+ // older serialization form
26
+ const p = {};
27
+ for (const paramsForSending of json) {
28
+ const { address, timestamp, auth, invitation } = paramsForSending;
29
+ p[address] = { timestamp, auth, invitation };
28
30
  }
29
- return copyParams(p);
30
- };
31
- this.setFor = (address, params) => {
32
- return this.fileProc.order.startOrChain(async () => {
33
- const existing = this.params.get(address);
34
- if (existing && (existing.timestamp >= params.timestamp)) {
35
- return;
36
- }
37
- const p = { address };
38
- copyParams(params, p);
39
- this.params.set(p.address, p);
40
- await this.persist();
41
- });
42
- };
43
- this.fileProc = new file_based_json_1.JsonFileProc(this.onFileEvent.bind(this));
44
- Object.seal(this);
45
- }
46
- static async makeAndInit(file) {
47
- const paramsFromOthers = new ParamsFromOthers();
48
- await paramsFromOthers.fileProc.start(file, []);
49
- await paramsFromOthers.absorbRemoteChanges();
50
- return paramsFromOthers;
31
+ return p;
32
+ }
33
+ else if (json && (typeof json === 'object')) {
34
+ return json;
35
+ }
36
+ else {
37
+ return {};
38
+ }
51
39
  }
52
- async absorbRemoteChanges() {
53
- // XXX
54
- // - check for changes: what is needed here from fileProc, and what is
55
- // generic in absorbing remote changes to refactor it into JsonFileProc
56
- // - absorb and sync, if needed: what can be in JsonFileProc
57
- // Code from pre-v.sync:
58
- // const { json } = await this.fileProc.get();
59
- // this.setFromJSON(json);
40
+ let params = parseParamsFromJSON(await paramsFile.readJSON());
41
+ async function onChangeFromOtherDevices(newVersion) {
42
+ changeProc.startOrChain(async () => {
43
+ const { version, json } = await paramsFile.v.readJSON();
44
+ if (version === newVersion) {
45
+ const incomingParams = parseParamsFromJSON(json);
46
+ mergeIncomingParams(incomingParams);
47
+ }
48
+ });
60
49
  }
61
- async onFileEvent(ev) {
62
- if (ev.src === 'local') {
63
- return;
50
+ function mergeIncomingParams(incomingParams) {
51
+ for (const [address, inP] of Object.entries(incomingParams)) {
52
+ const existing = params[address];
53
+ if (existing && (existing.timestamp >= inP.timestamp)) {
54
+ return;
55
+ }
56
+ params[address] = inP;
64
57
  }
65
- switch (ev.type) {
66
- case 'file-change':
67
- await this.fileProc.order.startOrChain(() => this.absorbRemoteChanges());
68
- break;
69
- case 'removed':
70
- throw new Error(`Unexpected removal of file with invites info`);
71
- default:
58
+ }
59
+ function doOnConflict() {
60
+ return changeProc.startOrChain(async () => {
61
+ const { remote, state } = await paramsFile.v.sync.status(false);
62
+ if ((state !== 'conflicting') || !(remote === null || remote === void 0 ? void 0 : remote.latest)) {
72
63
  return;
64
+ }
65
+ const onRemote = parseParamsFromJSON((await paramsFile.v.readJSON({ remoteVersion: remote.latest })).json);
66
+ const numRemRecs = Object.keys(onRemote).length;
67
+ const numExistingRecs = Object.keys(params).length;
68
+ let paramsWhereUpdated = (numExistingRecs > numRemRecs);
69
+ for (const [address, inP] of Object.entries(onRemote)) {
70
+ const existing = params[address];
71
+ if (existing.timestamp < inP.timestamp) {
72
+ params[address] = inP;
73
+ paramsWhereUpdated = true;
74
+ }
75
+ else if (existing.timestamp > inP.timestamp) {
76
+ paramsWhereUpdated = true;
77
+ }
78
+ }
79
+ if (paramsWhereUpdated) {
80
+ await paramsFile.v.sync.upload({ uploadVersion: remote.latest + 1 });
81
+ }
82
+ else {
83
+ await paramsFile.v.sync.adoptRemote({ remoteVersion: remote.latest });
84
+ }
85
+ });
86
+ }
87
+ const { stopFileWatching, triggerUpload } = (0, single_file_1.watchAndApplyChangesFromOtherDevices)(paramsFile, changeProc, onChangeFromOtherDevices, doOnConflict);
88
+ function get(address) {
89
+ const p = params[address];
90
+ if (!p) {
91
+ return;
73
92
  }
93
+ return copyParams(p);
74
94
  }
75
- async persist() {
76
- const json = Array.from(this.params.values());
77
- await this.fileProc.save(json, false);
95
+ ;
96
+ async function set(address, params) {
97
+ return changeProc.startOrChain(async () => {
98
+ const existing = params[address];
99
+ if (existing && (existing.timestamp >= params.timestamp)) {
100
+ return;
101
+ }
102
+ params[address] = copyParams(params);
103
+ await paramsFile.writeJSON(params);
104
+ triggerUpload();
105
+ });
78
106
  }
79
- async close() {
80
- await this.fileProc.close();
107
+ async function close() {
108
+ stopFileWatching();
81
109
  }
110
+ return {
111
+ get,
112
+ set,
113
+ close
114
+ };
82
115
  }
83
- exports.ParamsFromOthers = ParamsFromOthers;
84
- Object.freeze(ParamsFromOthers.prototype);
85
- Object.freeze(ParamsFromOthers);
86
116
  /**
87
117
  * This copies SendingParams' fields, returning a copy, which was either
88
118
  * created, or given.
@@ -7,6 +7,7 @@ export type { makeCryptor } from 'ecma-nacl-cryptors';
7
7
  type RequestedCAPs = web3n.caps.common.RequestedCAPs;
8
8
  type W3N = web3n.caps.common.W3N;
9
9
  type ProgressCB = web3n.startup.ProgressCB;
10
+ type FSItem = web3n.files.FSItem;
10
11
  export interface CoreConf {
11
12
  dataDir: string;
12
13
  signUpUrl: string;
@@ -27,7 +28,7 @@ export declare class Core {
27
28
  private closingProc;
28
29
  private isInitialized;
29
30
  private constructor();
30
- static make(conf: CoreConf, makeNet: MakeNet, makeResolver: ServiceLocatorMaker, makeCryptor: makeCryptor, random: AsyncRNG): Core;
31
+ static make(conf: CoreConf, makeNet: MakeNet, makeResolver: ServiceLocatorMaker, sysFilesOnDevice: () => Promise<FSItem>, makeCryptor: makeCryptor, random: AsyncRNG): Core;
31
32
  start(saveStorageKeyForAutologin?: (storageKey: Uint8Array) => void): {
32
33
  capsForStartup: web3n.startup.W3N;
33
34
  coreInit: Promise<string>;
@@ -31,7 +31,7 @@ const constants_1 = require("./storage/common/constants");
31
31
  const config_1 = require("./asmail/config");
32
32
  const deferred_1 = require("../lib-common/processes/deferred");
33
33
  class Core {
34
- constructor(makeNet, makeResolver, makeCryptor, random, appDirs, logger, signUpUrl) {
34
+ constructor(makeNet, makeResolver, makeCryptor, random, appDirs, sysFilesOnDevice, logger, signUpUrl) {
35
35
  this.makeNet = makeNet;
36
36
  this.makeResolver = makeResolver;
37
37
  this.random = random;
@@ -43,14 +43,14 @@ class Core {
43
43
  this.closingProc = undefined;
44
44
  this.isInitialized = false;
45
45
  this.cryptor = makeCryptor(this.logger.logError, this.logger.logWarning);
46
- this.storages = new storage_1.Storages(this.cryptor.cryptor.sbox, this.random, this.appDirs.storagePathFor);
46
+ this.storages = new storage_1.Storages(this.cryptor.cryptor.sbox, this.random, this.appDirs.storagePathFor, sysFilesOnDevice);
47
47
  this.asmail = new asmail_1.ASMail(this.cryptor.cryptor.sbox, this.random, this.makeNet, this.appDirs.inboxPathFor, this.logger);
48
48
  Object.seal(this);
49
49
  }
50
- static make(conf, makeNet, makeResolver, makeCryptor, random) {
50
+ static make(conf, makeNet, makeResolver, sysFilesOnDevice, makeCryptor, random) {
51
51
  const dirs = (0, app_files_1.appDirs)(conf.dataDir);
52
52
  const logger = (0, log_to_file_1.makeLogger)(dirs.getUtilFS());
53
- const core = new Core(makeNet, makeResolver, makeCryptor, random, dirs, logger, conf.signUpUrl);
53
+ const core = new Core(makeNet, makeResolver, makeCryptor, random, dirs, sysFilesOnDevice, logger, conf.signUpUrl);
54
54
  return core;
55
55
  }
56
56
  start(saveStorageKeyForAutologin) {
@@ -33,7 +33,7 @@ Object.defineProperty(exports, "KEY_USE", { enumerable: true, get: function () {
33
33
  const INTRO_KEYS_FOLDER = 'introductory-keys';
34
34
  async function makeKeyrings(cryptor, random, logger, fs, localFS, getSigner, pkeyOnServer) {
35
35
  const workLabel = cryptor_work_labels_1.cryptoWorkLabels.makeRandom('asmail');
36
- const introKeysFS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(fs, INTRO_KEYS_FOLDER);
36
+ const introKeysFS = await (0, fs_sync_utils_1.getOrMakeDirOnInit)(fs, INTRO_KEYS_FOLDER);
37
37
  const publishedKeys = await (0, published_intro_key_1.makeHolderOfPublishedIntroKey)(introKeysFS, getSigner, random, pkeyOnServer);
38
38
  const peerKeys = await (0, peer_keys_1.makePeersKeyring)(fs, localFS, random, logger);
39
39
  async function generateIntroKeysToSendMsg(address, introPKeyFromServer) {
@@ -26,17 +26,23 @@ const MAX_NUM_OF_PREV = 3;
26
26
  const INTRO_KEY_ON_SERVER_FILE = `published-on-server.json`;
27
27
  async function makeHolderOfPublishedIntroKey(introKeysFS, getSigner, random, pkeyOnServer) {
28
28
  var _a;
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();
29
+ const { file: keysFile, changeProc } = await (0, single_file_1.initSyncedFile)(introKeysFS, INTRO_KEY_ON_SERVER_FILE, newFile => newFile.writeJSON({ previous: [] }));
30
+ let keys = await keysFile.readJSON();
31
+ async function onChangeFromOtherDevices(newVersion) {
32
+ changeProc.startOrChain(async () => {
33
+ const { version, json } = await keysFile.v.readJSON();
34
+ if (version === newVersion) {
35
+ keys = json;
36
+ }
37
+ });
33
38
  }
34
- await setKeysFromFile();
35
- const { changeProc, stopFileWatching, triggerUpload } = (0, single_file_1.watchAndApplyChangesFromOtherDevices)(keysFile, () => changeProc.startOrChain(setKeysFromFile), doOnConflict);
39
+ const { stopFileWatching, triggerUpload } = (0, single_file_1.watchAndApplyChangesFromOtherDevices)(keysFile, changeProc, onChangeFromOtherDevices, doOnConflict);
36
40
  if (keysFile.isNew) {
37
41
  await update();
38
42
  }
39
- updateIfCurrentExpired();
43
+ else {
44
+ updateIfCurrentExpired();
45
+ }
40
46
  let periodicExpiryCheck = setTimeout(updateIfCurrentExpired, UPDATE_BEFORE_EXPIRY * 1000 / 20);
41
47
  (_a = periodicExpiryCheck.unref) === null || _a === void 0 ? void 0 : _a.call(periodicExpiryCheck);
42
48
  async function makeNewIntroKey() {
@@ -77,8 +83,8 @@ async function makeHolderOfPublishedIntroKey(introKeysFS, getSigner, random, pke
77
83
  function doOnConflict() {
78
84
  return changeProc.startOrChain(async () => {
79
85
  var _a, _b, _c, _d, _e, _f;
80
- const { remote } = await keysFile.v.sync.status(false);
81
- if (!remote) {
86
+ const { remote, state } = await keysFile.v.sync.status(false);
87
+ if ((state !== 'conflicting') || !(remote === null || remote === void 0 ? void 0 : remote.latest)) {
82
88
  return;
83
89
  }
84
90
  const onRemote = (await keysFile.v.readJSON({ remoteVersion: remote.latest })).json;
@@ -98,12 +104,6 @@ async function makeHolderOfPublishedIntroKey(introKeysFS, getSigner, random, pke
98
104
  await pkeyOnServer.setOnServer((_f = (_e = keys.current) === null || _e === void 0 ? void 0 : _e.certs) !== null && _f !== void 0 ? _f : null);
99
105
  await keysFile.v.sync.upload({ uploadVersion: remote.latest + 1 });
100
106
  }
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
107
  });
108
108
  }
109
109
  /**
@@ -30,9 +30,9 @@ const LOGS_DIR = 'peer-keys-logs';
30
30
  const PEER_KEYS_FOLDER = 'peer-keys-db';
31
31
  const NUM_OF_LOGS_TO_START_CUT = 200;
32
32
  async function makePeersKeyring(keyringFS, localFS, random, logger) {
33
- const logsFS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(keyringFS, LOGS_DIR);
33
+ const logsFS = await (0, fs_sync_utils_1.getOrMakeDirOnInit)(keyringFS, LOGS_DIR);
34
34
  const logsLocalFS = await localFS.writableSubRoot(LOGS_DIR);
35
- const peerKeysFS = await (0, fs_sync_utils_1.getOrMakeSyncedDirOnInit)(keyringFS, PEER_KEYS_FOLDER);
35
+ const peerKeysFS = await (0, fs_sync_utils_1.getOrMakeDirOnInit)(keyringFS, PEER_KEYS_FOLDER);
36
36
  // XXX log and saving reduction(!):
37
37
  // - we don't need to log message counts
38
38
  // - we don't have to immediately save changes in message counts
@@ -165,11 +165,10 @@ async function makePeersKeyring(keyringFS, localFS, random, logger) {
165
165
  await logs.recordGettingCryptoFromPeer(entryToLog, peerAddr);
166
166
  }
167
167
  async function periodicCheckAndUploadOfData() {
168
- // XXX disable uploading for a moment by commenting out
169
- // if (logs.numOfLogs() >= NUM_OF_LOGS_TO_START_CUT) {
170
- // const syncedIndexDbVersion = await keysDB.saveAndSync();
171
- // await logs.cutLogOnDatasetSyncAndUpload(syncedIndexDbVersion);
172
- // }
168
+ if (logs.numOfLogs() >= NUM_OF_LOGS_TO_START_CUT) {
169
+ const syncedIndexDbVersion = await keysDB.saveAndSync();
170
+ await logs.cutLogOnDatasetSyncAndUpload(syncedIndexDbVersion);
171
+ }
173
172
  }
174
173
  function millisBeforeNextRun() {
175
174
  return 30 * 60 * 1000 + Math.floor(10 * 60 * 1000 * Math.random());
@@ -17,10 +17,11 @@ export declare class Storages implements FactoryOfFSs {
17
17
  private readonly cryptor;
18
18
  private readonly random;
19
19
  private readonly storageDirForUser;
20
+ private readonly sysFilesOnDevice;
20
21
  private synced;
21
22
  private local;
22
23
  private preCloseWaits;
23
- constructor(cryptor: AsyncSBoxCryptor, random: AsyncRNG, storageDirForUser: StoragePathForUser);
24
+ constructor(cryptor: AsyncSBoxCryptor, random: AsyncRNG, storageDirForUser: StoragePathForUser, sysFilesOnDevice: () => Promise<FSItem>);
24
25
  makeStorageCAP(appDomain: string, policy: StoragePolicy): {
25
26
  cap: Service;
26
27
  close: () => void;
@@ -216,10 +216,11 @@ class StorageAndFS {
216
216
  Object.freeze(StorageAndFS.prototype);
217
217
  Object.freeze(StorageAndFS);
218
218
  class Storages {
219
- constructor(cryptor, random, storageDirForUser) {
219
+ constructor(cryptor, random, storageDirForUser, sysFilesOnDevice) {
220
220
  this.cryptor = cryptor;
221
221
  this.random = random;
222
222
  this.storageDirForUser = storageDirForUser;
223
+ this.sysFilesOnDevice = sysFilesOnDevice;
223
224
  this.synced = undefined;
224
225
  this.local = undefined;
225
226
  this.preCloseWaits = new Set();
@@ -349,7 +350,7 @@ class Storages {
349
350
  fs = await this.local.userFS();
350
351
  }
351
352
  else if (type === 'device') {
352
- fs = await (0, system_folders_1.userFilesOnDevice)();
353
+ return await (0, system_folders_1.userFilesOnDevice)();
353
354
  }
354
355
  else {
355
356
  throw new Error(`Unknown storage type ${type}`);
@@ -375,7 +376,7 @@ class Storages {
375
376
  };
376
377
  }
377
378
  else if (type === 'device') {
378
- return (0, system_folders_1.sysFilesOnDevice)();
379
+ return this.sysFilesOnDevice();
379
380
  }
380
381
  else {
381
382
  throw new Error(`Unknown storage type ${type}`);