include node_modules so release .zip is deployable
This commit is contained in:
+492
@@ -0,0 +1,492 @@
|
||||
// event.ts
|
||||
import { schnorr } from "@noble/curves/secp256k1";
|
||||
import { sha256 } from "@noble/hashes/sha256";
|
||||
import { bytesToHex } from "@noble/hashes/utils";
|
||||
|
||||
// utils.ts
|
||||
var utf8Decoder = new TextDecoder("utf-8");
|
||||
var utf8Encoder = new TextEncoder();
|
||||
var MessageNode = class {
|
||||
_value;
|
||||
_next;
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
set value(message) {
|
||||
this._value = message;
|
||||
}
|
||||
get next() {
|
||||
return this._next;
|
||||
}
|
||||
set next(node) {
|
||||
this._next = node;
|
||||
}
|
||||
constructor(message) {
|
||||
this._value = message;
|
||||
this._next = null;
|
||||
}
|
||||
};
|
||||
var MessageQueue = class {
|
||||
_first;
|
||||
_last;
|
||||
get first() {
|
||||
return this._first;
|
||||
}
|
||||
set first(messageNode) {
|
||||
this._first = messageNode;
|
||||
}
|
||||
get last() {
|
||||
return this._last;
|
||||
}
|
||||
set last(messageNode) {
|
||||
this._last = messageNode;
|
||||
}
|
||||
_size;
|
||||
get size() {
|
||||
return this._size;
|
||||
}
|
||||
set size(v) {
|
||||
this._size = v;
|
||||
}
|
||||
constructor() {
|
||||
this._first = null;
|
||||
this._last = null;
|
||||
this._size = 0;
|
||||
}
|
||||
enqueue(message) {
|
||||
const newNode = new MessageNode(message);
|
||||
if (this._size === 0 || !this._last) {
|
||||
this._first = newNode;
|
||||
this._last = newNode;
|
||||
} else {
|
||||
this._last.next = newNode;
|
||||
this._last = newNode;
|
||||
}
|
||||
this._size++;
|
||||
return true;
|
||||
}
|
||||
dequeue() {
|
||||
if (this._size === 0 || !this._first)
|
||||
return null;
|
||||
let prev = this._first;
|
||||
this._first = prev.next;
|
||||
prev.next = null;
|
||||
this._size--;
|
||||
return prev.value;
|
||||
}
|
||||
};
|
||||
|
||||
// event.ts
|
||||
var verifiedSymbol = Symbol("verified");
|
||||
function serializeEvent(evt) {
|
||||
if (!validateEvent(evt))
|
||||
throw new Error("can't serialize event with wrong or missing properties");
|
||||
return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content]);
|
||||
}
|
||||
function getEventHash(event) {
|
||||
let eventHash = sha256(utf8Encoder.encode(serializeEvent(event)));
|
||||
return bytesToHex(eventHash);
|
||||
}
|
||||
var isRecord = (obj) => obj instanceof Object;
|
||||
function validateEvent(event) {
|
||||
if (!isRecord(event))
|
||||
return false;
|
||||
if (typeof event.kind !== "number")
|
||||
return false;
|
||||
if (typeof event.content !== "string")
|
||||
return false;
|
||||
if (typeof event.created_at !== "number")
|
||||
return false;
|
||||
if (typeof event.pubkey !== "string")
|
||||
return false;
|
||||
if (!event.pubkey.match(/^[a-f0-9]{64}$/))
|
||||
return false;
|
||||
if (!Array.isArray(event.tags))
|
||||
return false;
|
||||
for (let i = 0; i < event.tags.length; i++) {
|
||||
let tag = event.tags[i];
|
||||
if (!Array.isArray(tag))
|
||||
return false;
|
||||
for (let j = 0; j < tag.length; j++) {
|
||||
if (typeof tag[j] === "object")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function verifySignature(event) {
|
||||
if (typeof event[verifiedSymbol] === "boolean")
|
||||
return event[verifiedSymbol];
|
||||
const hash = getEventHash(event);
|
||||
if (hash !== event.id) {
|
||||
return event[verifiedSymbol] = false;
|
||||
}
|
||||
try {
|
||||
return event[verifiedSymbol] = schnorr.verify(event.sig, hash, event.pubkey);
|
||||
} catch (err) {
|
||||
return event[verifiedSymbol] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// filter.ts
|
||||
function matchFilter(filter, event) {
|
||||
if (filter.ids && filter.ids.indexOf(event.id) === -1) {
|
||||
if (!filter.ids.some((prefix) => event.id.startsWith(prefix))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (filter.kinds && filter.kinds.indexOf(event.kind) === -1)
|
||||
return false;
|
||||
if (filter.authors && filter.authors.indexOf(event.pubkey) === -1) {
|
||||
if (!filter.authors.some((prefix) => event.pubkey.startsWith(prefix))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (let f in filter) {
|
||||
if (f[0] === "#") {
|
||||
let tagName = f.slice(1);
|
||||
let values = filter[`#${tagName}`];
|
||||
if (values && !event.tags.find(([t, v]) => t === f.slice(1) && values.indexOf(v) !== -1))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (filter.since && event.created_at < filter.since)
|
||||
return false;
|
||||
if (filter.until && event.created_at > filter.until)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
function matchFilters(filters, event) {
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
if (matchFilter(filters[i], event))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// fakejson.ts
|
||||
function getHex64(json, field) {
|
||||
let len = field.length + 3;
|
||||
let idx = json.indexOf(`"${field}":`) + len;
|
||||
let s = json.slice(idx).indexOf(`"`) + idx + 1;
|
||||
return json.slice(s, s + 64);
|
||||
}
|
||||
function getSubscriptionId(json) {
|
||||
let idx = json.slice(0, 22).indexOf(`"EVENT"`);
|
||||
if (idx === -1)
|
||||
return null;
|
||||
let pstart = json.slice(idx + 7 + 1).indexOf(`"`);
|
||||
if (pstart === -1)
|
||||
return null;
|
||||
let start = idx + 7 + 1 + pstart;
|
||||
let pend = json.slice(start + 1, 80).indexOf(`"`);
|
||||
if (pend === -1)
|
||||
return null;
|
||||
let end = start + 1 + pend;
|
||||
return json.slice(start + 1, end);
|
||||
}
|
||||
|
||||
// relay.ts
|
||||
var newListeners = () => ({
|
||||
connect: [],
|
||||
disconnect: [],
|
||||
error: [],
|
||||
notice: [],
|
||||
auth: []
|
||||
});
|
||||
function relayInit(url, options = {}) {
|
||||
let { listTimeout = 3e3, getTimeout = 3e3, countTimeout = 3e3 } = options;
|
||||
var ws;
|
||||
var openSubs = {};
|
||||
var listeners = newListeners();
|
||||
var subListeners = {};
|
||||
var pubListeners = {};
|
||||
var connectionPromise;
|
||||
async function connectRelay() {
|
||||
if (connectionPromise)
|
||||
return connectionPromise;
|
||||
connectionPromise = new Promise((resolve, reject) => {
|
||||
try {
|
||||
ws = new WebSocket(url);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
ws.onopen = () => {
|
||||
listeners.connect.forEach((cb) => cb());
|
||||
resolve();
|
||||
};
|
||||
ws.onerror = () => {
|
||||
connectionPromise = void 0;
|
||||
listeners.error.forEach((cb) => cb());
|
||||
reject();
|
||||
};
|
||||
ws.onclose = async () => {
|
||||
connectionPromise = void 0;
|
||||
listeners.disconnect.forEach((cb) => cb());
|
||||
};
|
||||
let incomingMessageQueue = new MessageQueue();
|
||||
let handleNextInterval;
|
||||
ws.onmessage = (e) => {
|
||||
incomingMessageQueue.enqueue(e.data);
|
||||
if (!handleNextInterval) {
|
||||
handleNextInterval = setInterval(handleNext, 0);
|
||||
}
|
||||
};
|
||||
function handleNext() {
|
||||
if (incomingMessageQueue.size === 0) {
|
||||
clearInterval(handleNextInterval);
|
||||
handleNextInterval = null;
|
||||
return;
|
||||
}
|
||||
var json = incomingMessageQueue.dequeue();
|
||||
if (!json)
|
||||
return;
|
||||
let subid = getSubscriptionId(json);
|
||||
if (subid) {
|
||||
let so = openSubs[subid];
|
||||
if (so && so.alreadyHaveEvent && so.alreadyHaveEvent(getHex64(json, "id"), url)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
let data = JSON.parse(json);
|
||||
switch (data[0]) {
|
||||
case "EVENT": {
|
||||
let id2 = data[1];
|
||||
let event = data[2];
|
||||
if (validateEvent(event) && openSubs[id2] && (openSubs[id2].skipVerification || verifySignature(event)) && matchFilters(openSubs[id2].filters, event)) {
|
||||
openSubs[id2];
|
||||
(subListeners[id2]?.event || []).forEach((cb) => cb(event));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "COUNT":
|
||||
let id = data[1];
|
||||
let payload = data[2];
|
||||
if (openSubs[id]) {
|
||||
;
|
||||
(subListeners[id]?.count || []).forEach((cb) => cb(payload));
|
||||
}
|
||||
return;
|
||||
case "EOSE": {
|
||||
let id2 = data[1];
|
||||
if (id2 in subListeners) {
|
||||
subListeners[id2].eose.forEach((cb) => cb());
|
||||
subListeners[id2].eose = [];
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "OK": {
|
||||
let id2 = data[1];
|
||||
let ok = data[2];
|
||||
let reason = data[3] || "";
|
||||
if (id2 in pubListeners) {
|
||||
let { resolve: resolve2, reject: reject2 } = pubListeners[id2];
|
||||
if (ok)
|
||||
resolve2(null);
|
||||
else
|
||||
reject2(new Error(reason));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "NOTICE":
|
||||
let notice = data[1];
|
||||
listeners.notice.forEach((cb) => cb(notice));
|
||||
return;
|
||||
case "AUTH": {
|
||||
let challenge = data[1];
|
||||
listeners.auth?.forEach((cb) => cb(challenge));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
return connectionPromise;
|
||||
}
|
||||
function connected() {
|
||||
return ws?.readyState === 1;
|
||||
}
|
||||
async function connect() {
|
||||
if (connected())
|
||||
return;
|
||||
await connectRelay();
|
||||
}
|
||||
async function trySend(params) {
|
||||
let msg = JSON.stringify(params);
|
||||
if (!connected()) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
||||
if (!connected()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
ws.send(msg);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
const sub = (filters, {
|
||||
verb = "REQ",
|
||||
skipVerification = false,
|
||||
alreadyHaveEvent = null,
|
||||
id = Math.random().toString().slice(2)
|
||||
} = {}) => {
|
||||
let subid = id;
|
||||
openSubs[subid] = {
|
||||
id: subid,
|
||||
filters,
|
||||
skipVerification,
|
||||
alreadyHaveEvent
|
||||
};
|
||||
trySend([verb, subid, ...filters]);
|
||||
let subscription = {
|
||||
sub: (newFilters, newOpts = {}) => sub(newFilters || filters, {
|
||||
skipVerification: newOpts.skipVerification || skipVerification,
|
||||
alreadyHaveEvent: newOpts.alreadyHaveEvent || alreadyHaveEvent,
|
||||
id: subid
|
||||
}),
|
||||
unsub: () => {
|
||||
delete openSubs[subid];
|
||||
delete subListeners[subid];
|
||||
trySend(["CLOSE", subid]);
|
||||
},
|
||||
on: (type, cb) => {
|
||||
subListeners[subid] = subListeners[subid] || {
|
||||
event: [],
|
||||
count: [],
|
||||
eose: []
|
||||
};
|
||||
subListeners[subid][type].push(cb);
|
||||
},
|
||||
off: (type, cb) => {
|
||||
let listeners2 = subListeners[subid];
|
||||
let idx = listeners2[type].indexOf(cb);
|
||||
if (idx >= 0)
|
||||
listeners2[type].splice(idx, 1);
|
||||
},
|
||||
get events() {
|
||||
return eventsGenerator(subscription);
|
||||
}
|
||||
};
|
||||
return subscription;
|
||||
};
|
||||
function _publishEvent(event, type) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!event.id) {
|
||||
reject(new Error(`event ${event} has no id`));
|
||||
return;
|
||||
}
|
||||
let id = event.id;
|
||||
trySend([type, event]);
|
||||
pubListeners[id] = { resolve, reject };
|
||||
});
|
||||
}
|
||||
return {
|
||||
url,
|
||||
sub,
|
||||
on: (type, cb) => {
|
||||
listeners[type].push(cb);
|
||||
if (type === "connect" && ws?.readyState === 1) {
|
||||
;
|
||||
cb();
|
||||
}
|
||||
},
|
||||
off: (type, cb) => {
|
||||
let index = listeners[type].indexOf(cb);
|
||||
if (index !== -1)
|
||||
listeners[type].splice(index, 1);
|
||||
},
|
||||
list: (filters, opts) => new Promise((resolve) => {
|
||||
let s = sub(filters, opts);
|
||||
let events = [];
|
||||
let timeout = setTimeout(() => {
|
||||
s.unsub();
|
||||
resolve(events);
|
||||
}, listTimeout);
|
||||
s.on("eose", () => {
|
||||
s.unsub();
|
||||
clearTimeout(timeout);
|
||||
resolve(events);
|
||||
});
|
||||
s.on("event", (event) => {
|
||||
events.push(event);
|
||||
});
|
||||
}),
|
||||
get: (filter, opts) => new Promise((resolve) => {
|
||||
let s = sub([filter], opts);
|
||||
let timeout = setTimeout(() => {
|
||||
s.unsub();
|
||||
resolve(null);
|
||||
}, getTimeout);
|
||||
s.on("event", (event) => {
|
||||
s.unsub();
|
||||
clearTimeout(timeout);
|
||||
resolve(event);
|
||||
});
|
||||
}),
|
||||
count: (filters) => new Promise((resolve) => {
|
||||
let s = sub(filters, { ...sub, verb: "COUNT" });
|
||||
let timeout = setTimeout(() => {
|
||||
s.unsub();
|
||||
resolve(null);
|
||||
}, countTimeout);
|
||||
s.on("count", (event) => {
|
||||
s.unsub();
|
||||
clearTimeout(timeout);
|
||||
resolve(event);
|
||||
});
|
||||
}),
|
||||
async publish(event) {
|
||||
await _publishEvent(event, "EVENT");
|
||||
},
|
||||
async auth(event) {
|
||||
await _publishEvent(event, "AUTH");
|
||||
},
|
||||
connect,
|
||||
close() {
|
||||
listeners = newListeners();
|
||||
subListeners = {};
|
||||
pubListeners = {};
|
||||
if (ws?.readyState === WebSocket.OPEN) {
|
||||
ws.close();
|
||||
}
|
||||
},
|
||||
get status() {
|
||||
return ws?.readyState ?? 3;
|
||||
}
|
||||
};
|
||||
}
|
||||
async function* eventsGenerator(sub) {
|
||||
let nextResolve;
|
||||
const eventQueue = [];
|
||||
const pushToQueue = (event) => {
|
||||
if (nextResolve) {
|
||||
nextResolve(event);
|
||||
nextResolve = void 0;
|
||||
} else {
|
||||
eventQueue.push(event);
|
||||
}
|
||||
};
|
||||
sub.on("event", pushToQueue);
|
||||
try {
|
||||
while (true) {
|
||||
if (eventQueue.length > 0) {
|
||||
yield eventQueue.shift();
|
||||
} else {
|
||||
const event = await new Promise((resolve) => {
|
||||
nextResolve = resolve;
|
||||
});
|
||||
yield event;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
sub.off("event", pushToQueue);
|
||||
}
|
||||
}
|
||||
export {
|
||||
eventsGenerator,
|
||||
relayInit
|
||||
};
|
||||
Reference in New Issue
Block a user