core-3nweb-client-lib 0.45.0 → 0.45.2

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.
@@ -0,0 +1,2 @@
1
+ import { DnsResolver } from "./service-locator";
2
+ export declare function dohAt(dohServerUrl: string): DnsResolver;
@@ -0,0 +1,68 @@
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.dohAt = dohAt;
20
+ const https = require("https");
21
+ const request_utils_1 = require("./request-utils");
22
+ const service_locator_1 = require("./service-locator");
23
+ function dohAt(dohServerUrl) {
24
+ async function resolveTxt(domain) {
25
+ const answer = await sendDohQuestion(dohServerUrl, domain, 'TXT');
26
+ let txt = [];
27
+ for (const { data: line } of answer) {
28
+ txt.push([line]);
29
+ }
30
+ return txt;
31
+ }
32
+ return { resolveTxt };
33
+ }
34
+ async function sendDohQuestion(serverUrl, domain, type) {
35
+ const opts = {
36
+ method: 'GET',
37
+ url: `${serverUrl}?name=${domain}&type=${type}`,
38
+ responseType: 'json'
39
+ };
40
+ const httpsOpts = (0, request_utils_1.formHttpsReqOpts)(opts);
41
+ httpsOpts.headers.accept = 'application/dns-json';
42
+ const reply = await (0, request_utils_1.processRequest)(opts => https.request(opts), httpsOpts, opts, undefined)
43
+ .catch((exc) => {
44
+ if (exc.type === 'connect') {
45
+ throw { code: service_locator_1.DNS_ERR_CODE.ECONNREFUSED, hostname: domain, cause: exc };
46
+ }
47
+ else {
48
+ throw { code: service_locator_1.DNS_ERR_CODE.ESERVFAIL, hostname: domain, cause: exc };
49
+ }
50
+ });
51
+ const { status, data } = reply;
52
+ if (status !== 200) {
53
+ throw {
54
+ code: service_locator_1.DNS_ERR_CODE.ESERVFAIL, hostname: domain,
55
+ message: `status ${reply.status} from DoH server`
56
+ };
57
+ }
58
+ if (data.Status !== 0) {
59
+ throw { code: service_locator_1.DNS_ERR_CODE.NOTFOUND, hostname: domain };
60
+ }
61
+ if (data.Answer) {
62
+ return data.Answer;
63
+ }
64
+ else {
65
+ throw { code: service_locator_1.DNS_ERR_CODE.NODATA, hostname: domain };
66
+ }
67
+ }
68
+ Object.freeze(exports);
@@ -94,7 +94,7 @@ async function attachRequestReaders(clReq, opts) {
94
94
  }
95
95
  catch (exc) {
96
96
  if (exc.cause === TIMEOUT_FLAG) {
97
- clReq.abort();
97
+ clReq.destroy();
98
98
  }
99
99
  throw exc;
100
100
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2025 3NSoft Inc.
3
+ Copyright (C) 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
@@ -18,6 +18,7 @@
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.checkServicesStartingFromSignup = checkServicesStartingFromSignup;
20
20
  const _3nweb_signup_1 = require("./3nweb-signup");
21
+ const doh_1 = require("./doh");
21
22
  const service_locator_1 = require("./service-locator");
22
23
  const dns_1 = require("dns");
23
24
  async function checkServicesStartingFromSignup(client, signupUrl, signupToken, progress) {
@@ -126,16 +127,7 @@ async function checkSignup(client, signupURL, signupToken) {
126
127
  };
127
128
  }
128
129
  }
129
- const srvLocator = (0, service_locator_1.makeServiceLocator)({
130
- resolveTxt: domain => new Promise((resolve, reject) => (0, dns_1.resolveTxt)(domain, (err, texts) => {
131
- if (err) {
132
- reject(err);
133
- }
134
- else {
135
- resolve(texts);
136
- }
137
- }))
138
- });
130
+ const srvLocator = (0, service_locator_1.makeServiceLocator)({ resolveTxt: dns_1.promises.resolveTxt }, (0, doh_1.dohAt)(`https://cloudflare-dns.com/dns-query`), (0, doh_1.dohAt)(`https://dns.google/resolve`));
139
131
  async function checkUserDomainDNS(service, domain) {
140
132
  try {
141
133
  const serviceUrl = await (srvLocator(service))(`u@${domain}`);
@@ -26,12 +26,20 @@ export declare function mailerIdInfoAt(client: NetClient, url: string): Promise<
26
26
  * @param url
27
27
  */
28
28
  export declare function storageInfoAt(client: NetClient, url: string): Promise<StorageRootRoute>;
29
+ export declare const DNS_ERR_CODE: {
30
+ NODATA: string;
31
+ NOTFOUND: string;
32
+ ESERVFAIL: string;
33
+ ECONNREFUSED: string;
34
+ ETIMEOUT: string;
35
+ };
29
36
  export type ServiceTypeDNSLabel = 'mailerid' | 'asmail' | '3nstorage';
30
37
  export type ServiceLocatorMaker = (serviceLabel: ServiceTypeDNSLabel) => ServiceLocator;
31
38
  export type ServiceLocator = (address: string) => Promise<string>;
32
- export declare function makeServiceLocator(resolver: {
39
+ export interface DnsResolver {
33
40
  resolveTxt: (typeof dnsPromises)['resolveTxt'];
34
- }): ServiceLocatorMaker;
41
+ }
42
+ export declare function makeServiceLocator(...resolvers: DnsResolver[]): ServiceLocatorMaker;
35
43
  /**
36
44
  * @param resolver
37
45
  * @param address
@@ -16,6 +16,7 @@
16
16
  this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.DNS_ERR_CODE = void 0;
19
20
  exports.asmailInfoAt = asmailInfoAt;
20
21
  exports.mailerIdInfoAt = mailerIdInfoAt;
21
22
  exports.storageInfoAt = storageInfoAt;
@@ -215,43 +216,55 @@ function getRecordAtStartOf(txt) {
215
216
  value: txt
216
217
  };
217
218
  }
218
- const DNS_ERR_CODE = {
219
+ exports.DNS_ERR_CODE = {
219
220
  NODATA: 'ENODATA',
220
221
  NOTFOUND: 'ENOTFOUND',
221
222
  ESERVFAIL: 'ESERVFAIL',
222
223
  ECONNREFUSED: 'ECONNREFUSED',
223
224
  ETIMEOUT: 'ETIMEOUT'
224
225
  };
225
- Object.freeze(DNS_ERR_CODE);
226
- function makeServiceLocator(resolver) {
226
+ Object.freeze(exports.DNS_ERR_CODE);
227
+ function makeServiceLocator(...resolvers) {
228
+ if (resolvers.length === 0) {
229
+ throw Error(`no DNS resolvers given`);
230
+ }
227
231
  return serviceLabel => async (address) => {
228
- try {
229
- const domain = domainOfAddress(address);
230
- const txtRecords = await resolver.resolveTxt(domain);
231
- const recValue = extractPair(txtRecords, serviceLabel);
232
- if (!recValue) {
233
- throw noServiceRecordExc(address);
234
- }
235
- const url = checkAndPrepareURL(recValue);
236
- return url;
237
- }
238
- catch (err) {
239
- const { code, hostname, message } = err;
240
- if (code === DNS_ERR_CODE.NODATA) {
241
- throw noServiceRecordExc(address);
242
- }
243
- else if ((code === DNS_ERR_CODE.ESERVFAIL)
244
- || (code === DNS_ERR_CODE.ECONNREFUSED)
245
- || (code === DNS_ERR_CODE.ETIMEOUT)) {
246
- throw noConnectionExc({ code, hostname, message });
247
- }
248
- else if (hostname) {
249
- throw domainNotFoundExc(address, { code, hostname, message });
232
+ const domain = domainOfAddress(address);
233
+ let prevConnectionExc = undefined;
234
+ for (const resolver of resolvers) {
235
+ try {
236
+ const txtRecords = await resolver.resolveTxt(domain);
237
+ const recValue = extractPair(txtRecords, serviceLabel);
238
+ if (!recValue) {
239
+ throw noServiceRecordExc(address);
240
+ }
241
+ const url = checkAndPrepareURL(recValue);
242
+ return url;
250
243
  }
251
- else {
252
- throw err;
244
+ catch (err) {
245
+ const { code, hostname, message } = err;
246
+ if (code === exports.DNS_ERR_CODE.NODATA) {
247
+ throw noServiceRecordExc(address);
248
+ }
249
+ else if ((code === exports.DNS_ERR_CODE.ESERVFAIL)
250
+ || (code === exports.DNS_ERR_CODE.ECONNREFUSED)
251
+ || (code === exports.DNS_ERR_CODE.ETIMEOUT)) {
252
+ if (!prevConnectionExc) {
253
+ prevConnectionExc = noConnectionExc({ code, hostname, message });
254
+ }
255
+ }
256
+ else if ((code === exports.DNS_ERR_CODE.NOTFOUND) || hostname) {
257
+ throw domainNotFoundExc(address, { code, hostname, message });
258
+ }
259
+ else {
260
+ if (!prevConnectionExc) {
261
+ prevConnectionExc = err;
262
+ }
263
+ throw err;
264
+ }
253
265
  }
254
266
  }
267
+ throw prevConnectionExc;
255
268
  };
256
269
  }
257
270
  /**
@@ -143,6 +143,7 @@ export declare class FolderNode extends NodeInFS<FolderPersistance> {
143
143
  createFile(name: string, exclusive: boolean): Promise<FileNode>;
144
144
  createLink(name: string, params: LinkParameters<any>): Promise<void>;
145
145
  removeChild(f: NodeInFS<any>): Promise<void>;
146
+ removeChildEntryUnconditionally(name: string): Promise<void>;
146
147
  private changeChildName;
147
148
  moveChildTo(childName: string, dst: FolderNode, nameInDst: string): Promise<void>;
148
149
  private moveChildOut;
@@ -612,6 +612,16 @@ class FolderNode extends node_in_fs_1.NodeInFS {
612
612
  // stays consistent
613
613
  this.callRemoveObjOn('local', f);
614
614
  }
615
+ async removeChildEntryUnconditionally(name) {
616
+ await this.doTransition(async (state, version) => {
617
+ const child = state.nodes[name];
618
+ if (!child) {
619
+ return [];
620
+ }
621
+ delete state.nodes[name];
622
+ return this.makeEntryRemovalEvent(version, 'local', child);
623
+ });
624
+ }
615
625
  changeChildName(oldName, newName) {
616
626
  return this.doTransition(async (state, version) => {
617
627
  const child = state.nodes[oldName];
@@ -146,32 +146,62 @@ class XspFS {
146
146
  if (typeof folderName !== 'string') {
147
147
  throw new Error('Cannot remove root folder');
148
148
  }
149
- const folder = (await parentFolder.getFolder(folderName)
150
- .catch(setExcPath(path)));
151
- if (!removeContent && !folder.isEmpty()) {
152
- throw (0, file_1.makeFileException)('notEmpty', path);
149
+ try {
150
+ const folder = (await parentFolder.getFolder(folderName)
151
+ .catch(setExcPath(path)));
152
+ if (!removeContent && !folder.isEmpty()) {
153
+ throw (0, file_1.makeFileException)('notEmpty', path);
154
+ }
155
+ // note that internal folder.delete() removes all children as a matter
156
+ // of not leaving inaccessible nodes, i.e. content is removed implicitly
157
+ await parentFolder.removeChild(folder);
158
+ }
159
+ catch (exc) {
160
+ if (exc.type === 'secondary') {
161
+ await parentFolder.removeChildEntryUnconditionally(folderName);
162
+ }
163
+ else {
164
+ throw exc;
165
+ }
153
166
  }
154
- // note that internal folder.delete() removes all children as a matter
155
- // of not leaving inaccessible nodes, i.e. content is removed implicitly
156
- await parentFolder.removeChild(folder);
157
167
  }
158
168
  async deleteFile(path) {
159
169
  const { fileName, folderPath } = split(path);
160
170
  const root = this.v.getRootIfNotClosed(path);
161
171
  const parentFolder = await root.getFolderInThisSubTree(folderPath)
162
172
  .catch(setExcPath(path));
163
- const file = await parentFolder.getFile(fileName)
164
- .catch(setExcPath(path));
165
- await parentFolder.removeChild(file);
173
+ try {
174
+ const file = await parentFolder.getFile(fileName)
175
+ .catch(setExcPath(path));
176
+ await parentFolder.removeChild(file);
177
+ }
178
+ catch (exc) {
179
+ if (exc.type === 'secondary') {
180
+ await parentFolder.removeChildEntryUnconditionally(fileName);
181
+ }
182
+ else {
183
+ throw exc;
184
+ }
185
+ }
166
186
  }
167
187
  async deleteLink(path) {
168
188
  const { fileName, folderPath } = split(path);
169
189
  const root = this.v.getRootIfNotClosed(path);
170
190
  const parentFolder = await root.getFolderInThisSubTree(folderPath)
171
191
  .catch(setExcPath(path));
172
- const link = await parentFolder.getLink(fileName)
173
- .catch(setExcPath(path));
174
- await parentFolder.removeChild(link);
192
+ try {
193
+ const link = await parentFolder.getLink(fileName)
194
+ .catch(setExcPath(path));
195
+ await parentFolder.removeChild(link);
196
+ }
197
+ catch (exc) {
198
+ if (exc.type === 'secondary') {
199
+ await parentFolder.removeChildEntryUnconditionally(fileName);
200
+ }
201
+ else {
202
+ throw exc;
203
+ }
204
+ }
175
205
  }
176
206
  async move(initPath, newPath) {
177
207
  const srcFolderPath = splitPathIntoParts(initPath);
@@ -13,3 +13,4 @@ export { DeviceFS } from './lib-client/local-files/device-fs';
13
13
  export { makeLogger } from './lib-client/logging/log-to-file';
14
14
  export declare const SYSTEM_DOMAIN = "3nweb.computer";
15
15
  export { checkServicesStartingFromSignup } from './lib-client/service-checks';
16
+ export { dohAt } from './lib-client/doh';
@@ -30,7 +30,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
30
30
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.checkServicesStartingFromSignup = exports.SYSTEM_DOMAIN = exports.makeLogger = exports.DeviceFS = exports.sysFolders = exports.appDirs = exports.makeNetClient = exports.makeServiceLocator = exports.ASMail = exports.SignIn = exports.reverseDomain = exports.Storages = exports.IdManager = exports.SignUp = void 0;
33
+ exports.dohAt = exports.checkServicesStartingFromSignup = exports.SYSTEM_DOMAIN = exports.makeLogger = exports.DeviceFS = exports.sysFolders = exports.appDirs = exports.makeNetClient = exports.makeServiceLocator = exports.ASMail = exports.SignIn = exports.reverseDomain = exports.Storages = exports.IdManager = exports.SignUp = void 0;
34
34
  __exportStar(require("./core"), exports);
35
35
  var sign_up_1 = require("./core/startup/sign-up");
36
36
  Object.defineProperty(exports, "SignUp", { enumerable: true, get: function () { return sign_up_1.SignUp; } });
@@ -58,4 +58,6 @@ Object.defineProperty(exports, "makeLogger", { enumerable: true, get: function (
58
58
  exports.SYSTEM_DOMAIN = '3nweb.computer';
59
59
  var service_checks_1 = require("./lib-client/service-checks");
60
60
  Object.defineProperty(exports, "checkServicesStartingFromSignup", { enumerable: true, get: function () { return service_checks_1.checkServicesStartingFromSignup; } });
61
+ var doh_1 = require("./lib-client/doh");
62
+ Object.defineProperty(exports, "dohAt", { enumerable: true, get: function () { return doh_1.dohAt; } });
61
63
  Object.freeze(exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "core-3nweb-client-lib",
3
- "version": "0.45.0",
3
+ "version": "0.45.2",
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",
@@ -47,8 +47,8 @@
47
47
  "@types/node": "^22.13.14",
48
48
  "@types/punycode": "^2.1.1",
49
49
  "@types/ws": "^8.18.1",
50
- "napi-nacl": "^1.2.0",
51
50
  "jasmine": "^5.13.0",
51
+ "napi-nacl": "^1.2.1",
52
52
  "protobufjs-cli": "^1.0.2",
53
53
  "spec-3nweb-server": "^1.8.5",
54
54
  "tsuml2": "^0.17.1",