mirror of
https://github.com/savoirfairelinux/opendht.git
synced 2026-11-08 04:21:51 +00:00
182 lines
6.7 KiB
JavaScript
182 lines
6.7 KiB
JavaScript
import { EventEmitter } from 'node:events';
|
|
import { dlopen } from 'node:process';
|
|
import load from 'node-gyp-build';
|
|
import createCrypto from './crypto.js';
|
|
|
|
const binding = { exports: {} };
|
|
dlopen(binding, process.env.OPENDHT_NODE_BINDING || load.path(import.meta.dirname));
|
|
const native = binding.exports;
|
|
const { PublicKey, PrivateKey, Certificate, Identity, handle } = createCrypto(native);
|
|
|
|
function eventReporter(owner, event) {
|
|
return value => { owner.deref()?.emit(event, value); };
|
|
}
|
|
|
|
class DhtRunner extends EventEmitter {
|
|
#native;
|
|
#closing;
|
|
#listeners = 0;
|
|
#pending = 0;
|
|
#iterators = new Set();
|
|
|
|
constructor() {
|
|
super();
|
|
const owner = new WeakRef(this);
|
|
this.#native = new native.NativeRunner(eventReporter(owner, 'statusChanged'));
|
|
}
|
|
|
|
#updateOwner() {
|
|
this.#native.setOwner(this.#listeners || this.#pending || this.listenerCount('statusChanged') ? this : null);
|
|
}
|
|
|
|
#track(operation) {
|
|
this.#pending++;
|
|
this.#updateOwner();
|
|
return operation.finally(() => {
|
|
this.#pending--;
|
|
this.#updateOwner();
|
|
});
|
|
}
|
|
|
|
on(event, listener) { super.on(event, listener); this.#updateOwner(); return this; }
|
|
addListener(event, listener) { return this.on(event, listener); }
|
|
prependListener(event, listener) { super.prependListener(event, listener); this.#updateOwner(); return this; }
|
|
removeListener(event, listener) { super.removeListener(event, listener); this.#updateOwner(); return this; }
|
|
off(event, listener) { return this.removeListener(event, listener); }
|
|
removeAllListeners(...args) { super.removeAllListeners(...args); this.#updateOwner(); return this; }
|
|
|
|
run(options) {
|
|
if (typeof options?.proxyServer === 'string' && options.proxyServer) {
|
|
const proxy = new URL(options.proxyServer);
|
|
if (proxy.protocol !== 'http:' && proxy.protocol !== 'https:')
|
|
throw new TypeError('proxyServer must be an HTTP(S) URL');
|
|
}
|
|
if (options?.identity !== undefined) options = { ...options, identity: handle(options.identity, Identity) };
|
|
this.#native.run(options);
|
|
}
|
|
isRunning() { return this.#native.isRunning(); }
|
|
getBoundPort(family = 4) { return this.#native.getBoundPort(family); }
|
|
getNodeId() { return this.#native.getNodeId(); }
|
|
getStatus() { return this.#native.getStatus(); }
|
|
bootstrap(host, port) { this.#native.bootstrap(host, port); }
|
|
get(key) {
|
|
return this.#iterate((push, finish) => {
|
|
const operation = this.#native.get(key, push);
|
|
this.#track(operation.done).then(() => finish(), finish);
|
|
return () => this.#native.cancelGet(operation.token);
|
|
}, true);
|
|
}
|
|
getAll(key) { return this.#track(this.#native.getAll(key)); }
|
|
put(key, data, options) { return this.#track(this.#native.put(key, data, options)); }
|
|
putSigned(key, data, options) { return this.#track(this.#native.putSigned(key, data, options)); }
|
|
putEncrypted(key, data, recipient, options) {
|
|
return this.#track(this.#native.putEncrypted(key, data, options, handle(recipient, PublicKey)));
|
|
}
|
|
|
|
listen(key, callback) {
|
|
if (callback === undefined) {
|
|
return this.#iterate(push => {
|
|
const subscription = this.listen(key, (value, expired) => push({ value, expired }));
|
|
return () => subscription.cancel();
|
|
});
|
|
}
|
|
const token = this.#native.listen(key, callback);
|
|
this.#listeners++;
|
|
this.#updateOwner();
|
|
let cancelled = false;
|
|
return {
|
|
cancel: () => {
|
|
if (cancelled) return;
|
|
this.#native.cancelListen(token);
|
|
cancelled = true;
|
|
this.#listeners--;
|
|
this.#updateOwner();
|
|
},
|
|
};
|
|
}
|
|
|
|
#iterate(subscribe, rejectOnClose = false) {
|
|
let values = [];
|
|
let offset = 0;
|
|
const pending = [];
|
|
let done = false;
|
|
let failure;
|
|
const finish = error => {
|
|
if (done) return;
|
|
done = true;
|
|
failure = error;
|
|
unsubscribe();
|
|
this.#iterators.delete(onClose);
|
|
if (failure) {
|
|
values = [];
|
|
offset = 0;
|
|
}
|
|
for (const { resolve, reject } of pending.splice(0)) {
|
|
if (failure) reject(failure);
|
|
else resolve({ value: undefined, done: true });
|
|
}
|
|
};
|
|
const cancel = () => {
|
|
values = [];
|
|
offset = 0;
|
|
failure = undefined;
|
|
finish();
|
|
};
|
|
const onClose = () => {
|
|
if (rejectOnClose) {
|
|
const error = new Error('DHT runner is closed');
|
|
error.code = 'ERR_DHT_CLOSED';
|
|
finish(error);
|
|
} else cancel();
|
|
};
|
|
const unsubscribe = subscribe(value => {
|
|
if (done) return;
|
|
const waiter = pending.shift();
|
|
if (waiter) waiter.resolve({ value, done: false });
|
|
else values.push(value);
|
|
}, finish);
|
|
this.#iterators.add(onClose);
|
|
return {
|
|
next() {
|
|
if (failure) return Promise.reject(failure);
|
|
if (offset < values.length) {
|
|
const value = values[offset];
|
|
values[offset++] = undefined;
|
|
if (offset === values.length) {
|
|
values = [];
|
|
offset = 0;
|
|
}
|
|
return Promise.resolve({ value, done: false });
|
|
}
|
|
if (done) return Promise.resolve({ value: undefined, done: true });
|
|
return new Promise((resolve, reject) => pending.push({ resolve, reject }));
|
|
},
|
|
return() {
|
|
cancel();
|
|
return Promise.resolve({ value: undefined, done: true });
|
|
},
|
|
throw(error) {
|
|
cancel();
|
|
return Promise.reject(error);
|
|
},
|
|
cancel,
|
|
[Symbol.asyncIterator]() { return this; },
|
|
[Symbol.asyncDispose]() { cancel(); return Promise.resolve(); },
|
|
};
|
|
}
|
|
|
|
close() {
|
|
if (!this.#closing) {
|
|
this.#closing = this.#native.close();
|
|
for (const cancel of this.#iterators) cancel();
|
|
}
|
|
return this.#closing;
|
|
}
|
|
ref() { this.#native.ref(); return this; }
|
|
unref() { this.#native.unref(); return this; }
|
|
[Symbol.asyncDispose]() { return this.close(); }
|
|
}
|
|
|
|
export { DhtRunner, PublicKey, PrivateKey, Certificate, Identity };
|
|
export const hash = native.hash;
|
|
export const version = native.version; |