@highway1/core 0.1.53 → 0.1.55
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/package.json +5 -18
- package/src/discovery/agent-card-encoder.ts +0 -119
- package/src/discovery/agent-card-schema.ts +0 -87
- package/src/discovery/agent-card-types.ts +0 -99
- package/src/discovery/agent-card.ts +0 -190
- package/src/discovery/bootstrap.ts +0 -63
- package/src/discovery/capability-matcher.ts +0 -167
- package/src/discovery/dht.ts +0 -310
- package/src/discovery/index.ts +0 -3
- package/src/discovery/relay-index.ts +0 -98
- package/src/discovery/search-index.ts +0 -247
- package/src/discovery/semantic-search.ts +0 -218
- package/src/identity/did.ts +0 -48
- package/src/identity/document.ts +0 -77
- package/src/identity/index.ts +0 -4
- package/src/identity/keys.ts +0 -79
- package/src/identity/signer.ts +0 -55
- package/src/index.ts +0 -39
- package/src/messaging/codec.ts +0 -47
- package/src/messaging/defense.ts +0 -236
- package/src/messaging/envelope.ts +0 -107
- package/src/messaging/index.ts +0 -8
- package/src/messaging/queue.ts +0 -181
- package/src/messaging/rate-limiter.ts +0 -85
- package/src/messaging/router.ts +0 -209
- package/src/messaging/storage.ts +0 -281
- package/src/messaging/types.ts +0 -149
- package/src/transport/connection.ts +0 -77
- package/src/transport/index.ts +0 -2
- package/src/transport/node.ts +0 -154
- package/src/transport/relay-client.ts +0 -437
- package/src/transport/relay-types.ts +0 -196
- package/src/trust/endorsement.ts +0 -167
- package/src/trust/index.ts +0 -194
- package/src/trust/interaction-history.ts +0 -155
- package/src/trust/sybil-defense.ts +0 -232
- package/src/trust/trust-score.ts +0 -136
- package/src/utils/errors.ts +0 -38
- package/src/utils/logger.ts +0 -48
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Token Bucket Rate Limiter
|
|
3
|
-
*
|
|
4
|
-
* Classic token bucket algorithm for per-sender rate limiting.
|
|
5
|
-
* Tokens refill at a constant rate up to capacity.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface TokenBucketConfig {
|
|
9
|
-
capacity: number; // Max tokens (burst size)
|
|
10
|
-
refillRate: number; // Tokens per millisecond
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class TokenBucket {
|
|
14
|
-
private tokens: number;
|
|
15
|
-
private lastRefill: number;
|
|
16
|
-
private readonly capacity: number;
|
|
17
|
-
private readonly refillRate: number; // tokens per ms
|
|
18
|
-
|
|
19
|
-
constructor(config: TokenBucketConfig, initialTokens?: number, lastRefill?: number) {
|
|
20
|
-
this.capacity = config.capacity;
|
|
21
|
-
this.refillRate = config.refillRate;
|
|
22
|
-
this.tokens = initialTokens ?? config.capacity;
|
|
23
|
-
this.lastRefill = lastRefill ?? Date.now();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Attempt to consume one token. Returns true if allowed. */
|
|
27
|
-
consume(): boolean {
|
|
28
|
-
this.refill();
|
|
29
|
-
if (this.tokens >= 1) {
|
|
30
|
-
this.tokens -= 1;
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getRemaining(): number {
|
|
37
|
-
this.refill();
|
|
38
|
-
return Math.floor(this.tokens);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Milliseconds until at least one token is available */
|
|
42
|
-
getResetTime(): number {
|
|
43
|
-
this.refill();
|
|
44
|
-
if (this.tokens >= 1) return 0;
|
|
45
|
-
const needed = 1 - this.tokens;
|
|
46
|
-
return Math.ceil(needed / this.refillRate);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Serialize state for persistence */
|
|
50
|
-
toState(): { tokens: number; lastRefill: number } {
|
|
51
|
-
return { tokens: this.tokens, lastRefill: this.lastRefill };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private refill(): void {
|
|
55
|
-
const now = Date.now();
|
|
56
|
-
const elapsed = now - this.lastRefill;
|
|
57
|
-
const newTokens = elapsed * this.refillRate;
|
|
58
|
-
this.tokens = Math.min(this.capacity, this.tokens + newTokens);
|
|
59
|
-
this.lastRefill = now;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Rate limiter tiers based on trust score
|
|
65
|
-
*/
|
|
66
|
-
export interface RateLimitTiers {
|
|
67
|
-
/** Trust < 0.3: new/unknown agents */
|
|
68
|
-
newAgent: TokenBucketConfig;
|
|
69
|
-
/** Trust 0.3–0.6: established agents */
|
|
70
|
-
established: TokenBucketConfig;
|
|
71
|
-
/** Trust > 0.6: trusted agents */
|
|
72
|
-
trusted: TokenBucketConfig;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const DEFAULT_RATE_LIMIT_TIERS: RateLimitTiers = {
|
|
76
|
-
newAgent: { capacity: 10, refillRate: 10 / (60 * 1000) }, // 10/min, burst 10
|
|
77
|
-
established: { capacity: 100, refillRate: 60 / (60 * 1000) }, // 60/min, burst 100
|
|
78
|
-
trusted: { capacity: 1000, refillRate: 600 / (60 * 1000) }, // 600/min, burst 1000
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export function getTierConfig(trustScore: number, tiers: RateLimitTiers): TokenBucketConfig {
|
|
82
|
-
if (trustScore >= 0.6) return tiers.trusted;
|
|
83
|
-
if (trustScore >= 0.3) return tiers.established;
|
|
84
|
-
return tiers.newAgent;
|
|
85
|
-
}
|
package/src/messaging/router.ts
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import type { MessageEnvelope } from './envelope.js';
|
|
2
|
-
import type { RelayClient } from '../transport/relay-client.js';
|
|
3
|
-
import { encodeMessage, decodeMessage } from './codec.js';
|
|
4
|
-
import { validateEnvelope, verifyEnvelope } from './envelope.js';
|
|
5
|
-
import { createLogger } from '../utils/logger.js';
|
|
6
|
-
import { MessagingError } from '../utils/errors.js';
|
|
7
|
-
import { extractPublicKey } from '../identity/did.js';
|
|
8
|
-
import { verify } from '../identity/keys.js';
|
|
9
|
-
|
|
10
|
-
const logger = createLogger('router');
|
|
11
|
-
|
|
12
|
-
export type MessageHandler = (
|
|
13
|
-
envelope: MessageEnvelope
|
|
14
|
-
) => Promise<MessageEnvelope | void>;
|
|
15
|
-
|
|
16
|
-
export interface MessageRouter {
|
|
17
|
-
registerHandler: (protocol: string, handler: MessageHandler) => void;
|
|
18
|
-
unregisterHandler: (protocol: string) => void;
|
|
19
|
-
registerCatchAllHandler: (handler: MessageHandler) => void;
|
|
20
|
-
sendMessage: (envelope: MessageEnvelope) => Promise<MessageEnvelope | void>;
|
|
21
|
-
start: () => Promise<void>;
|
|
22
|
-
stop: () => Promise<void>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Create a message router using relay client (CVP-0011)
|
|
27
|
-
*/
|
|
28
|
-
export function createMessageRouter(
|
|
29
|
-
relayClient: RelayClient,
|
|
30
|
-
verifyFn: (signature: Uint8Array, data: Uint8Array) => Promise<boolean>
|
|
31
|
-
): MessageRouter {
|
|
32
|
-
const handlers = new Map<string, MessageHandler>();
|
|
33
|
-
let catchAllHandler: MessageHandler | undefined;
|
|
34
|
-
|
|
35
|
-
// Map to track pending requests waiting for responses
|
|
36
|
-
const pendingRequests = new Map<string, {
|
|
37
|
-
resolve: (envelope: MessageEnvelope | undefined) => void;
|
|
38
|
-
timeout: NodeJS.Timeout;
|
|
39
|
-
}>();
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
registerHandler: (protocol: string, handler: MessageHandler) => {
|
|
43
|
-
handlers.set(protocol, handler);
|
|
44
|
-
logger.info('Registered message handler', { protocol });
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
unregisterHandler: (protocol: string) => {
|
|
48
|
-
handlers.delete(protocol);
|
|
49
|
-
logger.info('Unregistered message handler', { protocol });
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
registerCatchAllHandler: (handler: MessageHandler) => {
|
|
53
|
-
catchAllHandler = handler;
|
|
54
|
-
logger.info('Registered catch-all message handler');
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
sendMessage: async (envelope: MessageEnvelope) => {
|
|
58
|
-
try {
|
|
59
|
-
if (!validateEnvelope(envelope)) {
|
|
60
|
-
throw new MessagingError('Invalid message envelope');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const encoded = encodeMessage(envelope);
|
|
64
|
-
await relayClient.sendEnvelope(envelope.to, encoded);
|
|
65
|
-
|
|
66
|
-
logger.info('Message sent via relay', {
|
|
67
|
-
id: envelope.id,
|
|
68
|
-
from: envelope.from,
|
|
69
|
-
to: envelope.to,
|
|
70
|
-
protocol: envelope.protocol,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// If this is a request, wait for response
|
|
74
|
-
if (envelope.type === 'request') {
|
|
75
|
-
logger.debug('Waiting for response to request', { id: envelope.id });
|
|
76
|
-
|
|
77
|
-
const RESPONSE_TIMEOUT = 30000; // 30 seconds (CVP-0010 §4.2)
|
|
78
|
-
|
|
79
|
-
return new Promise<MessageEnvelope | undefined>((resolve) => {
|
|
80
|
-
const timeout = setTimeout(() => {
|
|
81
|
-
pendingRequests.delete(envelope.id);
|
|
82
|
-
logger.warn('Response timeout', { id: envelope.id, timeout: RESPONSE_TIMEOUT });
|
|
83
|
-
resolve(undefined);
|
|
84
|
-
}, RESPONSE_TIMEOUT);
|
|
85
|
-
|
|
86
|
-
pendingRequests.set(envelope.id, { resolve, timeout });
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return undefined;
|
|
91
|
-
} catch (error) {
|
|
92
|
-
if (error instanceof MessagingError) throw error;
|
|
93
|
-
throw new MessagingError('Failed to send message', error);
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
start: async () => {
|
|
98
|
-
// Register handler for incoming DELIVER messages
|
|
99
|
-
relayClient.onDeliver(async (deliverMsg) => {
|
|
100
|
-
try {
|
|
101
|
-
const envelope = decodeMessage(deliverMsg.envelope);
|
|
102
|
-
|
|
103
|
-
if (!validateEnvelope(envelope)) {
|
|
104
|
-
logger.warn('Received invalid message envelope');
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Verify signature against sender DID embedded public key
|
|
109
|
-
const isValidSignature = await verifyEnvelope(envelope, async (signature, data) => {
|
|
110
|
-
const senderPublicKey = extractPublicKey(envelope.from);
|
|
111
|
-
return verify(signature, data, senderPublicKey);
|
|
112
|
-
});
|
|
113
|
-
if (!isValidSignature) {
|
|
114
|
-
logger.warn('Received message with invalid signature', {
|
|
115
|
-
id: envelope.id,
|
|
116
|
-
from: envelope.from,
|
|
117
|
-
});
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Optional caller-supplied verification hook (e.g. policy checks)
|
|
122
|
-
try {
|
|
123
|
-
const { signature, ...envelopeWithoutSig } = envelope;
|
|
124
|
-
const dataBytes = new TextEncoder().encode(JSON.stringify(envelopeWithoutSig));
|
|
125
|
-
const signatureBytes = Buffer.from(signature, 'hex');
|
|
126
|
-
const hookValid = await verifyFn(signatureBytes, dataBytes);
|
|
127
|
-
if (!hookValid) {
|
|
128
|
-
logger.warn('Message rejected by custom verifier', {
|
|
129
|
-
id: envelope.id,
|
|
130
|
-
from: envelope.from,
|
|
131
|
-
});
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
} catch (error) {
|
|
135
|
-
logger.warn('Custom verification hook failed', {
|
|
136
|
-
id: envelope.id,
|
|
137
|
-
from: envelope.from,
|
|
138
|
-
error: (error as Error).message,
|
|
139
|
-
});
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
logger.info('Received message', {
|
|
144
|
-
id: envelope.id,
|
|
145
|
-
from: envelope.from,
|
|
146
|
-
to: envelope.to,
|
|
147
|
-
protocol: envelope.protocol,
|
|
148
|
-
type: envelope.type,
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Check if this is a response to a pending request
|
|
152
|
-
if (envelope.type === 'response' && envelope.replyTo) {
|
|
153
|
-
const pending = pendingRequests.get(envelope.replyTo);
|
|
154
|
-
if (pending) {
|
|
155
|
-
clearTimeout(pending.timeout);
|
|
156
|
-
pendingRequests.delete(envelope.replyTo);
|
|
157
|
-
pending.resolve(envelope);
|
|
158
|
-
logger.info('Matched response to pending request', {
|
|
159
|
-
requestId: envelope.replyTo,
|
|
160
|
-
responseId: envelope.id,
|
|
161
|
-
});
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Dispatch to handler
|
|
167
|
-
const handler = handlers.get(envelope.protocol);
|
|
168
|
-
let response: MessageEnvelope | void = undefined;
|
|
169
|
-
|
|
170
|
-
if (handler) {
|
|
171
|
-
response = await handler(envelope);
|
|
172
|
-
} else if (catchAllHandler) {
|
|
173
|
-
logger.debug('Using catch-all handler for protocol', { protocol: envelope.protocol });
|
|
174
|
-
response = await catchAllHandler(envelope);
|
|
175
|
-
} else {
|
|
176
|
-
logger.warn('No handler for protocol', { protocol: envelope.protocol });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Send response back if handler returned one
|
|
180
|
-
if (response) {
|
|
181
|
-
const encoded = encodeMessage(response);
|
|
182
|
-
await relayClient.sendEnvelope(response.to, encoded);
|
|
183
|
-
logger.info('Sent response back to sender', {
|
|
184
|
-
responseId: response.id,
|
|
185
|
-
replyTo: response.replyTo,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
} catch (error) {
|
|
189
|
-
logger.error('Error handling incoming message', error);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
logger.info('Message router started');
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
stop: async () => {
|
|
197
|
-
// Clear all pending requests
|
|
198
|
-
for (const [, pending] of pendingRequests.entries()) {
|
|
199
|
-
clearTimeout(pending.timeout);
|
|
200
|
-
pending.resolve(undefined);
|
|
201
|
-
}
|
|
202
|
-
pendingRequests.clear();
|
|
203
|
-
|
|
204
|
-
handlers.clear();
|
|
205
|
-
catchAllHandler = undefined;
|
|
206
|
-
logger.info('Message router stopped');
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
}
|
package/src/messaging/storage.ts
DELETED
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message Storage - LevelDB operations for message queue
|
|
3
|
-
*
|
|
4
|
-
* Key schema:
|
|
5
|
-
* msg:inbound:{timestamp}:{id} → StoredMessage
|
|
6
|
-
* msg:outbound:{timestamp}:{id} → StoredMessage
|
|
7
|
-
* block:{did} → BlocklistEntry
|
|
8
|
-
* allow:{did} → AllowlistEntry
|
|
9
|
-
* seen:{messageId} → SeenEntry
|
|
10
|
-
* rate:{did} → RateLimitState
|
|
11
|
-
* idx:from:{did}:{timestamp}:{id} → '1'
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { Level } from 'level';
|
|
15
|
-
import { createLogger } from '../utils/logger.js';
|
|
16
|
-
import type {
|
|
17
|
-
StoredMessage,
|
|
18
|
-
BlocklistEntry,
|
|
19
|
-
AllowlistEntry,
|
|
20
|
-
SeenEntry,
|
|
21
|
-
RateLimitState,
|
|
22
|
-
MessageFilter,
|
|
23
|
-
PaginationOptions,
|
|
24
|
-
MessagePage,
|
|
25
|
-
} from './types.js';
|
|
26
|
-
|
|
27
|
-
const logger = createLogger('message-storage');
|
|
28
|
-
|
|
29
|
-
export class MessageStorage {
|
|
30
|
-
private db: Level<string, any>;
|
|
31
|
-
private ready = false;
|
|
32
|
-
|
|
33
|
-
constructor(dbPath: string) {
|
|
34
|
-
this.db = new Level<string, any>(dbPath, { valueEncoding: 'json' });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async open(): Promise<void> {
|
|
38
|
-
await this.db.open();
|
|
39
|
-
this.ready = true;
|
|
40
|
-
logger.info('Message storage opened');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async close(): Promise<void> {
|
|
44
|
-
if (this.ready) {
|
|
45
|
-
await this.db.close();
|
|
46
|
-
this.ready = false;
|
|
47
|
-
logger.info('Message storage closed');
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ─── Message Operations ───────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
async putMessage(msg: StoredMessage): Promise<void> {
|
|
54
|
-
const ts = String(msg.receivedAt ?? msg.sentAt ?? Date.now()).padStart(16, '0');
|
|
55
|
-
const key = `msg:${msg.direction}:${ts}:${msg.envelope.id}`;
|
|
56
|
-
await this.db.put(key, msg);
|
|
57
|
-
|
|
58
|
-
// Secondary index by sender
|
|
59
|
-
const idxKey = `idx:from:${msg.envelope.from}:${ts}:${msg.envelope.id}`;
|
|
60
|
-
await this.db.put(idxKey, '1');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async getMessage(id: string): Promise<StoredMessage | null> {
|
|
64
|
-
// Scan both directions since we don't know the timestamp
|
|
65
|
-
for (const direction of ['inbound', 'outbound'] as const) {
|
|
66
|
-
const prefix = `msg:${direction}:`;
|
|
67
|
-
for await (const [, value] of this.db.iterator<string, StoredMessage>({
|
|
68
|
-
gte: prefix,
|
|
69
|
-
lte: prefix + '\xff',
|
|
70
|
-
valueEncoding: 'json',
|
|
71
|
-
})) {
|
|
72
|
-
if (value.envelope.id === id) return value;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async updateMessage(id: string, updates: Partial<StoredMessage>): Promise<void> {
|
|
79
|
-
const msg = await this.getMessage(id);
|
|
80
|
-
if (!msg) return;
|
|
81
|
-
const ts = String(msg.receivedAt ?? msg.sentAt ?? Date.now()).padStart(16, '0');
|
|
82
|
-
const key = `msg:${msg.direction}:${ts}:${id}`;
|
|
83
|
-
await this.db.put(key, { ...msg, ...updates });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async deleteMessage(id: string): Promise<void> {
|
|
87
|
-
const msg = await this.getMessage(id);
|
|
88
|
-
if (!msg) return;
|
|
89
|
-
const ts = String(msg.receivedAt ?? msg.sentAt ?? Date.now()).padStart(16, '0');
|
|
90
|
-
const key = `msg:${msg.direction}:${ts}:${id}`;
|
|
91
|
-
const idxKey = `idx:from:${msg.envelope.from}:${ts}:${id}`;
|
|
92
|
-
await this.db.batch([
|
|
93
|
-
{ type: 'del', key },
|
|
94
|
-
{ type: 'del', key: idxKey },
|
|
95
|
-
]);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async queryMessages(
|
|
99
|
-
direction: 'inbound' | 'outbound',
|
|
100
|
-
filter: MessageFilter = {},
|
|
101
|
-
pagination: PaginationOptions = {}
|
|
102
|
-
): Promise<MessagePage> {
|
|
103
|
-
const { limit = 50, offset = 0 } = pagination;
|
|
104
|
-
const prefix = `msg:${direction}:`;
|
|
105
|
-
const results: StoredMessage[] = [];
|
|
106
|
-
let total = 0;
|
|
107
|
-
let skipped = 0;
|
|
108
|
-
|
|
109
|
-
for await (const [, value] of this.db.iterator<string, StoredMessage>({
|
|
110
|
-
gte: prefix,
|
|
111
|
-
lte: prefix + '\xff',
|
|
112
|
-
reverse: true, // newest first
|
|
113
|
-
valueEncoding: 'json',
|
|
114
|
-
})) {
|
|
115
|
-
if (!this.matchesFilter(value, filter)) continue;
|
|
116
|
-
total++;
|
|
117
|
-
if (skipped < offset) { skipped++; continue; }
|
|
118
|
-
if (results.length < limit) results.push(value);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
messages: results,
|
|
123
|
-
total,
|
|
124
|
-
hasMore: total > offset + results.length,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private matchesFilter(msg: StoredMessage, filter: MessageFilter): boolean {
|
|
129
|
-
if (filter.fromDid) {
|
|
130
|
-
const froms = Array.isArray(filter.fromDid) ? filter.fromDid : [filter.fromDid];
|
|
131
|
-
if (!froms.includes(msg.envelope.from)) return false;
|
|
132
|
-
}
|
|
133
|
-
if (filter.toDid) {
|
|
134
|
-
const tos = Array.isArray(filter.toDid) ? filter.toDid : [filter.toDid];
|
|
135
|
-
if (!tos.includes(msg.envelope.to)) return false;
|
|
136
|
-
}
|
|
137
|
-
if (filter.protocol) {
|
|
138
|
-
const protos = Array.isArray(filter.protocol) ? filter.protocol : [filter.protocol];
|
|
139
|
-
if (!protos.includes(msg.envelope.protocol)) return false;
|
|
140
|
-
}
|
|
141
|
-
if (filter.type && msg.envelope.type !== filter.type) return false;
|
|
142
|
-
if (filter.unreadOnly && msg.readAt != null) return false;
|
|
143
|
-
if (filter.status) {
|
|
144
|
-
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
145
|
-
if (!statuses.includes(msg.status)) return false;
|
|
146
|
-
}
|
|
147
|
-
if (filter.maxAge) {
|
|
148
|
-
const age = Date.now() - (msg.receivedAt ?? msg.sentAt ?? 0);
|
|
149
|
-
if (age > filter.maxAge) return false;
|
|
150
|
-
}
|
|
151
|
-
if (filter.minTrustScore != null && (msg.trustScore ?? 0) < filter.minTrustScore) return false;
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async countMessages(direction: 'inbound' | 'outbound', filter: MessageFilter = {}): Promise<number> {
|
|
156
|
-
const prefix = `msg:${direction}:`;
|
|
157
|
-
let count = 0;
|
|
158
|
-
for await (const [, value] of this.db.iterator<string, StoredMessage>({
|
|
159
|
-
gte: prefix,
|
|
160
|
-
lte: prefix + '\xff',
|
|
161
|
-
valueEncoding: 'json',
|
|
162
|
-
})) {
|
|
163
|
-
if (this.matchesFilter(value, filter)) count++;
|
|
164
|
-
}
|
|
165
|
-
return count;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// ─── Blocklist ────────────────────────────────────────────────────────────
|
|
169
|
-
|
|
170
|
-
async putBlock(entry: BlocklistEntry): Promise<void> {
|
|
171
|
-
await this.db.put(`block:${entry.did}`, entry);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async getBlock(did: string): Promise<BlocklistEntry | null> {
|
|
175
|
-
try {
|
|
176
|
-
return await this.db.get(`block:${did}`);
|
|
177
|
-
} catch {
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async deleteBlock(did: string): Promise<void> {
|
|
183
|
-
try { await this.db.del(`block:${did}`); } catch { /* not found */ }
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async listBlocked(): Promise<BlocklistEntry[]> {
|
|
187
|
-
const results: BlocklistEntry[] = [];
|
|
188
|
-
for await (const [, value] of this.db.iterator<string, BlocklistEntry>({
|
|
189
|
-
gte: 'block:',
|
|
190
|
-
lte: 'block:\xff',
|
|
191
|
-
valueEncoding: 'json',
|
|
192
|
-
})) {
|
|
193
|
-
results.push(value);
|
|
194
|
-
}
|
|
195
|
-
return results;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// ─── Allowlist ────────────────────────────────────────────────────────────
|
|
199
|
-
|
|
200
|
-
async putAllow(entry: AllowlistEntry): Promise<void> {
|
|
201
|
-
await this.db.put(`allow:${entry.did}`, entry);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async getAllow(did: string): Promise<AllowlistEntry | null> {
|
|
205
|
-
try {
|
|
206
|
-
return await this.db.get(`allow:${did}`);
|
|
207
|
-
} catch {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async deleteAllow(did: string): Promise<void> {
|
|
213
|
-
try { await this.db.del(`allow:${did}`); } catch { /* not found */ }
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async listAllowed(): Promise<AllowlistEntry[]> {
|
|
217
|
-
const results: AllowlistEntry[] = [];
|
|
218
|
-
for await (const [, value] of this.db.iterator<string, AllowlistEntry>({
|
|
219
|
-
gte: 'allow:',
|
|
220
|
-
lte: 'allow:\xff',
|
|
221
|
-
valueEncoding: 'json',
|
|
222
|
-
})) {
|
|
223
|
-
results.push(value);
|
|
224
|
-
}
|
|
225
|
-
return results;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// ─── Seen Cache ───────────────────────────────────────────────────────────
|
|
229
|
-
|
|
230
|
-
async putSeen(entry: SeenEntry): Promise<void> {
|
|
231
|
-
await this.db.put(`seen:${entry.messageId}`, entry);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async getSeen(messageId: string): Promise<SeenEntry | null> {
|
|
235
|
-
try {
|
|
236
|
-
return await this.db.get(`seen:${messageId}`);
|
|
237
|
-
} catch {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async cleanupSeen(maxAgeMs: number): Promise<void> {
|
|
243
|
-
const cutoff = Date.now() - maxAgeMs;
|
|
244
|
-
const toDelete: string[] = [];
|
|
245
|
-
for await (const [key, value] of this.db.iterator<string, SeenEntry>({
|
|
246
|
-
gte: 'seen:',
|
|
247
|
-
lte: 'seen:\xff',
|
|
248
|
-
valueEncoding: 'json',
|
|
249
|
-
})) {
|
|
250
|
-
if (value.seenAt < cutoff) toDelete.push(key);
|
|
251
|
-
}
|
|
252
|
-
await this.db.batch(toDelete.map((key) => ({ type: 'del' as const, key })));
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// ─── Rate Limit State ─────────────────────────────────────────────────────
|
|
256
|
-
|
|
257
|
-
async putRateLimit(state: RateLimitState): Promise<void> {
|
|
258
|
-
await this.db.put(`rate:${state.did}`, state);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async getRateLimit(did: string): Promise<RateLimitState | null> {
|
|
262
|
-
try {
|
|
263
|
-
return await this.db.get(`rate:${did}`);
|
|
264
|
-
} catch {
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async cleanupRateLimits(maxAgeMs: number): Promise<void> {
|
|
270
|
-
const cutoff = Date.now() - maxAgeMs;
|
|
271
|
-
const toDelete: string[] = [];
|
|
272
|
-
for await (const [key, value] of this.db.iterator<string, RateLimitState>({
|
|
273
|
-
gte: 'rate:',
|
|
274
|
-
lte: 'rate:\xff',
|
|
275
|
-
valueEncoding: 'json',
|
|
276
|
-
})) {
|
|
277
|
-
if (value.lastRefill < cutoff) toDelete.push(key);
|
|
278
|
-
}
|
|
279
|
-
await this.db.batch(toDelete.map((key) => ({ type: 'del' as const, key })));
|
|
280
|
-
}
|
|
281
|
-
}
|