mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-11-01 19:11:02 +00:00
feat: rename to BitGate and close the drop-in gap
Renames the project and makes it as easy to adopt as BitLogin, which was the stated bar and which it did not previously meet. Rename (ADR 0005): @nostr-governance/* -> @bitgate/*, governance-* elements -> bitgate-*, createGovernanceRuntime -> createBitGate. "Governance" reads as DAO voting to this audience and named a category rather than a product; BitGate names the benefit and joins the bit- family that BitVid and BitRoad compose with. Domain types keep their names — the product is BitGate, the subject matter is still governance. The migration plan is left as written; this ADR records the change rather than rewriting history. Drop-in distribution (ADR 0006): adopting BitGate meant supplying a transport, authoring a policy, and wiring every element in JavaScript. Now: - createRelayTransport(urls) — WebSocket transport with reconnection, backoff, subscription replay, and cross-relay dedupe - policy presets (social, commerce, admin-only) addressable by name; the engine still carries no thresholds of its own - <bitgate-provider> — configuration in markup, with descendants finding the runtime through a bubbling context event rather than DOM walking, which breaks across shadow boundaries - npm run build:widget — a self-contained dist/bitgate.js That last one came from running it in a browser rather than from a test. Bare specifiers resolve in Node and vitest but not in a browser, so the widget source worked everywhere except a plain static page — the one place it exists to serve. The bundle is not committed: consumers build from reviewed source. Also found by running it: the provider's ready event fires during element upgrade, before a page that defines elements after parsing can attach a listener, so it now exposes `ready` as a promise too. Four bugs fixed along the way, three found by new tests and one by the browser: admin state stayed silent when a roster change altered capabilities but no denials; the relay transport sent every subscription twice on connect; the commerce preset computed a meaningless visibility verdict at checkout and could hide a listing from its own seller; <bitgate-status> threw on an unusable decision. 759 tests; lint, typecheck, and both builds clean.
This commit is contained in:
1 parent
7f70a64d61
commit
adeba151b8
63 files changed
+2774
-401
No files matched your search
@@ -11,8 +11,8 @@ import {
|
||||
hasCapability,
|
||||
isProtectedActor,
|
||||
isValidTarget,
|
||||
} from "@nostr-governance/core";
|
||||
import { encodeContribution, encodeReport, encodeRoles } from "@nostr-governance/nostr";
|
||||
} from "@bitgate/core";
|
||||
import { encodeContribution, encodeReport, encodeRoles } from "@bitgate/nostr";
|
||||
|
||||
/**
|
||||
* Stable error codes. Applications match on these rather than on message text.
|
||||
@@ -61,7 +61,7 @@ function failure(code, message) {
|
||||
return { ok: false, code, message };
|
||||
}
|
||||
|
||||
/** @type {Record<string, import('@nostr-governance/core').GovernanceCapability>} */
|
||||
/** @type {Record<string, import('@bitgate/core').GovernanceCapability>} */
|
||||
const KIND_CAPABILITY = {
|
||||
"user-allow": "contribute-user-allow",
|
||||
"user-deny": "contribute-user-deny",
|
||||
@@ -131,8 +131,8 @@ export class GovernanceCommands {
|
||||
/**
|
||||
* Publish a contribution list.
|
||||
*
|
||||
* @param {import('@nostr-governance/core').Contribution["kind"]} kind
|
||||
* @param {import('@nostr-governance/core').GovernanceTarget[]} targets
|
||||
* @param {import('@bitgate/core').Contribution["kind"]} kind
|
||||
* @param {import('@bitgate/core').GovernanceTarget[]} targets
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.source] - Community list marker
|
||||
* @returns {Promise<CommandResult>}
|
||||
@@ -259,7 +259,7 @@ export class GovernanceCommands {
|
||||
|
||||
/**
|
||||
* Publish a policy document. Requires `manage-policy`.
|
||||
* @param {import('@nostr-governance/core').PolicyDefinition} policy
|
||||
* @param {import('@bitgate/core').PolicyDefinition} policy
|
||||
* @returns {Promise<CommandResult>}
|
||||
*/
|
||||
async setPolicy(policy) {
|
||||
@@ -281,9 +281,9 @@ export class GovernanceCommands {
|
||||
kind: 30078,
|
||||
content: JSON.stringify(policy),
|
||||
tags: [
|
||||
["d", `${this.runtime.namespace}:governance:policy:v1`],
|
||||
["d", `${this.runtime.namespace}:bitgate:policy:v1`],
|
||||
["v", "1"],
|
||||
["client", "nostr-governance"],
|
||||
["client", "bitgate"],
|
||||
["scope", "policy"],
|
||||
],
|
||||
});
|
||||
@@ -307,9 +307,9 @@ export class GovernanceCommands {
|
||||
}
|
||||
|
||||
const tags = [
|
||||
["d", `${this.runtime.namespace}:governance:community-sources:v1`],
|
||||
["d", `${this.runtime.namespace}:bitgate:community-sources:v1`],
|
||||
["v", "1"],
|
||||
["client", "nostr-governance"],
|
||||
["client", "bitgate"],
|
||||
["scope", "community-sources"],
|
||||
];
|
||||
for (const source of sources ?? []) {
|
||||
@@ -325,7 +325,7 @@ export class GovernanceCommands {
|
||||
* Reporting needs no capability: any account may report, and whether the
|
||||
* report counts is decided by the viewer's trust graph at evaluation time.
|
||||
*
|
||||
* @param {import('@nostr-governance/core').GovernanceTarget} target
|
||||
* @param {import('@bitgate/core').GovernanceTarget} target
|
||||
* @param {string} category
|
||||
* @param {string} [content]
|
||||
* @returns {Promise<CommandResult>}
|
||||
@@ -349,7 +349,7 @@ export class GovernanceCommands {
|
||||
* Set a viewer override. Viewer-local, so no capability is required and
|
||||
* nothing is published.
|
||||
*
|
||||
* @param {import('@nostr-governance/core').GovernanceTarget} target
|
||||
* @param {import('@bitgate/core').GovernanceTarget} target
|
||||
* @param {string} visibility
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.reason]
|
||||
@@ -366,7 +366,7 @@ export class GovernanceCommands {
|
||||
|
||||
/**
|
||||
* Clear a viewer override.
|
||||
* @param {import('@nostr-governance/core').GovernanceTarget} target
|
||||
* @param {import('@bitgate/core').GovernanceTarget} target
|
||||
* @returns {CommandResult}
|
||||
*/
|
||||
clearOverride(target) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @nostr-governance/runtime
|
||||
// @bitgate/runtime
|
||||
//
|
||||
// Stateful orchestration: stores, transport wiring, viewer lifecycle,
|
||||
// evaluation, and commands. Transport, signer, and storage are injected, so the
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
export { Emitter } from "./emitter.js";
|
||||
|
||||
export { createRelayTransport } from "./relayTransport.js";
|
||||
|
||||
export {
|
||||
storageKey,
|
||||
createMemoryStorage,
|
||||
@@ -26,7 +28,7 @@ export {
|
||||
DEFAULT_CHUNK_SIZE,
|
||||
GovernanceRuntime,
|
||||
chunk,
|
||||
createGovernanceRuntime,
|
||||
createBitGate,
|
||||
} from "./runtime.js";
|
||||
|
||||
export { ERROR_CODES, GovernanceCommands, createCommands } from "./commands.js";
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// and so an application can boot a working runtime before wiring real relays.
|
||||
|
||||
/**
|
||||
* @typedef {import('@nostr-governance/nostr').NostrEvent} NostrEvent
|
||||
* @typedef {import('@bitgate/nostr').NostrEvent} NostrEvent
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ export function storageKey({
|
||||
viewerPubkey,
|
||||
}) {
|
||||
const segments = [
|
||||
"nostr-governance",
|
||||
"bitgate",
|
||||
applicationId,
|
||||
namespace,
|
||||
rootFingerprint,
|
||||
|
||||
@@ -0,0 +1,408 @@
|
||||
// WebSocket relay transport.
|
||||
//
|
||||
// The runtime depends on no particular relay pool, and an application with one
|
||||
// should keep using it. This exists so that nobody has to write a transport
|
||||
// just to get started: it is the difference between "supply an adapter" and
|
||||
// "pass some relay URLs".
|
||||
//
|
||||
// Deliberately small. It speaks enough of the Nostr protocol to load governance
|
||||
// state, subscribe to reports and mute lists, and publish administrative
|
||||
// events. It is not a general-purpose relay client and does not try to be.
|
||||
|
||||
/**
|
||||
* @typedef {import('@bitgate/nostr').NostrEvent} NostrEvent
|
||||
* @typedef {import('./interfaces.js').GovernanceTransport} GovernanceTransport
|
||||
* @typedef {import('./interfaces.js').SubscriptionHandlers} SubscriptionHandlers
|
||||
*/
|
||||
|
||||
/** Milliseconds to wait for a relay's stored events before resolving `list`. */
|
||||
const DEFAULT_LIST_TIMEOUT = 8_000;
|
||||
|
||||
/** Milliseconds between reconnection attempts, before backoff. */
|
||||
const DEFAULT_RECONNECT_DELAY = 1_000;
|
||||
|
||||
/** Ceiling for reconnect backoff. */
|
||||
const MAX_RECONNECT_DELAY = 30_000;
|
||||
|
||||
let subscriptionCounter = 0;
|
||||
|
||||
/**
|
||||
* Create a transport backed by plain WebSockets.
|
||||
*
|
||||
* @param {string[]} urls - Relay URLs (`wss://…`)
|
||||
* @param {Object} [options]
|
||||
* @param {typeof WebSocket} [options.WebSocketImpl] - Injected for tests or Node
|
||||
* @param {number} [options.listTimeout] - Milliseconds to wait for stored events
|
||||
* @param {number} [options.reconnectDelay] - Initial reconnect delay
|
||||
* @param {(message: string, detail?: unknown) => void} [options.onDiagnostic]
|
||||
* @returns {GovernanceTransport & { close: () => void, relays: string[] }}
|
||||
*/
|
||||
export function createRelayTransport(urls, options = {}) {
|
||||
const relays = (Array.isArray(urls) ? urls : [])
|
||||
.map((url) => (typeof url === "string" ? url.trim() : ""))
|
||||
.filter(Boolean);
|
||||
|
||||
if (relays.length === 0) {
|
||||
throw new Error("createRelayTransport requires at least one relay URL");
|
||||
}
|
||||
|
||||
const SocketImpl = options.WebSocketImpl ?? globalThis.WebSocket;
|
||||
if (typeof SocketImpl !== "function") {
|
||||
throw new Error("No WebSocket implementation available; pass options.WebSocketImpl");
|
||||
}
|
||||
|
||||
const listTimeout = options.listTimeout ?? DEFAULT_LIST_TIMEOUT;
|
||||
const baseReconnectDelay = options.reconnectDelay ?? DEFAULT_RECONNECT_DELAY;
|
||||
const diagnostic = options.onDiagnostic ?? (() => {});
|
||||
|
||||
/** @type {Map<string, { socket: any, ready: boolean, queue: string[], attempts: number, timer: any }>} */
|
||||
const connections = new Map();
|
||||
|
||||
// OK frames answer a publish, not a subscription, so they are dispatched
|
||||
// through their own listener set. Keeping them here rather than rebinding a
|
||||
// socket's onmessage avoids stacking wrappers across repeated publishes.
|
||||
/** @type {Set<(url: string, frame: any[]) => void>} */
|
||||
const okListeners = new Set();
|
||||
|
||||
/** @type {Map<string, { filters: object[], handlers: SubscriptionHandlers, relays: Set<string> }>} */
|
||||
const subscriptions = new Map();
|
||||
|
||||
let closed = false;
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {string} payload
|
||||
*/
|
||||
function send(url, payload) {
|
||||
const connection = connections.get(url);
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
if (connection.ready) {
|
||||
try {
|
||||
connection.socket.send(payload);
|
||||
} catch (error) {
|
||||
diagnostic("relay send failed", { url, error: String(error) });
|
||||
}
|
||||
return;
|
||||
}
|
||||
// REQ frames are never queued: every live subscription is re-issued from
|
||||
// the subscription map when the socket opens, so queueing one too would
|
||||
// send it twice. Everything else (EVENT, CLOSE) is queued rather than
|
||||
// dropped, since nothing else would re-send it.
|
||||
if (!payload.startsWith('["REQ"')) {
|
||||
connection.queue.push(payload);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} url */
|
||||
function connect(url) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
const existing = connections.get(url) ?? { socket: null, ready: false, queue: [], attempts: 0, timer: null };
|
||||
connections.set(url, existing);
|
||||
|
||||
let socket;
|
||||
try {
|
||||
socket = new SocketImpl(url);
|
||||
} catch (error) {
|
||||
diagnostic("relay connect threw", { url, error: String(error) });
|
||||
scheduleReconnect(url);
|
||||
return;
|
||||
}
|
||||
|
||||
existing.socket = socket;
|
||||
existing.ready = false;
|
||||
|
||||
socket.onopen = () => {
|
||||
existing.ready = true;
|
||||
existing.attempts = 0;
|
||||
|
||||
// Re-open every live subscription: a reconnect that dropped them would
|
||||
// leave the UI silently stale rather than visibly broken.
|
||||
for (const [id, subscription] of subscriptions) {
|
||||
subscription.relays.add(url);
|
||||
send(url, JSON.stringify(["REQ", id, ...subscription.filters]));
|
||||
}
|
||||
|
||||
const queued = existing.queue.splice(0);
|
||||
for (const payload of queued) {
|
||||
send(url, payload);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onmessage = (message) => {
|
||||
let frame;
|
||||
try {
|
||||
frame = JSON.parse(typeof message?.data === "string" ? message.data : "");
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [verb, id, payload] = frame;
|
||||
|
||||
if (verb === "OK") {
|
||||
for (const listener of Array.from(okListeners)) {
|
||||
listener(url, frame);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (verb === "NOTICE") {
|
||||
diagnostic("relay notice", { url, message: id });
|
||||
return;
|
||||
}
|
||||
|
||||
const subscription = subscriptions.get(id);
|
||||
if (!subscription) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (verb === "EVENT" && payload) {
|
||||
subscription.handlers.onEvent?.(payload);
|
||||
} else if (verb === "EOSE") {
|
||||
subscription.handlers.onEose?.();
|
||||
} else if (verb === "CLOSED") {
|
||||
diagnostic("relay closed subscription", { url, id, reason: payload });
|
||||
}
|
||||
};
|
||||
|
||||
socket.onerror = (error) => {
|
||||
diagnostic("relay socket error", { url, error: String(error?.message ?? error) });
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
existing.ready = false;
|
||||
for (const subscription of subscriptions.values()) {
|
||||
subscription.relays.delete(url);
|
||||
}
|
||||
scheduleReconnect(url);
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {string} url */
|
||||
function scheduleReconnect(url) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
const connection = connections.get(url);
|
||||
if (!connection || connection.timer) {
|
||||
return;
|
||||
}
|
||||
|
||||
connection.attempts += 1;
|
||||
const delay = Math.min(baseReconnectDelay * 2 ** (connection.attempts - 1), MAX_RECONNECT_DELAY);
|
||||
|
||||
connection.timer = setTimeout(() => {
|
||||
connection.timer = null;
|
||||
connect(url);
|
||||
}, delay);
|
||||
|
||||
// Never let a reconnect timer hold a Node process open.
|
||||
connection.timer?.unref?.();
|
||||
}
|
||||
|
||||
for (const url of relays) {
|
||||
connect(url);
|
||||
}
|
||||
|
||||
return {
|
||||
relays,
|
||||
|
||||
/**
|
||||
* Collect stored events matching the filters.
|
||||
*
|
||||
* Resolves on EOSE from every relay, or when the timeout elapses —
|
||||
* whichever comes first. A single slow relay must not stall a page load,
|
||||
* and results already received are returned rather than discarded.
|
||||
*
|
||||
* @param {object[]} filters
|
||||
* @param {{ timeout?: number }} [listOptions]
|
||||
* @returns {Promise<NostrEvent[]>}
|
||||
*/
|
||||
async list(filters, listOptions = {}) {
|
||||
const id = `bg-list-${(subscriptionCounter += 1)}`;
|
||||
/** @type {Map<string, NostrEvent>} */
|
||||
const collected = new Map();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
let pendingEose = relays.length;
|
||||
|
||||
const finish = () => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
subscriptions.delete(id);
|
||||
for (const url of relays) {
|
||||
send(url, JSON.stringify(["CLOSE", id]));
|
||||
}
|
||||
resolve([...collected.values()]);
|
||||
};
|
||||
|
||||
const timer = setTimeout(finish, listOptions.timeout ?? listTimeout);
|
||||
timer?.unref?.();
|
||||
|
||||
subscriptions.set(id, {
|
||||
filters,
|
||||
relays: new Set(),
|
||||
handlers: {
|
||||
onEvent: (event) => {
|
||||
// Relays overlap heavily; dedupe by id so a caller does not have to.
|
||||
if (event?.id && !collected.has(event.id)) {
|
||||
collected.set(event.id, event);
|
||||
}
|
||||
},
|
||||
onEose: () => {
|
||||
pendingEose -= 1;
|
||||
if (pendingEose <= 0) {
|
||||
finish();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const url of relays) {
|
||||
send(url, JSON.stringify(["REQ", id, ...filters]));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Open a live subscription across every relay.
|
||||
* @param {object[]} filters
|
||||
* @param {SubscriptionHandlers} handlers
|
||||
* @returns {{ close: () => void }}
|
||||
*/
|
||||
subscribe(filters, handlers) {
|
||||
const id = `bg-sub-${(subscriptionCounter += 1)}`;
|
||||
/** @type {Set<string>} */
|
||||
const seen = new Set();
|
||||
|
||||
subscriptions.set(id, {
|
||||
filters,
|
||||
relays: new Set(),
|
||||
handlers: {
|
||||
onEvent: (event) => {
|
||||
if (!event?.id || seen.has(event.id)) {
|
||||
return;
|
||||
}
|
||||
seen.add(event.id);
|
||||
handlers.onEvent?.(event);
|
||||
},
|
||||
onEose: handlers.onEose,
|
||||
onError: handlers.onError,
|
||||
},
|
||||
});
|
||||
|
||||
const payload = JSON.stringify(["REQ", id, ...filters]);
|
||||
for (const url of relays) {
|
||||
send(url, payload);
|
||||
}
|
||||
|
||||
return {
|
||||
close() {
|
||||
subscriptions.delete(id);
|
||||
const closePayload = JSON.stringify(["CLOSE", id]);
|
||||
for (const url of relays) {
|
||||
send(url, closePayload);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Publish an event to every relay.
|
||||
*
|
||||
* Resolves once every relay has answered or the timeout elapses. Partial
|
||||
* acceptance is success: one relay accepting means the event exists on the
|
||||
* network, and the failures stay on the result for diagnostics.
|
||||
*
|
||||
* @param {NostrEvent} event
|
||||
* @param {{ timeout?: number }} [publishOptions]
|
||||
* @returns {Promise<import('./interfaces.js').PublishResult>}
|
||||
*/
|
||||
async publish(event, publishOptions = {}) {
|
||||
/** @type {string[]} */
|
||||
const accepted = [];
|
||||
/** @type {Array<{ relay: string, error: string }>} */
|
||||
const failed = [];
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
const answered = new Set();
|
||||
|
||||
/** @param {string} url @param {any[]} frame */
|
||||
const listener = (url, frame) => {
|
||||
if (frame[1] !== event.id || answered.has(url)) {
|
||||
return;
|
||||
}
|
||||
answered.add(url);
|
||||
|
||||
if (frame[2] === true) {
|
||||
accepted.push(url);
|
||||
} else {
|
||||
failed.push({ relay: url, error: String(frame[3] ?? "rejected") });
|
||||
}
|
||||
|
||||
if (answered.size >= relays.length) {
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
const finish = () => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
okListeners.delete(listener);
|
||||
|
||||
// Relays that never answered are reported as timed out rather than
|
||||
// silently omitted, so diagnostics show the full picture.
|
||||
for (const url of relays) {
|
||||
if (!answered.has(url)) {
|
||||
failed.push({ relay: url, error: "timeout" });
|
||||
}
|
||||
}
|
||||
|
||||
resolve({ ok: accepted.length > 0, accepted, failed });
|
||||
};
|
||||
|
||||
const timer = setTimeout(finish, publishOptions.timeout ?? listTimeout);
|
||||
timer?.unref?.();
|
||||
|
||||
okListeners.add(listener);
|
||||
send(relays[0], JSON.stringify(["EVENT", event]));
|
||||
for (const url of relays.slice(1)) {
|
||||
send(url, JSON.stringify(["EVENT", event]));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Close every socket and stop reconnecting. */
|
||||
close() {
|
||||
closed = true;
|
||||
subscriptions.clear();
|
||||
for (const connection of connections.values()) {
|
||||
if (connection.timer) {
|
||||
clearTimeout(connection.timer);
|
||||
connection.timer = null;
|
||||
}
|
||||
try {
|
||||
connection.socket?.close?.();
|
||||
} catch {
|
||||
// Closing an already-dead socket is not an error worth surfacing.
|
||||
}
|
||||
}
|
||||
connections.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
normalizePolicyDefinition,
|
||||
normalizePubkey,
|
||||
serializeAdminState,
|
||||
} from "@nostr-governance/core";
|
||||
} from "@bitgate/core";
|
||||
import {
|
||||
CANONICAL_KIND,
|
||||
LEGACY_KIND,
|
||||
@@ -28,9 +28,9 @@ import {
|
||||
decodeRoles,
|
||||
selectReplaceable,
|
||||
verifyEvents,
|
||||
} from "@nostr-governance/nostr";
|
||||
} from "@bitgate/nostr";
|
||||
|
||||
import { snapshotFingerprint } from "@nostr-governance/core";
|
||||
import { snapshotFingerprint } from "@bitgate/core";
|
||||
|
||||
import { Emitter } from "./emitter.js";
|
||||
import { createMemoryStorage, createNullSigner, storageKey } from "./interfaces.js";
|
||||
@@ -44,15 +44,15 @@ import {
|
||||
} from "./stores.js";
|
||||
|
||||
/**
|
||||
* @typedef {import('@nostr-governance/nostr').NostrEvent} NostrEvent
|
||||
* @typedef {import('@bitgate/nostr').NostrEvent} NostrEvent
|
||||
* @typedef {import('./interfaces.js').GovernanceTransport} GovernanceTransport
|
||||
* @typedef {import('./interfaces.js').GovernanceSigner} GovernanceSigner
|
||||
* @typedef {import('./interfaces.js').GovernanceStorage} GovernanceStorage
|
||||
* @typedef {import('@nostr-governance/core').PolicyDefinition} PolicyDefinition
|
||||
* @typedef {import('@nostr-governance/core').GovernanceTarget} GovernanceTarget
|
||||
* @typedef {import('@nostr-governance/core').GovernanceDecision} GovernanceDecision
|
||||
* @typedef {import('@nostr-governance/core').GovernanceSnapshot} GovernanceSnapshot
|
||||
* @typedef {import('@nostr-governance/core').ViewerState} ViewerState
|
||||
* @typedef {import('@bitgate/core').PolicyDefinition} PolicyDefinition
|
||||
* @typedef {import('@bitgate/core').GovernanceTarget} GovernanceTarget
|
||||
* @typedef {import('@bitgate/core').GovernanceDecision} GovernanceDecision
|
||||
* @typedef {import('@bitgate/core').GovernanceSnapshot} GovernanceSnapshot
|
||||
* @typedef {import('@bitgate/core').ViewerState} ViewerState
|
||||
*/
|
||||
|
||||
/** How many targets go into one relay filter chunk. */
|
||||
@@ -109,7 +109,7 @@ export class GovernanceRuntime extends Emitter {
|
||||
* @param {() => number} [options.now] - Injected clock, unix seconds
|
||||
* @param {number} [options.chunkSize]
|
||||
* @param {number} [options.muteWindowSeconds]
|
||||
* @param {import('@nostr-governance/nostr').SignatureVerifier} [options.verifySignature]
|
||||
* @param {import('@bitgate/nostr').SignatureVerifier} [options.verifySignature]
|
||||
* @param {string} [options.root] - Root administrator pubkey, from deployment config
|
||||
*/
|
||||
constructor({
|
||||
@@ -859,7 +859,7 @@ export class GovernanceRuntime extends Emitter {
|
||||
/**
|
||||
* Whether an actor holds a capability under the current roster.
|
||||
* @param {string} pubkey
|
||||
* @param {import('@nostr-governance/core').GovernanceCapability} capability
|
||||
* @param {import('@bitgate/core').GovernanceCapability} capability
|
||||
* @returns {boolean}
|
||||
*/
|
||||
can(pubkey, capability) {
|
||||
@@ -869,7 +869,7 @@ export class GovernanceRuntime extends Emitter {
|
||||
/**
|
||||
* Every capability an actor holds under the current roster.
|
||||
* @param {string} pubkey
|
||||
* @returns {import('@nostr-governance/core').GovernanceCapability[]}
|
||||
* @returns {import('@bitgate/core').GovernanceCapability[]}
|
||||
*/
|
||||
capabilitiesOf(pubkey) {
|
||||
return getActorCapabilities(pubkey, this.admin.authority);
|
||||
@@ -877,7 +877,7 @@ export class GovernanceRuntime extends Emitter {
|
||||
|
||||
/**
|
||||
* What the current viewer is allowed to do.
|
||||
* @returns {import('@nostr-governance/core').GovernanceCapability[]}
|
||||
* @returns {import('@bitgate/core').GovernanceCapability[]}
|
||||
*/
|
||||
viewerCapabilities() {
|
||||
return this.viewerPubkey ? this.capabilitiesOf(this.viewerPubkey) : [];
|
||||
@@ -942,6 +942,6 @@ export class GovernanceRuntime extends Emitter {
|
||||
* @param {ConstructorParameters<typeof GovernanceRuntime>[0]} options
|
||||
* @returns {GovernanceRuntime}
|
||||
*/
|
||||
export function createGovernanceRuntime(options) {
|
||||
export function createBitGate(options) {
|
||||
return new GovernanceRuntime(options);
|
||||
}
|
||||
@@ -11,17 +11,17 @@ import {
|
||||
normalizePubkey,
|
||||
reduceAdminState,
|
||||
serializeAdminState,
|
||||
} from "@nostr-governance/core";
|
||||
import { selectReplaceable, toMuteRecords } from "@nostr-governance/nostr";
|
||||
} from "@bitgate/core";
|
||||
import { selectReplaceable, toMuteRecords } from "@bitgate/nostr";
|
||||
|
||||
import { Emitter } from "./emitter.js";
|
||||
|
||||
/**
|
||||
* @typedef {import('@nostr-governance/core').PolicyDefinition} PolicyDefinition
|
||||
* @typedef {import('@nostr-governance/core').Contribution} Contribution
|
||||
* @typedef {import('@nostr-governance/core').AuthorityState} AuthorityState
|
||||
* @typedef {import('@nostr-governance/core').AdminState} AdminState
|
||||
* @typedef {import('@nostr-governance/nostr').NostrEvent} NostrEvent
|
||||
* @typedef {import('@bitgate/core').PolicyDefinition} PolicyDefinition
|
||||
* @typedef {import('@bitgate/core').Contribution} Contribution
|
||||
* @typedef {import('@bitgate/core').AuthorityState} AuthorityState
|
||||
* @typedef {import('@bitgate/core').AdminState} AdminState
|
||||
* @typedef {import('@bitgate/nostr').NostrEvent} NostrEvent
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -248,7 +248,7 @@ export class ReportStore extends Emitter {
|
||||
* The target is identified by `targetKey`; a decoded report's own `target`
|
||||
* field is accepted but not required, since it would be redundant.
|
||||
*
|
||||
* @param {{ reporter: string, category: string, createdAt?: number, target?: import('@nostr-governance/core').GovernanceTarget }} report
|
||||
* @param {{ reporter: string, category: string, createdAt?: number, target?: import('@bitgate/core').GovernanceTarget }} report
|
||||
* @param {string} targetKey
|
||||
*/
|
||||
ingest(report, targetKey) {
|
||||
@@ -349,7 +349,7 @@ export class TrustedMuteStore extends Emitter {
|
||||
* A newer list fully replaces the older one; an out-of-order delivery of an
|
||||
* older list is ignored rather than resurrecting removed entries.
|
||||
*
|
||||
* @param {import('@nostr-governance/nostr').DecodedMuteList} list
|
||||
* @param {import('@bitgate/nostr').DecodedMuteList} list
|
||||
*/
|
||||
replaceList(list) {
|
||||
if (!list?.owner) {
|
||||
|
||||
Reference in new issue
Block a user