@mh-gg/cli 0.1.1-alpha.20260613T085325975Z
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.
- package/README.md +5 -0
- package/bin/matterhorn.cjs +57 -0
- package/package.json +49 -0
- package/runtime/bin/appFrontend/artifacts.cjs +25 -0
- package/runtime/bin/appFrontend/buildServers.cjs +176 -0
- package/runtime/bin/appFrontend/commandEnv.cjs +74 -0
- package/runtime/bin/appFrontend/commandPolicy.cjs +23 -0
- package/runtime/bin/appFrontend/devServers.cjs +150 -0
- package/runtime/bin/appFrontend/httpServers.cjs +221 -0
- package/runtime/bin/appFrontend/paths.cjs +103 -0
- package/runtime/bin/appFrontend/ports.cjs +36 -0
- package/runtime/bin/appFrontend/processes.cjs +127 -0
- package/runtime/bin/appFrontend.cjs +45 -0
- package/runtime/bin/appHostCommand.cjs +381 -0
- package/runtime/bin/matterhorn.cjs +501 -0
- package/runtime/bin/matterhornAppLoader.cjs +588 -0
- package/runtime/bin/matterhornApps.cjs +223 -0
- package/runtime/bin/matterhornDeploy.cjs +108 -0
- package/runtime/bin/matterhornEmitAppBundle.cjs +20 -0
- package/runtime/bin/matterhornInstall.cjs +609 -0
- package/runtime/host/callAuth.cjs +76 -0
- package/runtime/host/host.cjs +103 -0
- package/runtime/host/hostAnnouncement.cjs +70 -0
- package/runtime/host/hostClients/constants.cjs +7 -0
- package/runtime/host/hostClients/frontendBundleRefresh.cjs +158 -0
- package/runtime/host/hostClients/frontendRequests.cjs +166 -0
- package/runtime/host/hostClients/index.cjs +68 -0
- package/runtime/host/hostClients/rejections.cjs +37 -0
- package/runtime/host/hostSession.cjs +160 -0
- package/runtime/host/inlineProgressBar.cjs +128 -0
- package/runtime/host/localPeerServer.cjs +114 -0
- package/runtime/host/localRelayClient.cjs +151 -0
- package/runtime/host/matterhornrc.cjs +75 -0
- package/runtime/host/memberRootRegistry.cjs +132 -0
- package/runtime/host/nodePeer.cjs +127 -0
- package/runtime/host/nodePeerRacePatch.cjs +106 -0
- package/runtime/host/peerJsConfig.cjs +26 -0
- package/runtime/host/pushEgress.cjs +48 -0
- package/runtime/host/pushStorage.cjs +233 -0
- package/runtime/host/relay/config.cjs +179 -0
- package/runtime/host/relay/connectionCleanup.cjs +34 -0
- package/runtime/host/relay/connectionDispatcher.cjs +140 -0
- package/runtime/host/relay/matterhornOperationEvents.cjs +100 -0
- package/runtime/host/relay/matterhornRuntimeEventBridge.cjs +182 -0
- package/runtime/host/relay/nostrRelay.cjs +30 -0
- package/runtime/host/relay/peerStartup.cjs +81 -0
- package/runtime/host/relay.cjs +653 -0
- package/runtime/host/relayClientRouting.cjs +1054 -0
- package/runtime/host/relayConfig.cjs +156 -0
- package/runtime/host/relayHostAuth.cjs +39 -0
- package/runtime/host/relayHostMessages.cjs +367 -0
- package/runtime/host/relayHttp.cjs +48 -0
- package/runtime/host/relayIdentity.cjs +496 -0
- package/runtime/host/relayIncomingGate.cjs +153 -0
- package/runtime/host/relayMeshEnvelopes.cjs +522 -0
- package/runtime/host/relayPeerLifecycle.cjs +96 -0
- package/runtime/host/relayPeerSignals.cjs +175 -0
- package/runtime/host/relayRoomRuntimePersistence.cjs +129 -0
- package/runtime/host/relayStatus.cjs +160 -0
- package/runtime/host/sfuRelay.cjs +553 -0
- package/runtime/host/sqliteRelayStorage.cjs +352 -0
- package/runtime/host/wireValidation/client.cjs +213 -0
- package/runtime/host/wireValidation/host.cjs +33 -0
- package/runtime/host/wireValidation/index.cjs +13 -0
- package/runtime/host/wireValidation/peerSignal.cjs +35 -0
- package/runtime/host/wireValidation/presenceEvent.cjs +49 -0
- package/runtime/host/wireValidation/push.cjs +49 -0
- package/runtime/host/wireValidation/relay.cjs +131 -0
- package/runtime/host/wireValidation/shared.cjs +49 -0
- package/runtime/scripts/ensureWorkspaceSdkBuild.cjs +148 -0
- package/runtime/scripts/killChildTree.cjs +18 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const { createNodePeer } = require("../nodePeer.cjs");
|
|
2
|
+
const { userDataDir } = require("../matterhornrc.cjs");
|
|
3
|
+
const { peerJsSignalingConfig } = require("../peerJsConfig.cjs");
|
|
4
|
+
const { DEFAULT_RELAY_IPC_HOST, DEFAULT_RELAY_IPC_PORT, ensureRelayConfig, rotateRelayConfigPeerId } = require("../relayConfig.cjs");
|
|
5
|
+
const { createRelayTrustStore, signRelayClaim } = require("../relayIdentity.cjs");
|
|
6
|
+
const {
|
|
7
|
+
DEFAULT_RELAY_ACTIVE_FANOUT,
|
|
8
|
+
DEFAULT_RELAY_MAX_CALL_SIGNAL_BYTES,
|
|
9
|
+
DEFAULT_RELAY_MAX_BYTES_PER_ROOM,
|
|
10
|
+
DEFAULT_RELAY_MAX_EVENT_BYTES,
|
|
11
|
+
DEFAULT_RELAY_MAX_EVENTS,
|
|
12
|
+
DEFAULT_RELAY_MAX_EVENTS_PER_PUBKEY_WINDOW,
|
|
13
|
+
DEFAULT_RELAY_MAX_EVENTS_PER_ROOM,
|
|
14
|
+
DEFAULT_RELAY_MAX_FUTURE_SECONDS,
|
|
15
|
+
DEFAULT_RELAY_MAX_HOST_IPC_MESSAGE_BYTES,
|
|
16
|
+
DEFAULT_RELAY_MAX_HOST_IPC_MESSAGES_PER_WINDOW,
|
|
17
|
+
DEFAULT_RELAY_HEARTBEAT_MS,
|
|
18
|
+
DEFAULT_RELAY_HEARTBEAT_TIMEOUT_MS,
|
|
19
|
+
DEFAULT_RELAY_FAILOVER_MS,
|
|
20
|
+
DEFAULT_RELAY_MAX_MESH_MESSAGE_BYTES,
|
|
21
|
+
DEFAULT_RELAY_MAX_MESH_MESSAGES_PER_WINDOW,
|
|
22
|
+
DEFAULT_RELAY_MAX_NOSTR_MESSAGE_BYTES,
|
|
23
|
+
DEFAULT_RELAY_MAX_ROOM_MESSAGE_BYTES,
|
|
24
|
+
DEFAULT_RELAY_MAX_ROOM_MESSAGES_PER_WINDOW,
|
|
25
|
+
DEFAULT_RELAY_MAX_UNAUTHENTICATED_MESSAGES_PER_WINDOW,
|
|
26
|
+
DEFAULT_RELAY_PUBKEY_QUOTA_WINDOW_SECONDS,
|
|
27
|
+
DEFAULT_RELAY_RATE_WINDOW_MS,
|
|
28
|
+
DEFAULT_RELAY_SFU_MAX_PARTICIPANTS,
|
|
29
|
+
defaultRelayStoragePath,
|
|
30
|
+
parseRelayArgs,
|
|
31
|
+
parseIceServers,
|
|
32
|
+
resolveStoragePath
|
|
33
|
+
} = require("@mh-gg/host-config");
|
|
34
|
+
const { relayAddressForPeerId, relayMeshAddressForPeerId, relayMeshPeerIdForRoomPeerId } = require("@mh-gg/relay-core");
|
|
35
|
+
|
|
36
|
+
function defaultStoragePath() {
|
|
37
|
+
return defaultRelayStoragePath(userDataDir());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseArgs(argv) {
|
|
41
|
+
return parseRelayArgs(argv, {
|
|
42
|
+
defaultIpcHost: DEFAULT_RELAY_IPC_HOST,
|
|
43
|
+
defaultIpcPort: DEFAULT_RELAY_IPC_PORT,
|
|
44
|
+
defaultStoragePath: defaultStoragePath()
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function positiveInteger(value, fallback) {
|
|
49
|
+
return Number.isInteger(value) && value > 0 ? value : fallback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function nonNegativeInteger(value, fallback) {
|
|
53
|
+
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createRelayRuntimeConfig(options = {}) {
|
|
57
|
+
const peerJsSignaling = options.peerJsSignaling || peerJsSignalingConfig();
|
|
58
|
+
const dataDir = options.dataDir || userDataDir();
|
|
59
|
+
const relayConfig = ensureRelayConfig({
|
|
60
|
+
dataDir,
|
|
61
|
+
ipcHost: options.ipcHost || options.host,
|
|
62
|
+
ipcPort: options.ipcPort ?? options.port,
|
|
63
|
+
relayPeerId: options.relayPeerId,
|
|
64
|
+
relayName: options.relayName,
|
|
65
|
+
peerJsSignaling
|
|
66
|
+
});
|
|
67
|
+
const relayTrustStore = createRelayTrustStore({ dataDir, defaultSignaling: peerJsSignaling });
|
|
68
|
+
const relayPeerId = options.relayPeerId || relayConfig.relayPeerId;
|
|
69
|
+
const relayMeshPeerId = relayMeshPeerIdForRoomPeerId(relayPeerId);
|
|
70
|
+
const sfuPeerId = options.sfuPeerId || `${relayPeerId}-sfu`;
|
|
71
|
+
const resolvedRelayRefs = [];
|
|
72
|
+
for (const ref of options.relayRefs || []) {
|
|
73
|
+
const resolved = relayTrustStore.resolveReference(ref);
|
|
74
|
+
if (!resolved.ok) throw new Error(resolved.message);
|
|
75
|
+
resolvedRelayRefs.push(resolved.relayAddress);
|
|
76
|
+
}
|
|
77
|
+
const runtimeConfig = {
|
|
78
|
+
ipcHost: options.ipcHost || options.host || relayConfig.ipcHost,
|
|
79
|
+
ipcPort: Number.isInteger(options.ipcPort) ? options.ipcPort : Number.isInteger(options.port) ? options.port : relayConfig.ipcPort,
|
|
80
|
+
ipcSecret: relayConfig.ipcSecret,
|
|
81
|
+
storagePath: options.storagePath || defaultStoragePath(),
|
|
82
|
+
peerJsSignaling,
|
|
83
|
+
relayPeerId,
|
|
84
|
+
relayMeshPeerId,
|
|
85
|
+
relayAddress: relayMeshAddressForPeerId(relayPeerId, peerJsSignaling),
|
|
86
|
+
relayIdentity: relayConfig.relayIdentity,
|
|
87
|
+
relayTrustStore,
|
|
88
|
+
sfuEnabled: Boolean(options.sfuEnabled),
|
|
89
|
+
sfuPeerId,
|
|
90
|
+
sfuPeerAddress: relayAddressForPeerId(sfuPeerId, peerJsSignaling),
|
|
91
|
+
sfuMaxParticipants: positiveInteger(options.sfuMaxParticipants, DEFAULT_RELAY_SFU_MAX_PARTICIPANTS),
|
|
92
|
+
relayPeers: [...(options.relayPeers || []), ...resolvedRelayRefs],
|
|
93
|
+
activeRelayFanout: positiveInteger(options.activeRelayFanout, DEFAULT_RELAY_ACTIVE_FANOUT),
|
|
94
|
+
relayHeartbeatMs: positiveInteger(options.relayHeartbeatMs, DEFAULT_RELAY_HEARTBEAT_MS),
|
|
95
|
+
relayHeartbeatTimeoutMs: positiveInteger(options.relayHeartbeatTimeoutMs, DEFAULT_RELAY_HEARTBEAT_TIMEOUT_MS),
|
|
96
|
+
relayFailoverMs: positiveInteger(options.relayFailoverMs, DEFAULT_RELAY_FAILOVER_MS),
|
|
97
|
+
maxEvents: options.maxEvents || DEFAULT_RELAY_MAX_EVENTS,
|
|
98
|
+
maxEventsPerRoom: options.maxEventsPerRoom || DEFAULT_RELAY_MAX_EVENTS_PER_ROOM,
|
|
99
|
+
maxEventsPerStream: Number.isInteger(options.maxEventsPerStream) && options.maxEventsPerStream > 0 ? options.maxEventsPerStream : 10000,
|
|
100
|
+
maxBytesPerRoom: options.maxBytesPerRoom || DEFAULT_RELAY_MAX_BYTES_PER_ROOM,
|
|
101
|
+
maxEventsPerPubkeyWindow: options.maxEventsPerPubkeyWindow || DEFAULT_RELAY_MAX_EVENTS_PER_PUBKEY_WINDOW,
|
|
102
|
+
pubkeyQuotaWindowSeconds: options.pubkeyQuotaWindowSeconds || DEFAULT_RELAY_PUBKEY_QUOTA_WINDOW_SECONDS,
|
|
103
|
+
maxEventBytes: positiveInteger(options.maxEventBytes, DEFAULT_RELAY_MAX_EVENT_BYTES),
|
|
104
|
+
maxFutureSeconds: nonNegativeInteger(options.maxFutureSeconds, DEFAULT_RELAY_MAX_FUTURE_SECONDS),
|
|
105
|
+
maxHostIpcMessageBytes: positiveInteger(options.maxHostIpcMessageBytes, DEFAULT_RELAY_MAX_HOST_IPC_MESSAGE_BYTES),
|
|
106
|
+
maxRoomMessageBytes: positiveInteger(options.maxRoomMessageBytes, DEFAULT_RELAY_MAX_ROOM_MESSAGE_BYTES),
|
|
107
|
+
maxMeshMessageBytes: positiveInteger(options.maxMeshMessageBytes, DEFAULT_RELAY_MAX_MESH_MESSAGE_BYTES),
|
|
108
|
+
maxNostrMessageBytes: positiveInteger(options.maxNostrMessageBytes, DEFAULT_RELAY_MAX_NOSTR_MESSAGE_BYTES),
|
|
109
|
+
maxCallSignalBytes: positiveInteger(options.maxCallSignalBytes, DEFAULT_RELAY_MAX_CALL_SIGNAL_BYTES),
|
|
110
|
+
rateWindowMs: positiveInteger(options.rateWindowMs, DEFAULT_RELAY_RATE_WINDOW_MS),
|
|
111
|
+
maxUnauthenticatedMessagesPerWindow: positiveInteger(options.maxUnauthenticatedMessagesPerWindow, DEFAULT_RELAY_MAX_UNAUTHENTICATED_MESSAGES_PER_WINDOW),
|
|
112
|
+
maxRoomMessagesPerWindow: positiveInteger(options.maxRoomMessagesPerWindow, DEFAULT_RELAY_MAX_ROOM_MESSAGES_PER_WINDOW),
|
|
113
|
+
maxMeshMessagesPerWindow: positiveInteger(options.maxMeshMessagesPerWindow, DEFAULT_RELAY_MAX_MESH_MESSAGES_PER_WINDOW),
|
|
114
|
+
maxHostIpcMessagesPerWindow: positiveInteger(options.maxHostIpcMessagesPerWindow, DEFAULT_RELAY_MAX_HOST_IPC_MESSAGES_PER_WINDOW),
|
|
115
|
+
iceServers: options.iceServers || [{ urls: "stun:stun.l.google.com:19302" }],
|
|
116
|
+
startPeer: options.startPeer !== false,
|
|
117
|
+
createPeer: options.createPeer || createNodePeer
|
|
118
|
+
};
|
|
119
|
+
runtimeConfig.relayClaim = signRelayClaim(runtimeConfig.relayIdentity, {
|
|
120
|
+
relayAddress: runtimeConfig.relayAddress,
|
|
121
|
+
roomPeerId: runtimeConfig.relayPeerId,
|
|
122
|
+
relayMeshPeerId: runtimeConfig.relayMeshPeerId
|
|
123
|
+
});
|
|
124
|
+
runtimeConfig.rotateRelayTransportIdentity = (reason) => {
|
|
125
|
+
const previousRelayAddress = runtimeConfig.relayAddress;
|
|
126
|
+
const rotated = rotateRelayConfigPeerId(runtimeConfig, { dataDir, peerJsSignaling });
|
|
127
|
+
runtimeConfig.relayPeerId = rotated.relayPeerId;
|
|
128
|
+
runtimeConfig.relayMeshPeerId = rotated.relayMeshPeerId;
|
|
129
|
+
runtimeConfig.relayAddress = rotated.relayAddress;
|
|
130
|
+
runtimeConfig.relayIdentity = rotated.relayIdentity;
|
|
131
|
+
runtimeConfig.sfuPeerId = options.sfuPeerId || `${rotated.relayPeerId}-sfu`;
|
|
132
|
+
runtimeConfig.sfuPeerAddress = relayAddressForPeerId(runtimeConfig.sfuPeerId, peerJsSignaling);
|
|
133
|
+
runtimeConfig.relayClaim = signRelayClaim(runtimeConfig.relayIdentity, {
|
|
134
|
+
relayAddress: runtimeConfig.relayAddress,
|
|
135
|
+
roomPeerId: runtimeConfig.relayPeerId,
|
|
136
|
+
relayMeshPeerId: runtimeConfig.relayMeshPeerId,
|
|
137
|
+
previousRelayAddress
|
|
138
|
+
});
|
|
139
|
+
if (reason) runtimeConfig.lastRelayTransportRotationReason = reason?.message || String(reason);
|
|
140
|
+
return runtimeConfig;
|
|
141
|
+
};
|
|
142
|
+
return runtimeConfig;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function createRelayOptionsFromArgs(args) {
|
|
146
|
+
return {
|
|
147
|
+
ipcHost: args.ipcHost,
|
|
148
|
+
ipcPort: args.ipcPort,
|
|
149
|
+
storagePath: args.storagePath,
|
|
150
|
+
relayPeerId: args.relayPeerId,
|
|
151
|
+
relayName: args.relayName,
|
|
152
|
+
relayPeers: args.relayPeers,
|
|
153
|
+
relayRefs: args.relayRefs,
|
|
154
|
+
activeRelayFanout: args.activeRelayFanout,
|
|
155
|
+
relayHeartbeatMs: args.relayHeartbeatMs,
|
|
156
|
+
relayHeartbeatTimeoutMs: args.relayHeartbeatTimeoutMs,
|
|
157
|
+
relayFailoverMs: args.relayFailoverMs,
|
|
158
|
+
maxEvents: args.maxEvents,
|
|
159
|
+
maxEventsPerRoom: args.maxEventsPerRoom,
|
|
160
|
+
maxBytesPerRoom: args.maxBytesPerRoom,
|
|
161
|
+
maxEventsPerPubkeyWindow: args.maxEventsPerPubkeyWindow,
|
|
162
|
+
pubkeyQuotaWindowSeconds: args.pubkeyQuotaWindowSeconds,
|
|
163
|
+
maxEventBytes: args.maxEventBytes,
|
|
164
|
+
maxFutureSeconds: args.maxFutureSeconds,
|
|
165
|
+
sfuEnabled: args.sfuEnabled,
|
|
166
|
+
sfuPeerId: args.sfuPeerId,
|
|
167
|
+
sfuMaxParticipants: args.sfuMaxParticipants,
|
|
168
|
+
iceServers: parseIceServers(args.iceServers),
|
|
169
|
+
localPeerjs: args.localPeerjs
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
createRelayOptionsFromArgs,
|
|
175
|
+
createRelayRuntimeConfig,
|
|
176
|
+
defaultStoragePath,
|
|
177
|
+
parseArgs,
|
|
178
|
+
resolveStoragePath
|
|
179
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { sendMessage } = require("@mh-gg/relay-core");
|
|
2
|
+
|
|
3
|
+
function createConnectionCleanup({
|
|
4
|
+
clientConnections,
|
|
5
|
+
clientRooms,
|
|
6
|
+
hostConnections,
|
|
7
|
+
incomingGate,
|
|
8
|
+
mesh,
|
|
9
|
+
relayClientRouting
|
|
10
|
+
}) {
|
|
11
|
+
return function cleanupConnection(conn) {
|
|
12
|
+
if (conn.MatterhornCleaned) return;
|
|
13
|
+
conn.MatterhornCleaned = true;
|
|
14
|
+
incomingGate.cleanup(conn);
|
|
15
|
+
const peerId = conn.MatterhornPeerId;
|
|
16
|
+
if (peerId) {
|
|
17
|
+
clientConnections.delete(peerId);
|
|
18
|
+
}
|
|
19
|
+
mesh.cleanupConnection(conn);
|
|
20
|
+
const roomName = clientRooms.get(conn);
|
|
21
|
+
relayClientRouting?.dropClientEphemeralTokens?.(conn);
|
|
22
|
+
clientRooms.delete(conn);
|
|
23
|
+
if (roomName && peerId && conn.MatterhornNotifyHostOnClose) {
|
|
24
|
+
const host = hostConnections.get(roomName);
|
|
25
|
+
if (host) sendMessage(host, { type: "relay/client-close", peerId });
|
|
26
|
+
else mesh.sendEnvelope({ type: "relay.client.close", roomName, peerId }, conn);
|
|
27
|
+
}
|
|
28
|
+
if (conn.hostRoomName && hostConnections.get(conn.hostRoomName) === conn) {
|
|
29
|
+
hostConnections.delete(conn.hostRoomName);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { createConnectionCleanup };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const { verifyRelayControl } = require("../relayIdentity.cjs");
|
|
2
|
+
const { sendMessage } = require("@mh-gg/relay-core");
|
|
3
|
+
|
|
4
|
+
function createConnectionDataHandler({
|
|
5
|
+
hostAuth,
|
|
6
|
+
isNostrMessage,
|
|
7
|
+
isRelayEnvelopeMessage,
|
|
8
|
+
mesh,
|
|
9
|
+
promoteRelayConnection,
|
|
10
|
+
relayClientRouting,
|
|
11
|
+
relayHostMessages,
|
|
12
|
+
relayMeshEnvelopes,
|
|
13
|
+
relayStatus,
|
|
14
|
+
relayTrust,
|
|
15
|
+
validateRelayHints
|
|
16
|
+
}) {
|
|
17
|
+
const relayControlNonces = new Set();
|
|
18
|
+
|
|
19
|
+
function rememberRelayClaim(claim, options = {}) {
|
|
20
|
+
if (!claim || typeof relayTrust?.rememberClaim !== "function") return { ok: true };
|
|
21
|
+
const result = relayTrust.rememberClaim(claim, options);
|
|
22
|
+
if (result.ok && result.claim?.relayAddress) mesh.learnRelayAddress(result.claim.relayAddress);
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function rememberRelayClaims(message) {
|
|
27
|
+
if (Array.isArray(message.relayClaims)) {
|
|
28
|
+
for (const claim of message.relayClaims) rememberRelayClaim(claim);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function rejectRelayClaim(conn, result) {
|
|
33
|
+
sendMessage(conn, {
|
|
34
|
+
type: "relay.error",
|
|
35
|
+
code: result.code || "invalid-relay-claim",
|
|
36
|
+
message: result.message || "Relay claim is invalid."
|
|
37
|
+
});
|
|
38
|
+
conn.close?.();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return function handleConnectionData(conn, message) {
|
|
42
|
+
if (isNostrMessage(message)) {
|
|
43
|
+
if (!mesh.hasRelayConnection(conn) && mesh.connectionHasRelayMetadata(conn) && conn.MatterhornRelayAddress) promoteRelayConnection(conn);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!message || typeof message !== "object") return;
|
|
47
|
+
if (isRelayEnvelopeMessage(message)) {
|
|
48
|
+
relayMeshEnvelopes.handleRelayEnvelope(conn, message);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (message.type === "relay.status.ok") {
|
|
52
|
+
if (message.relayAddress && !message.relayClaim && relayTrust?.pinForAddress?.(message.relayAddress)) {
|
|
53
|
+
rejectRelayClaim(conn, { code: "relay-claim-required", message: "Pinned relay address did not present a signed relay claim." });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const claimResult = rememberRelayClaim(message.relayClaim, { expectedRelayAddress: message.relayAddress });
|
|
57
|
+
if (!claimResult.ok) {
|
|
58
|
+
rejectRelayClaim(conn, claimResult);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
rememberRelayClaims(message);
|
|
62
|
+
if (message.load) {
|
|
63
|
+
conn.MatterhornRelayLoad = message.load;
|
|
64
|
+
}
|
|
65
|
+
if (message.relayAddress && mesh.bindConnectionAddress(conn, message.relayAddress)) promoteRelayConnection(conn);
|
|
66
|
+
mesh.recordRelayLiveness?.(conn, message.relayAddress);
|
|
67
|
+
if (!mesh.hasRelayConnection(conn)) mesh.learnRelayHints(message.relayHints);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (message.type === "relay.add") {
|
|
71
|
+
if (!hostAuth.requireHostIpcAuth(conn, message)) return;
|
|
72
|
+
let relayAddress = message.relayAddress;
|
|
73
|
+
if (message.relayRef && typeof relayTrust?.resolveReference === "function") {
|
|
74
|
+
const resolved = relayTrust.resolveReference(message.relayRef);
|
|
75
|
+
if (!resolved.ok) {
|
|
76
|
+
sendMessage(conn, { type: "relay.add.error", requestId: message.requestId, code: resolved.code, message: resolved.message });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
relayAddress = resolved.relayAddress;
|
|
80
|
+
if (resolved.relayClaim) rememberRelayClaim(resolved.relayClaim);
|
|
81
|
+
}
|
|
82
|
+
if (message.relayClaim) {
|
|
83
|
+
const claimResult = rememberRelayClaim(message.relayClaim, { expectedRelayAddress: relayAddress });
|
|
84
|
+
if (!claimResult.ok) {
|
|
85
|
+
sendMessage(conn, { type: "relay.add.error", requestId: message.requestId, code: claimResult.code, message: claimResult.message });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
relayAddress = claimResult.claim.relayAddress;
|
|
89
|
+
}
|
|
90
|
+
if (!relayAddress) {
|
|
91
|
+
sendMessage(conn, { type: "relay.add.error", requestId: message.requestId, code: "missing-relay-reference", message: "Relay address or reference is required." });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
mesh.learnRelayAddress(relayAddress);
|
|
95
|
+
sendMessage(conn, { type: "relay.add.ok", requestId: message.requestId, relayAddress: mesh.normalizeRelayAddress(relayAddress) });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (message.type === "relay.status") {
|
|
99
|
+
if (!hostAuth.requireHostIpcAuth(conn, message)) return;
|
|
100
|
+
sendMessage(conn, relayStatus.relayStatusMessage(message.requestId));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (message.type === "relay.disconnecting") {
|
|
104
|
+
if (!mesh.hasRelayConnection(conn) && !mesh.connectionHasRelayMetadata(conn)) return;
|
|
105
|
+
const expectedRelayAddress = conn.MatterhornRelayAddress || message.relayAddress;
|
|
106
|
+
const verification = verifyRelayControl(message.relayControl, message.relayClaim, { expectedRelayAddress });
|
|
107
|
+
if (!verification.ok) return;
|
|
108
|
+
const replayKey = `${verification.control.relayAddress}:${verification.control.nonce}`;
|
|
109
|
+
if (relayControlNonces.has(replayKey)) return;
|
|
110
|
+
relayControlNonces.add(replayKey);
|
|
111
|
+
const claimResult = rememberRelayClaim(message.relayClaim, { expectedRelayAddress: verification.control.relayAddress });
|
|
112
|
+
if (!claimResult.ok) return;
|
|
113
|
+
mesh.iceAddress(verification.control.relayAddress);
|
|
114
|
+
relayClientRouting.broadcastToRoom(message.roomName, relayClientRouting.relayHintMessage(message.roomName));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (typeof message.type === "string" && message.type.startsWith("relay.") && mesh.hasRelayConnection(conn)) {
|
|
118
|
+
relayMeshEnvelopes.handleRelayEnvelope(conn, message);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (message.type === "relay.hints") {
|
|
122
|
+
const validation = validateRelayHints(message);
|
|
123
|
+
if (!validation.ok) return;
|
|
124
|
+
mesh.recordRelayLiveness?.(conn);
|
|
125
|
+
mesh.learnRelayHints(message.relayHints);
|
|
126
|
+
rememberRelayClaim(message.relayClaim);
|
|
127
|
+
rememberRelayClaims(message);
|
|
128
|
+
if (typeof message.roomName === "string") relayClientRouting.broadcastToRoom(message.roomName, relayClientRouting.relayHintMessage(message.roomName));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (typeof message.type === "string" && message.type.startsWith("host.")) {
|
|
132
|
+
if (!hostAuth.requireHostIpcAuth(conn, message)) return;
|
|
133
|
+
relayHostMessages.handleHostMessage(conn, message);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
relayClientRouting.routeClientMessage(conn, message);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { createConnectionDataHandler };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const {
|
|
2
|
+
decryptPartyPayload,
|
|
3
|
+
isEncryptedPayload,
|
|
4
|
+
nostrEventToPartyEvent,
|
|
5
|
+
MATTERHORN_OPERATION_NOSTR_KIND
|
|
6
|
+
} = require("@mh-gg/event");
|
|
7
|
+
const { safeValidate, validateRoomOperation } = require("@mh-gg/protocol");
|
|
8
|
+
|
|
9
|
+
function tagValue(tags, name) {
|
|
10
|
+
const tag = Array.isArray(tags) ? tags.find((item) => Array.isArray(item) && item[0] === name) : undefined;
|
|
11
|
+
return tag ? tag[1] : undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function roomNameFromOperationEvent(nostrEvent) {
|
|
15
|
+
if (nostrEvent?.kind !== MATTERHORN_OPERATION_NOSTR_KIND) return undefined;
|
|
16
|
+
return tagValue(nostrEvent.tags, "d");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseOperationPayload(event, roomSecret) {
|
|
20
|
+
if (!isEncryptedPayload(event.payload)) return event.payload;
|
|
21
|
+
if (!roomSecret) return undefined;
|
|
22
|
+
return decryptPartyPayload({
|
|
23
|
+
roomSecret,
|
|
24
|
+
partyId: event.partyId,
|
|
25
|
+
kind: event.kind,
|
|
26
|
+
payload: event.payload,
|
|
27
|
+
header: event.header
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseMatterhornOperationEvent(nostrEvent, options = {}) {
|
|
32
|
+
let event;
|
|
33
|
+
try {
|
|
34
|
+
event = nostrEventToPartyEvent(nostrEvent);
|
|
35
|
+
} catch {
|
|
36
|
+
return { ok: false, reason: "invalid-matterhorn-event" };
|
|
37
|
+
}
|
|
38
|
+
if (event.kind !== "room.operation") return { ok: false, reason: "not-room-operation" };
|
|
39
|
+
|
|
40
|
+
const header = event.header;
|
|
41
|
+
const isV2 = header && header.scheme === "matterhorn.operation.v2";
|
|
42
|
+
|
|
43
|
+
// v2 events carry a plaintext header in Nostr tags. Relays MUST NOT need a room secret
|
|
44
|
+
// to validate and store them. Decryption is a client-only concern.
|
|
45
|
+
if (isV2) {
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
roomName: event.partyId,
|
|
49
|
+
header,
|
|
50
|
+
encryptedPayload: event.payload,
|
|
51
|
+
// Decrypted application payload only available when a roomSecret is explicitly supplied.
|
|
52
|
+
payload: options.roomSecret ? parseOperationPayload(event, options.roomSecret) : undefined
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Legacy plaintext/encrypted path (kept for transitional tooling). This path is only
|
|
57
|
+
// usable when the caller can supply a roomSecret for encrypted legacy payloads.
|
|
58
|
+
let payload;
|
|
59
|
+
try {
|
|
60
|
+
payload = parseOperationPayload(event, options.roomSecret);
|
|
61
|
+
} catch {
|
|
62
|
+
return { ok: false, reason: "operation-decrypt-failed" };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const operation = payload?.operation;
|
|
66
|
+
const validation = safeValidate(validateRoomOperation, operation);
|
|
67
|
+
if (!validation.ok) return { ok: false, reason: validation.message || "invalid-operation" };
|
|
68
|
+
if (operation.roomId !== event.partyId) return { ok: false, reason: "operation-room-mismatch" };
|
|
69
|
+
if (!isEncryptedPayload(event.payload) && operation.auth?.publicKey && nostrEvent?.pubkey && operation.auth.publicKey !== nostrEvent.pubkey) {
|
|
70
|
+
return { ok: false, reason: "operation-event-signer-mismatch" };
|
|
71
|
+
}
|
|
72
|
+
return { ok: true, roomName: event.partyId, operation };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function operationSortValue(operation) {
|
|
76
|
+
const createdAt = Number(operation?.createdAt || 0);
|
|
77
|
+
return Number.isFinite(createdAt) ? createdAt : 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function compareMatterhornOperations(left, right) {
|
|
81
|
+
const byCreatedAt = operationSortValue(left.operation) - operationSortValue(right.operation);
|
|
82
|
+
if (byCreatedAt !== 0) return byCreatedAt;
|
|
83
|
+
return String(left.operation.id).localeCompare(String(right.operation.id));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseMatterhornOperationEvents(events, options = {}) {
|
|
87
|
+
const parsed = [];
|
|
88
|
+
for (const event of events) {
|
|
89
|
+
const result = parseMatterhornOperationEvent(event, options);
|
|
90
|
+
if (result.ok) parsed.push(result);
|
|
91
|
+
}
|
|
92
|
+
return parsed.sort(compareMatterhornOperations);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
MATTERHORN_OPERATION_NOSTR_KIND,
|
|
97
|
+
parseMatterhornOperationEvent,
|
|
98
|
+
parseMatterhornOperationEvents,
|
|
99
|
+
roomNameFromOperationEvent
|
|
100
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
const { PROTOCOL } = require("@mh-gg/host-config");
|
|
2
|
+
const { generatePartyIdentity, MATTERHORN_INDEX_NGRAM_NOSTR_KIND } = require("@mh-gg/event");
|
|
3
|
+
const {
|
|
4
|
+
MATTERHORN_OPERATION_NOSTR_KIND,
|
|
5
|
+
parseMatterhornOperationEvent,
|
|
6
|
+
parseMatterhornOperationEvents,
|
|
7
|
+
roomNameFromOperationEvent
|
|
8
|
+
} = require("./matterhornOperationEvents.cjs");
|
|
9
|
+
|
|
10
|
+
function createMatterhornRuntimeEventBridge(options) {
|
|
11
|
+
const operationEventIdentity = options.operationEventIdentity || generatePartyIdentity(options.startedAt);
|
|
12
|
+
const runtimeEventOrigin = {};
|
|
13
|
+
const debug = options.debug || (() => {});
|
|
14
|
+
const encryptedRoomRuntimes = options.encryptedRoomRuntimes;
|
|
15
|
+
|
|
16
|
+
function relay() {
|
|
17
|
+
return options.relay();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function relayPluginRuntimes() {
|
|
21
|
+
return options.relayPluginRuntimes();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function relayClientRouting() {
|
|
25
|
+
return options.relayClientRouting();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function tagValue(tags, name) {
|
|
29
|
+
if (!Array.isArray(tags)) return undefined;
|
|
30
|
+
const tag = tags.find((item) => Array.isArray(item) && item[0] === name && typeof item[1] === "string");
|
|
31
|
+
return tag?.[1];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function roomNameFromControlPlaneEvent(event) {
|
|
35
|
+
return tagValue(event?.tags, "d") || tagValue(event?.tags, "room");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleRoomIndexNgramEvent(event, context = {}) {
|
|
39
|
+
if (context.originConn === runtimeEventOrigin) return;
|
|
40
|
+
if (event?.kind !== MATTERHORN_INDEX_NGRAM_NOSTR_KIND) return;
|
|
41
|
+
const roomName = roomNameFromControlPlaneEvent(event);
|
|
42
|
+
if (!roomName) return;
|
|
43
|
+
encryptedRoomRuntimes?.insertEvent?.(roomName, event);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// publishMatterhornOperation is a LEGACY path for plaintext relay-as-host rooms.
|
|
47
|
+
// Encrypted v2 rooms send pre-encrypted events directly; this function is not used for them.
|
|
48
|
+
// The bridge itself never holds or accesses room secrets.
|
|
49
|
+
function publishMatterhornOperation(roomName, operation) {
|
|
50
|
+
let event;
|
|
51
|
+
try {
|
|
52
|
+
// For legacy plaintext operations, wrap them in the simple envelope the relay can sign.
|
|
53
|
+
// This intentionally does not encrypt; durable encryption for encrypted rooms is done client-side.
|
|
54
|
+
event = {
|
|
55
|
+
kind: MATTERHORN_OPERATION_NOSTR_KIND,
|
|
56
|
+
created_at: Math.floor(operation.createdAt / 1000),
|
|
57
|
+
tags: [
|
|
58
|
+
["protocol", PROTOCOL],
|
|
59
|
+
["version", "1"],
|
|
60
|
+
["d", roomName],
|
|
61
|
+
["created-at-ms", String(operation.createdAt)]
|
|
62
|
+
],
|
|
63
|
+
content: JSON.stringify({ payload: { operation } }),
|
|
64
|
+
pubkey: operationEventIdentity.pubkey
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
debug(`matterhorn operation event creation failed for ${roomName}: ${error?.message || String(error)}`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const result = relay().publish(event, runtimeEventOrigin);
|
|
71
|
+
if (!result.ok) debug(`matterhorn operation event publish failed for ${roomName}: ${result.message || "unknown error"}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function applyMatterhornOperation(operation, options = {}) {
|
|
75
|
+
const runtimes = relayPluginRuntimes();
|
|
76
|
+
if (!runtimes?.hasRoom?.(operation.roomId)) return;
|
|
77
|
+
const result = await runtimes.handleRelayOperation({
|
|
78
|
+
roomName: operation.roomId,
|
|
79
|
+
operation
|
|
80
|
+
});
|
|
81
|
+
if (!options.broadcast || !result?.ok || !result.state) return;
|
|
82
|
+
relayClientRouting()?.broadcastToRoom(operation.roomId, {
|
|
83
|
+
type: "host/matterhorn-state",
|
|
84
|
+
protocol: PROTOCOL,
|
|
85
|
+
roomName: operation.roomId,
|
|
86
|
+
acceptedOperationId: operation.id,
|
|
87
|
+
state: result.state
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function operationFromV2(header, payload, roomName) {
|
|
92
|
+
return {
|
|
93
|
+
id: header.opId,
|
|
94
|
+
hlc: header.hlc,
|
|
95
|
+
roomId: roomName,
|
|
96
|
+
pluginId: header.plugin,
|
|
97
|
+
type: header.type,
|
|
98
|
+
schemaAction: header.action,
|
|
99
|
+
actor: {
|
|
100
|
+
memberId: header.member,
|
|
101
|
+
deviceId: header.device,
|
|
102
|
+
role: header.role
|
|
103
|
+
},
|
|
104
|
+
grants: header.grants,
|
|
105
|
+
epoch: header.epoch,
|
|
106
|
+
stream: header.stream,
|
|
107
|
+
seq: header.seq,
|
|
108
|
+
appPackId: header.appPackId,
|
|
109
|
+
appPackHash: header.appPackHash,
|
|
110
|
+
payload
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function applyMatterhornOperationEvent(event, options = {}) {
|
|
115
|
+
const roomName = roomNameFromOperationEvent(event);
|
|
116
|
+
if (!roomName) return;
|
|
117
|
+
|
|
118
|
+
// Parse WITHOUT a room secret. Relays must never hold room secrets for the operation event path.
|
|
119
|
+
const parsed = parseMatterhornOperationEvent(event);
|
|
120
|
+
if (!parsed.ok) {
|
|
121
|
+
debug(`matterhorn operation event ignored for ${roomName}: ${parsed.reason}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (parsed.operation) {
|
|
126
|
+
// Legacy plaintext operation path. Only apply if the room has a plugin runtime.
|
|
127
|
+
const runtimes = relayPluginRuntimes();
|
|
128
|
+
if (!runtimes?.hasRoom?.(roomName)) return;
|
|
129
|
+
await applyMatterhornOperation(parsed.operation, options);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (parsed.header) {
|
|
134
|
+
// v2 encrypted event: verify-and-store only. The relay never decrypts the payload.
|
|
135
|
+
if (parsed.payload) {
|
|
136
|
+
// A payload here means this was called with a roomSecret (legacy/config error). Do not apply.
|
|
137
|
+
debug(`matterhorn v2 operation event ignored for ${roomName}: relay was given a room secret`);
|
|
138
|
+
} else {
|
|
139
|
+
debug(`matterhorn v2 operation event stored without decryption for ${roomName}: ${parsed.header.opId}`);
|
|
140
|
+
encryptedRoomRuntimes?.insertEvent?.(roomName, event);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function handleMatterhornOperationEvent(event, context = {}) {
|
|
146
|
+
if (context.originConn === runtimeEventOrigin) return;
|
|
147
|
+
if (event?.kind === MATTERHORN_INDEX_NGRAM_NOSTR_KIND) {
|
|
148
|
+
handleRoomIndexNgramEvent(event, context);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
void applyMatterhornOperationEvent(event, { broadcast: true }).catch((error) => {
|
|
152
|
+
debug(`matterhorn operation event apply failed: ${error?.message || String(error)}`);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function catchUpMatterhornRuntime(roomName, roomSecret) {
|
|
157
|
+
const runtimes = relayPluginRuntimes();
|
|
158
|
+
if (!runtimes?.hasRoom?.(roomName)) return;
|
|
159
|
+
const events = relay().query([{ kinds: [MATTERHORN_OPERATION_NOSTR_KIND], "#d": [roomName] }]);
|
|
160
|
+
|
|
161
|
+
// Parse WITHOUT a room secret. v2 encrypted events are header-only; legacy events are plaintext.
|
|
162
|
+
for (const parsed of parseMatterhornOperationEvents(events)) {
|
|
163
|
+
if (parsed.operation) {
|
|
164
|
+
// Legacy plaintext operation path.
|
|
165
|
+
await applyMatterhornOperation(parsed.operation, { broadcast: false });
|
|
166
|
+
} else if (parsed.header) {
|
|
167
|
+
// v2 encrypted event: stored by the relay event policy. No replay needed.
|
|
168
|
+
debug(`matterhorn v2 operation event caught up without decryption for ${roomName}: ${parsed.header.opId}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
catchUpMatterhornRuntime,
|
|
175
|
+
handleMatterhornOperationEvent,
|
|
176
|
+
publishMatterhornOperation
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
createMatterhornRuntimeEventBridge
|
|
182
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const { MatterhornNostrRelay } = require("@mh-gg/relay-core");
|
|
2
|
+
const { createPartyEventPolicy } = require("@mh-gg/event");
|
|
3
|
+
const { createSqliteRelayStorage } = require("../sqliteRelayStorage.cjs");
|
|
4
|
+
|
|
5
|
+
function createRelayEventStore(config, options = {}) {
|
|
6
|
+
const storage = options.storage || createSqliteRelayStorage(config.storagePath);
|
|
7
|
+
const relay = new MatterhornNostrRelay({
|
|
8
|
+
eventPolicy: createPartyEventPolicy({
|
|
9
|
+
maxEventBytes: config.maxEventBytes,
|
|
10
|
+
maxFutureMs: config.maxFutureSeconds * 1000
|
|
11
|
+
}),
|
|
12
|
+
maxEventBytes: config.maxEventBytes,
|
|
13
|
+
maxEvents: config.maxEvents,
|
|
14
|
+
maxEventsPerRoom: config.maxEventsPerRoom,
|
|
15
|
+
maxBytesPerRoom: config.maxBytesPerRoom,
|
|
16
|
+
maxEventsPerPubkeyWindow: config.maxEventsPerPubkeyWindow,
|
|
17
|
+
pubkeyQuotaWindowSeconds: config.pubkeyQuotaWindowSeconds,
|
|
18
|
+
maxFutureSeconds: config.maxFutureSeconds,
|
|
19
|
+
maxMessageBytes: config.maxNostrMessageBytes,
|
|
20
|
+
onEvent: options.onEvent,
|
|
21
|
+
storage,
|
|
22
|
+
lazyStorage: options.lazyStorage !== false
|
|
23
|
+
});
|
|
24
|
+
return { relay, storage };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
createRelayEventStore,
|
|
29
|
+
createRelayEventPolicy: createPartyEventPolicy
|
|
30
|
+
};
|