move to pkg
This commit is contained in:
343
packages/system/dist/Connection.js
vendored
Normal file
343
packages/system/dist/Connection.js
vendored
Normal file
@ -0,0 +1,343 @@
|
||||
"use strict";
|
||||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
||||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
var _Connection_instances, _Connection_SendQueuedRequests, _Connection_ResetQueues, _Connection_SendJson, _Connection_sendPendingRaw, _Connection_sendOnWire, _Connection_maxSubscriptions_get;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Connection = void 0;
|
||||
const uuid_1 = require("uuid");
|
||||
const Const_1 = require("./Const");
|
||||
const ConnectionStats_1 = require("./ConnectionStats");
|
||||
const Util_1 = require("./Util");
|
||||
const ExternalStore_1 = __importDefault(require("./ExternalStore"));
|
||||
class Connection extends ExternalStore_1.default {
|
||||
constructor(addr, options, auth, ephemeral = false) {
|
||||
super();
|
||||
_Connection_instances.add(this);
|
||||
this.Socket = null;
|
||||
this.PendingRaw = [];
|
||||
this.PendingRequests = [];
|
||||
this.ActiveRequests = new Set();
|
||||
this.ConnectTimeout = Const_1.DefaultConnectTimeout;
|
||||
this.Stats = new ConnectionStats_1.ConnectionStats();
|
||||
this.HasStateChange = true;
|
||||
this.Authed = false;
|
||||
this.Down = true;
|
||||
this.Id = (0, uuid_1.v4)();
|
||||
this.Address = addr;
|
||||
this.Settings = options;
|
||||
this.IsClosed = false;
|
||||
this.ReconnectTimer = null;
|
||||
this.EventsCallback = new Map();
|
||||
this.AwaitingAuth = new Map();
|
||||
this.Auth = auth;
|
||||
this.Ephemeral = ephemeral;
|
||||
}
|
||||
ResetEphemeralTimeout() {
|
||||
if (this.EphemeralTimeout) {
|
||||
clearTimeout(this.EphemeralTimeout);
|
||||
}
|
||||
if (this.Ephemeral) {
|
||||
this.EphemeralTimeout = setTimeout(() => {
|
||||
this.Close();
|
||||
}, 30000);
|
||||
}
|
||||
}
|
||||
async Connect() {
|
||||
try {
|
||||
if (this.Info === undefined) {
|
||||
const u = new URL(this.Address);
|
||||
const rsp = await fetch(`${u.protocol === "wss:" ? "https:" : "http:"}//${u.host}`, {
|
||||
headers: {
|
||||
accept: "application/nostr+json",
|
||||
},
|
||||
});
|
||||
if (rsp.ok) {
|
||||
const data = await rsp.json();
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
if (v === "unset" || v === "" || v === "~") {
|
||||
data[k] = undefined;
|
||||
}
|
||||
}
|
||||
this.Info = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.warn("Could not load relay information", e);
|
||||
}
|
||||
if (this.Socket) {
|
||||
this.Id = (0, uuid_1.v4)();
|
||||
this.Socket.onopen = null;
|
||||
this.Socket.onmessage = null;
|
||||
this.Socket.onerror = null;
|
||||
this.Socket.onclose = null;
|
||||
}
|
||||
this.IsClosed = false;
|
||||
this.Socket = new WebSocket(this.Address);
|
||||
this.Socket.onopen = () => this.OnOpen();
|
||||
this.Socket.onmessage = e => this.OnMessage(e);
|
||||
this.Socket.onerror = e => this.OnError(e);
|
||||
this.Socket.onclose = e => this.OnClose(e);
|
||||
}
|
||||
Close() {
|
||||
this.IsClosed = true;
|
||||
if (this.ReconnectTimer !== null) {
|
||||
clearTimeout(this.ReconnectTimer);
|
||||
this.ReconnectTimer = null;
|
||||
}
|
||||
this.Socket?.close();
|
||||
this.notifyChange();
|
||||
}
|
||||
OnOpen() {
|
||||
this.ConnectTimeout = Const_1.DefaultConnectTimeout;
|
||||
console.log(`[${this.Address}] Open!`);
|
||||
this.Down = false;
|
||||
if (this.Ephemeral) {
|
||||
this.ResetEphemeralTimeout();
|
||||
}
|
||||
this.OnConnected?.();
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_sendPendingRaw).call(this);
|
||||
}
|
||||
OnClose(e) {
|
||||
if (!this.IsClosed) {
|
||||
this.ConnectTimeout = this.ConnectTimeout * 2;
|
||||
console.log(`[${this.Address}] Closed (${e.reason}), trying again in ${(this.ConnectTimeout / 1000)
|
||||
.toFixed(0)
|
||||
.toLocaleString()} sec`);
|
||||
this.ReconnectTimer = setTimeout(() => {
|
||||
this.Connect();
|
||||
}, this.ConnectTimeout);
|
||||
this.Stats.Disconnects++;
|
||||
}
|
||||
else {
|
||||
console.log(`[${this.Address}] Closed!`);
|
||||
this.ReconnectTimer = null;
|
||||
}
|
||||
this.OnDisconnect?.(this.Id);
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_ResetQueues).call(this);
|
||||
// reset connection Id on disconnect, for query-tracking
|
||||
this.Id = (0, uuid_1.v4)();
|
||||
this.notifyChange();
|
||||
}
|
||||
OnMessage(e) {
|
||||
if (e.data.length > 0) {
|
||||
const msg = JSON.parse(e.data);
|
||||
const tag = msg[0];
|
||||
switch (tag) {
|
||||
case "AUTH": {
|
||||
this._OnAuthAsync(msg[1])
|
||||
.then(() => __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_sendPendingRaw).call(this))
|
||||
.catch(console.error);
|
||||
this.Stats.EventsReceived++;
|
||||
this.notifyChange();
|
||||
break;
|
||||
}
|
||||
case "EVENT": {
|
||||
this.OnEvent?.(msg[1], {
|
||||
...msg[2],
|
||||
relays: [this.Address],
|
||||
});
|
||||
this.Stats.EventsReceived++;
|
||||
this.notifyChange();
|
||||
break;
|
||||
}
|
||||
case "EOSE": {
|
||||
this.OnEose?.(msg[1]);
|
||||
break;
|
||||
}
|
||||
case "OK": {
|
||||
// feedback to broadcast call
|
||||
console.debug(`${this.Address} OK: `, msg);
|
||||
const id = msg[1];
|
||||
if (this.EventsCallback.has(id)) {
|
||||
const cb = (0, Util_1.unwrap)(this.EventsCallback.get(id));
|
||||
this.EventsCallback.delete(id);
|
||||
cb(msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "NOTICE": {
|
||||
console.warn(`[${this.Address}] NOTICE: ${msg[1]}`);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn(`Unknown tag: ${tag}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OnError(e) {
|
||||
console.error(e);
|
||||
this.notifyChange();
|
||||
}
|
||||
/**
|
||||
* Send event on this connection
|
||||
*/
|
||||
SendEvent(e) {
|
||||
if (!this.Settings.write) {
|
||||
return;
|
||||
}
|
||||
const req = ["EVENT", e];
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_SendJson).call(this, req);
|
||||
this.Stats.EventsSent++;
|
||||
this.notifyChange();
|
||||
}
|
||||
/**
|
||||
* Send event on this connection and wait for OK response
|
||||
*/
|
||||
async SendAsync(e, timeout = 5000) {
|
||||
return new Promise(resolve => {
|
||||
if (!this.Settings.write) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const t = setTimeout(() => {
|
||||
resolve();
|
||||
}, timeout);
|
||||
this.EventsCallback.set(e.id, () => {
|
||||
clearTimeout(t);
|
||||
resolve();
|
||||
});
|
||||
const req = ["EVENT", e];
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_SendJson).call(this, req);
|
||||
this.Stats.EventsSent++;
|
||||
this.notifyChange();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Using relay document to determine if this relay supports a feature
|
||||
*/
|
||||
SupportsNip(n) {
|
||||
return this.Info?.supported_nips?.some(a => a === n) ?? false;
|
||||
}
|
||||
/**
|
||||
* Queue or send command to the relay
|
||||
* @param cmd The REQ to send to the server
|
||||
*/
|
||||
QueueReq(cmd, cbSent) {
|
||||
if (this.ActiveRequests.size >= __classPrivateFieldGet(this, _Connection_instances, "a", _Connection_maxSubscriptions_get)) {
|
||||
this.PendingRequests.push({
|
||||
cmd,
|
||||
cb: cbSent,
|
||||
});
|
||||
console.debug("Queuing:", this.Address, cmd);
|
||||
}
|
||||
else {
|
||||
this.ActiveRequests.add(cmd[1]);
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_SendJson).call(this, cmd);
|
||||
cbSent();
|
||||
}
|
||||
this.notifyChange();
|
||||
}
|
||||
CloseReq(id) {
|
||||
if (this.ActiveRequests.delete(id)) {
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_SendJson).call(this, ["CLOSE", id]);
|
||||
this.OnEose?.(id);
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_SendQueuedRequests).call(this);
|
||||
}
|
||||
this.notifyChange();
|
||||
}
|
||||
takeSnapshot() {
|
||||
return {
|
||||
connected: this.Socket?.readyState === WebSocket.OPEN,
|
||||
events: {
|
||||
received: this.Stats.EventsReceived,
|
||||
send: this.Stats.EventsSent,
|
||||
},
|
||||
avgLatency: this.Stats.Latency.length > 0
|
||||
? this.Stats.Latency.reduce((acc, v) => acc + v, 0) / this.Stats.Latency.length
|
||||
: 0,
|
||||
disconnects: this.Stats.Disconnects,
|
||||
info: this.Info,
|
||||
id: this.Id,
|
||||
pendingRequests: [...this.PendingRequests.map(a => a.cmd[1])],
|
||||
activeRequests: [...this.ActiveRequests],
|
||||
ephemeral: this.Ephemeral,
|
||||
address: this.Address,
|
||||
};
|
||||
}
|
||||
async _OnAuthAsync(challenge) {
|
||||
const authCleanup = () => {
|
||||
this.AwaitingAuth.delete(challenge);
|
||||
};
|
||||
if (!this.Auth) {
|
||||
throw new Error("Auth hook not registered");
|
||||
}
|
||||
this.AwaitingAuth.set(challenge, true);
|
||||
const authEvent = await this.Auth(challenge, this.Address);
|
||||
return new Promise(resolve => {
|
||||
if (!authEvent) {
|
||||
authCleanup();
|
||||
return Promise.reject("no event");
|
||||
}
|
||||
const t = setTimeout(() => {
|
||||
authCleanup();
|
||||
resolve();
|
||||
}, 10000);
|
||||
this.EventsCallback.set(authEvent.id, (msg) => {
|
||||
clearTimeout(t);
|
||||
authCleanup();
|
||||
if (msg.length > 3 && msg[2] === true) {
|
||||
this.Authed = true;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_sendOnWire).call(this, ["AUTH", authEvent]);
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.Connection = Connection;
|
||||
_Connection_instances = new WeakSet(), _Connection_SendQueuedRequests = function _Connection_SendQueuedRequests() {
|
||||
const canSend = __classPrivateFieldGet(this, _Connection_instances, "a", _Connection_maxSubscriptions_get) - this.ActiveRequests.size;
|
||||
if (canSend > 0) {
|
||||
for (let x = 0; x < canSend; x++) {
|
||||
const p = this.PendingRequests.shift();
|
||||
if (p) {
|
||||
this.ActiveRequests.add(p.cmd[1]);
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_SendJson).call(this, p.cmd);
|
||||
p.cb();
|
||||
console.debug("Sent pending REQ", this.Address, p.cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, _Connection_ResetQueues = function _Connection_ResetQueues() {
|
||||
this.ActiveRequests.clear();
|
||||
this.PendingRequests = [];
|
||||
this.PendingRaw = [];
|
||||
this.notifyChange();
|
||||
}, _Connection_SendJson = function _Connection_SendJson(obj) {
|
||||
const authPending = !this.Authed && (this.AwaitingAuth.size > 0 || this.Info?.limitation?.auth_required === true);
|
||||
if (this.Socket?.readyState !== WebSocket.OPEN || authPending) {
|
||||
this.PendingRaw.push(obj);
|
||||
if (this.Socket?.readyState === WebSocket.CLOSED && this.Ephemeral && this.IsClosed) {
|
||||
this.Connect();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_sendPendingRaw).call(this);
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_sendOnWire).call(this, obj);
|
||||
}, _Connection_sendPendingRaw = function _Connection_sendPendingRaw() {
|
||||
while (this.PendingRaw.length > 0) {
|
||||
const next = this.PendingRaw.shift();
|
||||
if (next) {
|
||||
__classPrivateFieldGet(this, _Connection_instances, "m", _Connection_sendOnWire).call(this, next);
|
||||
}
|
||||
}
|
||||
}, _Connection_sendOnWire = function _Connection_sendOnWire(obj) {
|
||||
if (this.Socket?.readyState !== WebSocket.OPEN) {
|
||||
throw new Error(`Socket is not open, state is ${this.Socket?.readyState}`);
|
||||
}
|
||||
const json = JSON.stringify(obj);
|
||||
this.Socket.send(json);
|
||||
return true;
|
||||
}, _Connection_maxSubscriptions_get = function _Connection_maxSubscriptions_get() {
|
||||
return this.Info?.limitation?.max_subscriptions ?? 25;
|
||||
};
|
||||
//# sourceMappingURL=Connection.js.map
|
Reference in New Issue
Block a user