@private.me/xbind 3.0.0 → 3.0.2
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 +55 -7
- package/dist-standalone/_deps/mldsa-wasm/dist/mldsa.js +1920 -1
- package/dist-standalone/_deps/shared/cjs/errors.js +729 -1
- package/dist-standalone/_deps/shared/cjs/index.js +463 -1
- package/dist-standalone/_deps/shared/cjs/types.js +315 -1
- package/dist-standalone/_deps/shared/errors.js +244 -1
- package/dist-standalone/_deps/shared/index.js +72 -1
- package/dist-standalone/_deps/shared/types.js +86 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/index.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/pagination.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/progress.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/search.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/types.js +1 -1
- package/dist-standalone/_deps/ux-helpers/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/index.js +1 -1
- package/dist-standalone/_deps/ux-helpers/pagination.js +1 -1
- package/dist-standalone/_deps/ux-helpers/progress.js +1 -1
- package/dist-standalone/_deps/ux-helpers/search.js +1 -1
- package/dist-standalone/_deps/xchange/auto-accept.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/auto-accept.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/errors.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/index.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/invite-client.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/lazy-init.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/trust-integration.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/xchange.js +1 -1
- package/dist-standalone/_deps/xchange/errors.js +1 -1
- package/dist-standalone/_deps/xchange/index.js +1 -1
- package/dist-standalone/_deps/xchange/invite-client.js +1 -1
- package/dist-standalone/_deps/xchange/lazy-init.js +1 -1
- package/dist-standalone/_deps/xchange/trust-integration.js +1 -1
- package/dist-standalone/_deps/xchange/xchange.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/discovery.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/errors.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/index.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/registry.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/schema.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/types.js +1 -1
- package/dist-standalone/_deps/xregistry/discovery.js +1 -1
- package/dist-standalone/_deps/xregistry/errors.js +1 -1
- package/dist-standalone/_deps/xregistry/index.js +1 -1
- package/dist-standalone/_deps/xregistry/registry.js +1 -1
- package/dist-standalone/_deps/xregistry/schema.js +1 -1
- package/dist-standalone/_deps/xregistry/types.js +1 -1
- package/dist-standalone/agent-call.js +659 -1
- package/dist-standalone/agent-sdk.js +328 -1
- package/dist-standalone/agent.js +1800 -1
- package/dist-standalone/approval.js +193 -1
- package/dist-standalone/async-iterators.js +382 -1
- package/dist-standalone/auth.js +219 -1
- package/dist-standalone/auto-accept.js +229 -1
- package/dist-standalone/backup-config.js +201 -1
- package/dist-standalone/backup.js +326 -1
- package/dist-standalone/batch-operations.js +388 -1
- package/dist-standalone/cancellation.js +477 -1
- package/dist-standalone/checkpoint.js +186 -1
- package/dist-standalone/circuit-breaker.js +468 -1
- package/dist-standalone/cjs/agent-call.js +701 -1
- package/dist-standalone/cjs/agent-sdk.js +332 -1
- package/dist-standalone/cjs/agent.js +1837 -1
- package/dist-standalone/cjs/approval.js +199 -1
- package/dist-standalone/cjs/async-iterators.js +392 -1
- package/dist-standalone/cjs/auth.js +225 -1
- package/dist-standalone/cjs/auto-accept.js +233 -1
- package/dist-standalone/cjs/backup-config.js +207 -1
- package/dist-standalone/cjs/backup.js +330 -1
- package/dist-standalone/cjs/batch-operations.js +397 -1
- package/dist-standalone/cjs/cancellation.js +490 -1
- package/dist-standalone/cjs/checkpoint.js +193 -1
- package/dist-standalone/cjs/circuit-breaker.js +476 -1
- package/dist-standalone/cjs/cli/init.js +492 -1
- package/dist-standalone/cjs/config-validation.js +522 -1
- package/dist-standalone/cjs/connect.js +312 -1
- package/dist-standalone/cjs/connection-pool.js +506 -1
- package/dist-standalone/cjs/correlation-id.js +339 -1
- package/dist-standalone/cjs/crypto-utils.js +176 -1
- package/dist-standalone/cjs/debug-mode.js +534 -1
- package/dist-standalone/cjs/did-document.js +101 -1
- package/dist-standalone/cjs/did-privateme.js +130 -1
- package/dist-standalone/cjs/did-web.js +201 -1
- package/dist-standalone/cjs/discovery.js +462 -1
- package/dist-standalone/cjs/dual-mode.js +251 -1
- package/dist-standalone/cjs/email-templates.js +313 -1
- package/dist-standalone/cjs/email-transport.js +239 -1
- package/dist-standalone/cjs/envelope.js +538 -1
- package/dist-standalone/cjs/errors.js +913 -1
- package/dist-standalone/cjs/event-emitter.js +461 -1
- package/dist-standalone/cjs/gateway-state.js +55 -1
- package/dist-standalone/cjs/gateway-transport.js +120 -1
- package/dist-standalone/cjs/graceful-degradation.js +403 -1
- package/dist-standalone/cjs/guardrails.js +223 -1
- package/dist-standalone/cjs/health-check.js +336 -1
- package/dist-standalone/cjs/http-compat.js +272 -1
- package/dist-standalone/cjs/http-status-map.js +571 -1
- package/dist-standalone/cjs/identity.js +645 -1
- package/dist-standalone/cjs/index.js +406 -1
- package/dist-standalone/cjs/invitation.js +421 -1
- package/dist-standalone/cjs/invite.js +328 -1
- package/dist-standalone/cjs/key-agreement.js +335 -1
- package/dist-standalone/cjs/lazy-init.js +300 -1
- package/dist-standalone/cjs/logger.js +291 -1
- package/dist-standalone/cjs/mdns-discovery.js +202 -1
- package/dist-standalone/cjs/nonce-store.js +80 -1
- package/dist-standalone/cjs/pairing-manager.js +223 -1
- package/dist-standalone/cjs/plugin-system.js +264 -1
- package/dist-standalone/cjs/plugins/logging.js +168 -1
- package/dist-standalone/cjs/plugins/metrics.js +181 -1
- package/dist-standalone/cjs/plugins/validation.js +302 -1
- package/dist-standalone/cjs/policy.js +320 -1
- package/dist-standalone/cjs/progress-callbacks.js +583 -1
- package/dist-standalone/cjs/redis-nonce-store.js +76 -1
- package/dist-standalone/cjs/registry-middleware.js +50 -1
- package/dist-standalone/cjs/retry-strategies.js +544 -1
- package/dist-standalone/cjs/retry-transport.js +102 -1
- package/dist-standalone/cjs/runtime/browser.js +533 -1
- package/dist-standalone/cjs/runtime/edge.js +526 -1
- package/dist-standalone/cjs/runtime/react-native.js +394 -1
- package/dist-standalone/cjs/security-policy.js +245 -1
- package/dist-standalone/cjs/serialization.js +1040 -1
- package/dist-standalone/cjs/split-channel.js +225 -1
- package/dist-standalone/cjs/subscription-proof.js +230 -1
- package/dist-standalone/cjs/succession.js +148 -1
- package/dist-standalone/cjs/timeouts.js +412 -1
- package/dist-standalone/cjs/trace-context.js +424 -1
- package/dist-standalone/cjs/trace-spans.js +495 -1
- package/dist-standalone/cjs/transport.js +63 -1
- package/dist-standalone/cjs/trust-registry.js +991 -1
- package/dist-standalone/cjs/types/error-response.js +56 -1
- package/dist-standalone/cjs/vault-auth.js +178 -1
- package/dist-standalone/cjs/vault-store-loader.js +194 -1
- package/dist-standalone/cjs/verify.js +25 -1
- package/dist-standalone/cjs/version-info.js +543 -1
- package/dist-standalone/cjs/xfetch.js +340 -1
- package/dist-standalone/cli/init.js +455 -1
- package/dist-standalone/cli/setup.js +514 -1
- package/dist-standalone/cli/types.js +27 -1
- package/dist-standalone/cli/xbind.js +148 -1
- package/dist-standalone/config-validation.js +513 -1
- package/dist-standalone/connect.js +274 -1
- package/dist-standalone/connection-pool.js +500 -1
- package/dist-standalone/correlation-id.js +326 -1
- package/dist-standalone/crypto-utils.js +157 -1
- package/dist-standalone/debug-mode.js +510 -1
- package/dist-standalone/did-document.js +96 -1
- package/dist-standalone/did-privateme.js +121 -1
- package/dist-standalone/did-web.js +196 -1
- package/dist-standalone/discovery.js +458 -1
- package/dist-standalone/dual-mode.js +247 -1
- package/dist-standalone/email-templates.js +309 -1
- package/dist-standalone/email-transport.js +232 -1
- package/dist-standalone/envelope.js +525 -1
- package/dist-standalone/errors.js +896 -1
- package/dist-standalone/event-emitter.js +456 -1
- package/dist-standalone/gateway-state.js +51 -1
- package/dist-standalone/gateway-transport.js +116 -1
- package/dist-standalone/graceful-degradation.js +396 -1
- package/dist-standalone/guardrails.js +216 -1
- package/dist-standalone/health-check.js +332 -1
- package/dist-standalone/http-compat.js +267 -1
- package/dist-standalone/http-status-map.js +561 -1
- package/dist-standalone/identity.js +619 -1
- package/dist-standalone/index.js +78 -1
- package/dist-standalone/invitation.js +415 -1
- package/dist-standalone/invite.js +324 -1
- package/dist-standalone/key-agreement.js +325 -1
- package/dist-standalone/lazy-init.js +295 -1
- package/dist-standalone/logger.js +285 -1
- package/dist-standalone/mdns-discovery.js +195 -1
- package/dist-standalone/nonce-store.js +76 -1
- package/dist-standalone/pairing-manager.js +219 -1
- package/dist-standalone/plugin-system.js +257 -1
- package/dist-standalone/plugins/logging.d.ts +84 -0
- package/dist-standalone/plugins/logging.js +163 -0
- package/dist-standalone/plugins/metrics.d.ts +111 -0
- package/dist-standalone/plugins/metrics.js +176 -0
- package/dist-standalone/plugins/validation.d.ts +104 -0
- package/dist-standalone/plugins/validation.js +297 -0
- package/dist-standalone/policy.js +315 -1
- package/dist-standalone/progress-callbacks.js +576 -1
- package/dist-standalone/redis-nonce-store.js +72 -1
- package/dist-standalone/registry-middleware.js +47 -1
- package/dist-standalone/retry-strategies.js +534 -1
- package/dist-standalone/retry-transport.js +98 -1
- package/dist-standalone/runtime/browser.d.ts +311 -0
- package/dist-standalone/runtime/browser.js +516 -0
- package/dist-standalone/runtime/edge.d.ts +282 -0
- package/dist-standalone/runtime/edge.js +511 -0
- package/dist-standalone/runtime/react-native.d.ts +157 -0
- package/dist-standalone/runtime/react-native.js +383 -0
- package/dist-standalone/security-policy.js +239 -1
- package/dist-standalone/serialization.js +1031 -1
- package/dist-standalone/split-channel.js +219 -1
- package/dist-standalone/subscription-proof.js +224 -1
- package/dist-standalone/succession.js +142 -1
- package/dist-standalone/timeouts.js +398 -1
- package/dist-standalone/trace-context.js +414 -1
- package/dist-standalone/trace-spans.js +488 -1
- package/dist-standalone/transport.js +59 -1
- package/dist-standalone/trust-registry.js +950 -1
- package/dist-standalone/types/error-response.d.ts +209 -0
- package/dist-standalone/types/error-response.js +52 -0
- package/dist-standalone/vault-auth.js +174 -1
- package/dist-standalone/vault-store-loader.js +187 -1
- package/dist-standalone/verify.js +16 -1
- package/dist-standalone/version-info.js +530 -1
- package/dist-standalone/xfetch.js +335 -1
- package/package.json +4 -10
- package/share1.dat +0 -0
- package/dist-standalone/_deps/mldsa-wasm/LICENSE +0 -24
- package/dist-standalone/_deps/mldsa-wasm/package.json +0 -46
- package/dist-standalone/_deps/shared/cjs/package.json +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/package.json +0 -1
- package/dist-standalone/_deps/xchange/cjs/package.json +0 -1
- package/dist-standalone/_deps/xregistry/cjs/package.json +0 -1
- package/dist-standalone/cjs/package.json +0 -3
- package/dist-standalone/package.json +0 -10
|
@@ -1 +1,193 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @module approval
|
|
3
|
+
* OAuth-style approval flow for agents
|
|
4
|
+
*
|
|
5
|
+
* Enterprise agents require explicit user consent before performing
|
|
6
|
+
* sensitive operations. This module implements OAuth-style consent
|
|
7
|
+
* screens and approval tokens.
|
|
8
|
+
*/
|
|
9
|
+
import { ok, err } from"./_deps/shared/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Approval error codes
|
|
12
|
+
*/
|
|
13
|
+
export var ApprovalErrorCode;
|
|
14
|
+
(function (ApprovalErrorCode) {
|
|
15
|
+
ApprovalErrorCode["USER_DENIED"] = "APPROVAL_USER_DENIED";
|
|
16
|
+
ApprovalErrorCode["TIMEOUT"] = "APPROVAL_TIMEOUT";
|
|
17
|
+
ApprovalErrorCode["INVALID_DURATION"] = "APPROVAL_INVALID_DURATION";
|
|
18
|
+
ApprovalErrorCode["SIGNATURE_FAILED"] = "APPROVAL_SIGNATURE_FAILED";
|
|
19
|
+
ApprovalErrorCode["TOKEN_EXPIRED"] = "APPROVAL_TOKEN_EXPIRED";
|
|
20
|
+
ApprovalErrorCode["TOKEN_REVOKED"] = "APPROVAL_TOKEN_REVOKED";
|
|
21
|
+
ApprovalErrorCode["INVALID_TOKEN"] = "APPROVAL_INVALID_TOKEN";
|
|
22
|
+
})(ApprovalErrorCode || (ApprovalErrorCode = {}));
|
|
23
|
+
/**
|
|
24
|
+
* Approval error
|
|
25
|
+
*/
|
|
26
|
+
export class ApprovalError extends Error {
|
|
27
|
+
code;
|
|
28
|
+
details;
|
|
29
|
+
constructor(code, message, details) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.details = details;
|
|
33
|
+
this.name = 'ApprovalError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* CLI approval presenter (prints to console, reads stdin)
|
|
38
|
+
* This is a legitimate CLI interface - console output is intentional
|
|
39
|
+
*/
|
|
40
|
+
export class CLIApprovalPresenter {
|
|
41
|
+
/* eslint-disable no-console */
|
|
42
|
+
async present(options) {
|
|
43
|
+
console.log('\n=== AGENT APPROVAL REQUEST ===');
|
|
44
|
+
console.log(`Agent: ${options.agentDid}`);
|
|
45
|
+
console.log(`Duration: ${options.duration}`);
|
|
46
|
+
console.log('\nRequested Scopes:');
|
|
47
|
+
for (const scope of options.scopes) {
|
|
48
|
+
console.log(` - ${scope}`);
|
|
49
|
+
}
|
|
50
|
+
if (options.limits) {
|
|
51
|
+
console.log('\nPolicy Limits:');
|
|
52
|
+
if (options.limits.amountPerTxn) {
|
|
53
|
+
console.log(` - Max per transaction: $${options.limits.amountPerTxn.toLocaleString()}`);
|
|
54
|
+
}
|
|
55
|
+
if (options.limits.dailyAmount) {
|
|
56
|
+
console.log(` - Daily limit: $${options.limits.dailyAmount.toLocaleString()}`);
|
|
57
|
+
}
|
|
58
|
+
if (options.limits.callsPerMinute) {
|
|
59
|
+
console.log(` - Rate limit: ${options.limits.callsPerMinute} calls/minute`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (options.description) {
|
|
63
|
+
console.log(`\nDescription: ${options.description}`);
|
|
64
|
+
}
|
|
65
|
+
console.log('\n[This is a mock presenter - auto-approving for development]');
|
|
66
|
+
console.log('In production, this would prompt for user input.\n');
|
|
67
|
+
// Auto-approve for development (production would prompt for y/n)
|
|
68
|
+
return ok({
|
|
69
|
+
approved: true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Approval flow coordinator
|
|
75
|
+
*
|
|
76
|
+
* Orchestrates the consent flow: present to user → sign token → track expiry
|
|
77
|
+
*/
|
|
78
|
+
export class ApprovalFlow {
|
|
79
|
+
presenter;
|
|
80
|
+
tokens = new Map();
|
|
81
|
+
constructor(options = {}) {
|
|
82
|
+
this.presenter = options.presenter ?? new CLIApprovalPresenter();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Request approval from user
|
|
86
|
+
*
|
|
87
|
+
* @param options - Approval request options
|
|
88
|
+
* @returns Approval result with token (if approved)
|
|
89
|
+
*/
|
|
90
|
+
async requestApproval(options) {
|
|
91
|
+
// Validate duration
|
|
92
|
+
const durationMs = this.parseDuration(options.duration);
|
|
93
|
+
if (durationMs <= 0) {
|
|
94
|
+
return err(new ApprovalError(ApprovalErrorCode.INVALID_DURATION, `Invalid duration: ${options.duration}`));
|
|
95
|
+
}
|
|
96
|
+
// Present to user
|
|
97
|
+
const result = await this.presenter.present(options);
|
|
98
|
+
if (!result.ok)
|
|
99
|
+
return result;
|
|
100
|
+
if (!result.value.approved) {
|
|
101
|
+
return ok({
|
|
102
|
+
approved: false,
|
|
103
|
+
reason: result.value.reason ?? 'User denied approval',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// Generate approval token
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
const token = {
|
|
109
|
+
id: this.generateTokenId(),
|
|
110
|
+
agentDid: options.agentDid,
|
|
111
|
+
scopes: options.scopes,
|
|
112
|
+
limits: options.limits,
|
|
113
|
+
expiresAt: now + durationMs,
|
|
114
|
+
createdAt: now,
|
|
115
|
+
valid: true,
|
|
116
|
+
signature: await this.signToken(options.agentDid, options.scopes, now + durationMs),
|
|
117
|
+
};
|
|
118
|
+
// Store token
|
|
119
|
+
this.tokens.set(token.id, token);
|
|
120
|
+
return ok({
|
|
121
|
+
approved: true,
|
|
122
|
+
token,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Verify an approval token
|
|
127
|
+
*
|
|
128
|
+
* @param tokenId - Token ID to verify
|
|
129
|
+
* @returns Token if valid, error otherwise
|
|
130
|
+
*/
|
|
131
|
+
verifyToken(tokenId) {
|
|
132
|
+
const token = this.tokens.get(tokenId);
|
|
133
|
+
if (!token) {
|
|
134
|
+
return err(new ApprovalError(ApprovalErrorCode.INVALID_TOKEN, `Token ${tokenId} not found`));
|
|
135
|
+
}
|
|
136
|
+
if (!token.valid) {
|
|
137
|
+
return err(new ApprovalError(ApprovalErrorCode.TOKEN_REVOKED, `Token ${tokenId} has been revoked`));
|
|
138
|
+
}
|
|
139
|
+
if (Date.now() > token.expiresAt) {
|
|
140
|
+
return err(new ApprovalError(ApprovalErrorCode.TOKEN_EXPIRED, `Token ${tokenId} expired at ${new Date(token.expiresAt).toISOString()}`));
|
|
141
|
+
}
|
|
142
|
+
return ok(token);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Revoke an approval token
|
|
146
|
+
*
|
|
147
|
+
* @param tokenId - Token ID to revoke
|
|
148
|
+
*/
|
|
149
|
+
revokeToken(tokenId) {
|
|
150
|
+
const token = this.tokens.get(tokenId);
|
|
151
|
+
if (token) {
|
|
152
|
+
this.tokens.set(tokenId, { ...token, valid: false });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Parse duration string to milliseconds
|
|
157
|
+
*/
|
|
158
|
+
parseDuration(duration) {
|
|
159
|
+
if (typeof duration === 'number')
|
|
160
|
+
return duration;
|
|
161
|
+
// Parse ISO 8601 duration or simple format
|
|
162
|
+
const matches = duration.match(/^(\d+)([smhd])$/);
|
|
163
|
+
if (!matches)
|
|
164
|
+
return -1;
|
|
165
|
+
const value = parseInt(matches[1] ?? '0', 10);
|
|
166
|
+
const unit = matches[2];
|
|
167
|
+
switch (unit) {
|
|
168
|
+
case 's': return value * 1000;
|
|
169
|
+
case 'm': return value * 60 * 1000;
|
|
170
|
+
case 'h': return value * 60 * 60 * 1000;
|
|
171
|
+
case 'd': return value * 24 * 60 * 60 * 1000;
|
|
172
|
+
default: return -1;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Generate a unique token ID
|
|
177
|
+
*/
|
|
178
|
+
generateTokenId() {
|
|
179
|
+
const randomBytes = new Uint8Array(12);
|
|
180
|
+
crypto.getRandomValues(randomBytes);
|
|
181
|
+
const randomString = Array.from(randomBytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
182
|
+
return `appr_${Date.now()}_${randomString}`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Sign an approval token (stub - production would use Ed25519)
|
|
186
|
+
*/
|
|
187
|
+
async signToken(agentDid, scopes, expiresAt) {
|
|
188
|
+
// Production would use Ed25519 signature over JSON.stringify({ agentDid, scopes, expiresAt })
|
|
189
|
+
// For now, return a mock signature
|
|
190
|
+
const payload = JSON.stringify({ agentDid, scopes, expiresAt });
|
|
191
|
+
return btoa(payload).substring(0, 32);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -1 +1,382 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Async iterator API for xBind message streams.
|
|
3
|
+
*
|
|
4
|
+
* Provides modern async iteration patterns for consuming message streams
|
|
5
|
+
* with backpressure handling, stream cancellation, and error propagation.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Subscribe to messages with async iteration
|
|
10
|
+
* for await (const message of agent.subscribe({ from: 'did:key:alice' })) {
|
|
11
|
+
* console.log('Received:', message.payload);
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @module async-iterators
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Async iterable message stream.
|
|
19
|
+
*
|
|
20
|
+
* Implements AsyncIterableIterator for use with `for await...of` syntax.
|
|
21
|
+
* Handles backpressure, cancellation, and error propagation.
|
|
22
|
+
*/
|
|
23
|
+
export class MessageStream {
|
|
24
|
+
agent;
|
|
25
|
+
options;
|
|
26
|
+
state;
|
|
27
|
+
constructor(agent, options = {}) {
|
|
28
|
+
this.agent = agent;
|
|
29
|
+
this.options = {
|
|
30
|
+
from: options.from,
|
|
31
|
+
scope: options.scope,
|
|
32
|
+
bufferSize: options.bufferSize ?? 100,
|
|
33
|
+
signal: options.signal,
|
|
34
|
+
allowCleartext: options.allowCleartext ?? false,
|
|
35
|
+
};
|
|
36
|
+
this.state = {
|
|
37
|
+
buffer: [],
|
|
38
|
+
pending: [],
|
|
39
|
+
done: false,
|
|
40
|
+
};
|
|
41
|
+
// Setup abort signal handler
|
|
42
|
+
if (this.options.signal) {
|
|
43
|
+
this.options.signal.addEventListener('abort', () => {
|
|
44
|
+
this.close(new Error('Stream aborted'));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// Register envelope handler
|
|
48
|
+
const handler = (envelope) => {
|
|
49
|
+
void this.handleEnvelope(envelope);
|
|
50
|
+
};
|
|
51
|
+
// Get first transport adapter and register handler
|
|
52
|
+
const transports = this.agent.getTransports();
|
|
53
|
+
const transport = transports[0];
|
|
54
|
+
if (transport) {
|
|
55
|
+
transport.onReceive(handler);
|
|
56
|
+
this.state.cleanup = () => {
|
|
57
|
+
// Remove handler by creating new transport without this handler
|
|
58
|
+
// (Transport interface doesn't support handler removal)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Handle incoming envelope from transport.
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
async handleEnvelope(envelope) {
|
|
67
|
+
if (this.state.done)
|
|
68
|
+
return;
|
|
69
|
+
// Verify and decrypt the envelope
|
|
70
|
+
const result = await this.agent.receive(envelope, {
|
|
71
|
+
allowCleartext: this.options.allowCleartext,
|
|
72
|
+
});
|
|
73
|
+
if (!result.ok) {
|
|
74
|
+
// Ignore verification errors (not all envelopes are for this stream)
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const message = result.value;
|
|
78
|
+
// Apply filters
|
|
79
|
+
if (this.options.from && message.sender !== this.options.from) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (this.options.scope && message.scope !== this.options.scope) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Deliver message
|
|
86
|
+
if (this.state.pending.length > 0) {
|
|
87
|
+
// Consumer is waiting - deliver immediately
|
|
88
|
+
const resolve = this.state.pending.shift();
|
|
89
|
+
resolve({ value: message, done: false });
|
|
90
|
+
}
|
|
91
|
+
else if (this.state.buffer.length < this.options.bufferSize) {
|
|
92
|
+
// Buffer has space - enqueue
|
|
93
|
+
this.state.buffer.push(message);
|
|
94
|
+
}
|
|
95
|
+
// else: buffer full - drop message (backpressure)
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Close the stream with optional error.
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
close(error) {
|
|
102
|
+
if (this.state.done)
|
|
103
|
+
return;
|
|
104
|
+
this.state.done = true;
|
|
105
|
+
this.state.error = error;
|
|
106
|
+
// Resolve all pending next() calls
|
|
107
|
+
for (const resolve of this.state.pending) {
|
|
108
|
+
if (error) {
|
|
109
|
+
resolve({ value: undefined, done: true });
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
resolve({ value: undefined, done: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
this.state.pending = [];
|
|
116
|
+
// Cleanup resources
|
|
117
|
+
if (this.state.cleanup) {
|
|
118
|
+
this.state.cleanup();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get next message from the stream.
|
|
123
|
+
*
|
|
124
|
+
* Returns a promise that resolves when:
|
|
125
|
+
* - A message is available in the buffer
|
|
126
|
+
* - A new message arrives from the transport
|
|
127
|
+
* - The stream is closed
|
|
128
|
+
*
|
|
129
|
+
* @returns Promise resolving to iterator result
|
|
130
|
+
*/
|
|
131
|
+
async next() {
|
|
132
|
+
// Stream already done
|
|
133
|
+
if (this.state.done) {
|
|
134
|
+
return { value: undefined, done: true };
|
|
135
|
+
}
|
|
136
|
+
// Check abort signal
|
|
137
|
+
if (this.options.signal?.aborted) {
|
|
138
|
+
this.close(new Error('Stream aborted'));
|
|
139
|
+
return { value: undefined, done: true };
|
|
140
|
+
}
|
|
141
|
+
// Message available in buffer
|
|
142
|
+
if (this.state.buffer.length > 0) {
|
|
143
|
+
const message = this.state.buffer.shift();
|
|
144
|
+
return { value: message, done: false };
|
|
145
|
+
}
|
|
146
|
+
// Wait for next message
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
this.state.pending.push(resolve);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Return method for iterator protocol.
|
|
153
|
+
* Closes the stream and completes iteration.
|
|
154
|
+
*
|
|
155
|
+
* @param value - Optional value (ignored)
|
|
156
|
+
* @returns Promise resolving to done result
|
|
157
|
+
*/
|
|
158
|
+
async return(value) {
|
|
159
|
+
this.close();
|
|
160
|
+
return { value: undefined, done: true };
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Throw method for iterator protocol.
|
|
164
|
+
* Closes the stream with error.
|
|
165
|
+
*
|
|
166
|
+
* @param error - Error to propagate
|
|
167
|
+
* @returns Promise resolving to done result
|
|
168
|
+
*/
|
|
169
|
+
async throw(error) {
|
|
170
|
+
this.close(error instanceof Error ? error : new Error(String(error)));
|
|
171
|
+
return { value: undefined, done: true };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Make this object async iterable.
|
|
175
|
+
* Enables `for await...of` syntax.
|
|
176
|
+
*
|
|
177
|
+
* @returns This stream instance
|
|
178
|
+
*/
|
|
179
|
+
[Symbol.asyncIterator]() {
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get number of buffered messages.
|
|
184
|
+
* Useful for monitoring backpressure.
|
|
185
|
+
*/
|
|
186
|
+
get bufferedCount() {
|
|
187
|
+
return this.state.buffer.length;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Check if stream is closed.
|
|
191
|
+
*/
|
|
192
|
+
get closed() {
|
|
193
|
+
return this.state.done;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get stream error if any.
|
|
197
|
+
*/
|
|
198
|
+
get error() {
|
|
199
|
+
return this.state.error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Collect messages from a stream into an array.
|
|
204
|
+
*
|
|
205
|
+
* Useful for batch processing or testing. Stream completes when:
|
|
206
|
+
* - Limit is reached
|
|
207
|
+
* - Timeout expires
|
|
208
|
+
* - Stream is aborted via signal
|
|
209
|
+
*
|
|
210
|
+
* @param agent - Agent instance to collect messages from
|
|
211
|
+
* @param options - Collection options
|
|
212
|
+
* @returns Promise resolving to array of messages
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```typescript
|
|
216
|
+
* // Collect up to 10 messages with 5 second timeout
|
|
217
|
+
* const messages = await collectMessages(agent, {
|
|
218
|
+
* from: 'did:key:alice',
|
|
219
|
+
* limit: 10,
|
|
220
|
+
* timeout: 5000
|
|
221
|
+
* });
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
export async function collectMessages(agent, options = {}) {
|
|
225
|
+
const messages = [];
|
|
226
|
+
const stream = new MessageStream(agent, options);
|
|
227
|
+
// Setup timeout if specified
|
|
228
|
+
let timeoutId;
|
|
229
|
+
if (options.timeout) {
|
|
230
|
+
timeoutId = setTimeout(() => {
|
|
231
|
+
void stream.return();
|
|
232
|
+
}, options.timeout);
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
for await (const message of stream) {
|
|
236
|
+
messages.push(message);
|
|
237
|
+
if (options.limit && messages.length >= options.limit) {
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
if (timeoutId) {
|
|
244
|
+
clearTimeout(timeoutId);
|
|
245
|
+
}
|
|
246
|
+
if (!stream.closed) {
|
|
247
|
+
await stream.return();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return messages;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Transform message stream with async mapping function.
|
|
254
|
+
*
|
|
255
|
+
* Enables functional-style stream processing with backpressure preservation.
|
|
256
|
+
*
|
|
257
|
+
* @param stream - Source message stream
|
|
258
|
+
* @param fn - Async transform function
|
|
259
|
+
* @returns Async iterable of transformed values
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```typescript
|
|
263
|
+
* const stream = agent.subscribe({ from: 'did:key:alice' });
|
|
264
|
+
* const payloads = mapStream(stream, async (msg) => msg.payload);
|
|
265
|
+
*
|
|
266
|
+
* for await (const payload of payloads) {
|
|
267
|
+
* console.log('Payload:', payload);
|
|
268
|
+
* }
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
export async function* mapStream(stream, fn) {
|
|
272
|
+
for await (const value of stream) {
|
|
273
|
+
yield await fn(value);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Filter message stream with async predicate.
|
|
278
|
+
*
|
|
279
|
+
* Only yields messages that satisfy the predicate.
|
|
280
|
+
*
|
|
281
|
+
* @param stream - Source message stream
|
|
282
|
+
* @param predicate - Async filter predicate
|
|
283
|
+
* @returns Async iterable of filtered messages
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```typescript
|
|
287
|
+
* const stream = agent.subscribe({ scope: 'notifications' });
|
|
288
|
+
* const urgent = filterStream(stream, async (msg) =>
|
|
289
|
+
* msg.payload.priority === 'high'
|
|
290
|
+
* );
|
|
291
|
+
*
|
|
292
|
+
* for await (const message of urgent) {
|
|
293
|
+
* await handleUrgent(message);
|
|
294
|
+
* }
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
export async function* filterStream(stream, predicate) {
|
|
298
|
+
for await (const value of stream) {
|
|
299
|
+
if (await predicate(value)) {
|
|
300
|
+
yield value;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Take first N messages from stream.
|
|
306
|
+
*
|
|
307
|
+
* Stream completes after yielding N messages.
|
|
308
|
+
*
|
|
309
|
+
* @param stream - Source message stream
|
|
310
|
+
* @param count - Maximum messages to take
|
|
311
|
+
* @returns Async iterable with at most count messages
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```typescript
|
|
315
|
+
* const stream = agent.subscribe();
|
|
316
|
+
* const first5 = takeStream(stream, 5);
|
|
317
|
+
*
|
|
318
|
+
* for await (const message of first5) {
|
|
319
|
+
* console.log('Message:', message);
|
|
320
|
+
* }
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
export async function* takeStream(stream, count) {
|
|
324
|
+
let taken = 0;
|
|
325
|
+
for await (const value of stream) {
|
|
326
|
+
if (taken >= count)
|
|
327
|
+
break;
|
|
328
|
+
yield value;
|
|
329
|
+
taken++;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Merge multiple message streams into one.
|
|
334
|
+
*
|
|
335
|
+
* Yields messages from all streams as they arrive.
|
|
336
|
+
* Completes when all source streams complete.
|
|
337
|
+
*
|
|
338
|
+
* @param streams - Array of source streams
|
|
339
|
+
* @returns Async iterable of merged messages
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* const stream1 = agent.subscribe({ from: 'did:key:alice' });
|
|
344
|
+
* const stream2 = agent.subscribe({ from: 'did:key:bob' });
|
|
345
|
+
* const merged = mergeStreams([stream1, stream2]);
|
|
346
|
+
*
|
|
347
|
+
* for await (const message of merged) {
|
|
348
|
+
* console.log('From:', message.sender);
|
|
349
|
+
* }
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
export async function* mergeStreams(streams) {
|
|
353
|
+
const iterators = streams.map((s) => s[Symbol.asyncIterator]());
|
|
354
|
+
const pending = new Map();
|
|
355
|
+
// Start all iterators
|
|
356
|
+
for (let i = 0; i < iterators.length; i++) {
|
|
357
|
+
pending.set(i, iterators[i].next());
|
|
358
|
+
}
|
|
359
|
+
while (pending.size > 0) {
|
|
360
|
+
// Race all pending next() calls
|
|
361
|
+
const entries = Array.from(pending.entries());
|
|
362
|
+
const winner = await Promise.race(entries.map(async ([idx, promise]) => ({ idx, result: await promise })));
|
|
363
|
+
pending.delete(winner.idx);
|
|
364
|
+
if (!winner.result.done) {
|
|
365
|
+
yield winner.result.value;
|
|
366
|
+
// Start next iteration for this stream
|
|
367
|
+
pending.set(winner.idx, iterators[winner.idx].next());
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Add subscribe method to Agent prototype.
|
|
373
|
+
* @internal
|
|
374
|
+
*/
|
|
375
|
+
export function installAsyncIterators(AgentClass) {
|
|
376
|
+
if ('subscribe' in AgentClass.prototype) {
|
|
377
|
+
return; // Already installed
|
|
378
|
+
}
|
|
379
|
+
AgentClass.prototype.subscribe = function (options) {
|
|
380
|
+
return new MessageStream(this, options);
|
|
381
|
+
};
|
|
382
|
+
}
|