web-agent-bridge 3.4.0 → 3.9.0
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/LICENSE +84 -84
- package/README.ar.md +1565 -1304
- package/README.md +171 -298
- package/bin/agent-runner.js +474 -474
- package/bin/cli.js +237 -237
- package/bin/wab-init.js +244 -223
- package/bin/wab.js +80 -80
- package/examples/azure-dns-wab.js +83 -83
- package/examples/bidi-agent.js +119 -119
- package/examples/cloudflare-wab-dns.js +121 -121
- package/examples/cpanel-wab-dns.js +114 -114
- package/examples/cross-site-agent.js +91 -91
- package/examples/dns-discovery-agent.js +166 -166
- package/examples/gcp-dns-wab.js +76 -76
- package/examples/governance-agent.js +169 -169
- package/examples/mcp-agent.js +94 -94
- package/examples/next-app-router/README.md +44 -44
- package/examples/plesk-wab-dns.js +103 -103
- package/examples/puppeteer-agent.js +108 -108
- package/examples/route53-wab-dns.js +144 -144
- package/examples/saas-dashboard/README.md +55 -55
- package/examples/safe-mode-agent.js +96 -96
- package/examples/self-discovery.js +106 -0
- package/examples/shopify-hydrogen/README.md +74 -74
- package/examples/vision-agent.js +171 -171
- package/examples/wab-sign.js +74 -74
- package/examples/wab-verify.js +60 -60
- package/examples/wordpress-elementor/README.md +77 -77
- package/package.json +93 -93
- package/public/.well-known/agent-tools.json +180 -180
- package/public/.well-known/ai-assets.json +59 -59
- package/public/.well-known/security.txt +8 -8
- package/public/.well-known/wab.json +28 -28
- package/public/activate.html +448 -368
- package/public/adopt.html +236 -0
- package/public/adoption-metrics.html +188 -188
- package/public/agent-workspace.html +359 -349
- package/public/ai.html +198 -198
- package/public/api.html +397 -413
- package/public/atp.html +171 -0
- package/public/azure-dns-integration.html +289 -289
- package/public/browser.html +486 -486
- package/public/cloudflare-integration.html +380 -380
- package/public/commander-dashboard.html +243 -243
- package/public/cookies.html +210 -210
- package/public/cpanel-integration.html +398 -398
- package/public/css/agent-workspace.css +1713 -1713
- package/public/css/premium.css +317 -317
- package/public/css/styles.css +1401 -1263
- package/public/dashboard-shieldlink.html +295 -0
- package/public/dashboard.html +711 -707
- package/public/dns.html +436 -436
- package/public/docs.html +588 -588
- package/public/enterprise-mesh.ar.html +80 -0
- package/public/enterprise-mesh.html +81 -0
- package/public/feed.xml +89 -89
- package/public/gcp-dns-integration.html +318 -318
- package/public/governance.ar.html +70 -0
- package/public/governance.html +69 -0
- package/public/growth.html +465 -465
- package/public/index.html +1372 -1266
- package/public/integrations.html +556 -556
- package/public/js/activate.js +449 -145
- package/public/js/agent-workspace.js +1740 -1740
- package/public/js/auth-nav.js +117 -65
- package/public/js/auth-redirect.js +12 -12
- package/public/js/cookie-consent.js +56 -56
- package/public/js/dns.js +438 -438
- package/public/js/wab-demo-page.js +721 -721
- package/public/js/ws-client.js +74 -74
- package/public/l-preview.html +242 -0
- package/public/llms-full.txt +360 -360
- package/public/llms.txt +125 -125
- package/public/login.html +85 -85
- package/public/mesh-dashboard.html +328 -328
- package/public/milestones.html +346 -0
- package/public/one-click.html +779 -0
- package/public/openapi.json +669 -669
- package/public/partners.ar.html +145 -0
- package/public/partners.html +143 -0
- package/public/phone-shield.html +281 -281
- package/public/plesk-integration.html +375 -375
- package/public/premium-dashboard.html +2489 -2489
- package/public/premium.html +793 -793
- package/public/privacy.html +297 -297
- package/public/provider-onboarding.html +172 -172
- package/public/provider-sandbox.html +134 -134
- package/public/providers.html +359 -359
- package/public/refusals.html +172 -0
- package/public/register.html +105 -105
- package/public/registrar-integrations.html +141 -141
- package/public/ring4.html +292 -0
- package/public/robots.txt +99 -99
- package/public/route53-integration.html +531 -531
- package/public/score.html +263 -0
- package/public/script/wab-consent.d.ts +36 -36
- package/public/script/wab-consent.js +104 -104
- package/public/script/wab-schema.js +131 -131
- package/public/script/wab.d.ts +108 -108
- package/public/script/wab.min.js +580 -580
- package/public/security.txt +8 -8
- package/public/shieldlink.html +244 -0
- package/public/shieldqr.html +231 -231
- package/public/sitemap.xml +13 -1
- package/public/terms.html +256 -256
- package/public/trust-graph-api.ar.html +92 -0
- package/public/trust-graph-api.html +91 -0
- package/public/wab-features.html +560 -0
- package/public/wab-trust.html +200 -200
- package/public/wab-truth.html +375 -0
- package/public/wab-vs-protocols.html +210 -210
- package/public/whitepaper.html +449 -449
- package/script/ai-agent-bridge.js +1754 -1754
- package/sdk/README.md +99 -99
- package/sdk/agent-mesh.js +449 -449
- package/sdk/atp.js +103 -0
- package/sdk/auto-discovery.js +301 -288
- package/sdk/commander.js +262 -262
- package/sdk/governance.js +262 -262
- package/sdk/index.d.ts +464 -464
- package/sdk/index.js +653 -649
- package/sdk/multi-agent.js +318 -318
- package/sdk/safe-mode.js +221 -221
- package/sdk/safety-shield.js +219 -219
- package/sdk/schema-discovery.js +83 -83
- package/server/adapters/index.js +520 -520
- package/server/config/plans.js +412 -367
- package/server/config/secrets.js +102 -102
- package/server/control-plane/index.js +301 -301
- package/server/data-plane/index.js +354 -354
- package/server/index.js +793 -670
- package/server/llm/index.js +404 -404
- package/server/middleware/adminAuth.js +35 -35
- package/server/middleware/api-tier.js +170 -0
- package/server/middleware/auth.js +50 -50
- package/server/middleware/featureGate.js +88 -88
- package/server/middleware/rateLimits.js +100 -100
- package/server/middleware/sensitiveAction.js +157 -157
- package/server/middleware/wab-trust.js +141 -0
- package/server/migrations/001_add_analytics_indexes.sql +7 -7
- package/server/migrations/002_premium_features.sql +418 -418
- package/server/migrations/003_ads_integer_cents.sql +33 -33
- package/server/migrations/004_agent_os.sql +158 -158
- package/server/migrations/005_marketplace_metering.sql +126 -126
- package/server/migrations/006_growth_suite.sql +138 -0
- package/server/migrations/007_governance.sql +106 -106
- package/server/migrations/008_plans.sql +144 -144
- package/server/migrations/009_shieldqr.sql +30 -30
- package/server/migrations/010_extended_trust.sql +33 -33
- package/server/migrations/011_outreach.sql +47 -0
- package/server/migrations/012_shieldlink.sql +116 -0
- package/server/migrations/013_ct_monitor.sql +13 -0
- package/server/migrations/014_wab_advanced_features.sql +128 -0
- package/server/migrations/015_wab_truth_layer.sql +101 -0
- package/server/migrations/016_ring4_external_trust.sql +84 -0
- package/server/migrations/017_ring4_extensions.sql +69 -0
- package/server/migrations/018_commercial_foundations.sql +167 -0
- package/server/migrations/019_unify_tier_constraints.sql +133 -0
- package/server/migrations/020_agent_transaction_primitive.sql +119 -0
- package/server/models/adapters/index.js +33 -33
- package/server/models/adapters/mysql.js +183 -183
- package/server/models/adapters/postgresql.js +172 -172
- package/server/models/adapters/sqlite.js +7 -7
- package/server/models/db.js +740 -740
- package/server/observability/failure-analysis.js +337 -337
- package/server/observability/index.js +394 -394
- package/server/protocol/capabilities.js +223 -223
- package/server/protocol/index.js +243 -243
- package/server/protocol/schema.js +584 -584
- package/server/registry/certification.js +271 -271
- package/server/registry/index.js +326 -326
- package/server/routes/activate.js +478 -0
- package/server/routes/admin-outreach.js +239 -0
- package/server/routes/admin-plans.js +76 -76
- package/server/routes/admin-premium.js +674 -673
- package/server/routes/admin-shieldlink.js +137 -0
- package/server/routes/admin-shieldqr.js +90 -90
- package/server/routes/admin-trust-monitor.js +139 -83
- package/server/routes/admin.js +550 -549
- package/server/routes/adopt.js +61 -0
- package/server/routes/ads.js +130 -130
- package/server/routes/agent-workspace.js +540 -540
- package/server/routes/api-keys.js +127 -0
- package/server/routes/api.js +150 -150
- package/server/routes/auth.js +71 -71
- package/server/routes/billing.js +57 -57
- package/server/routes/commander.js +316 -316
- package/server/routes/customer-shieldlink.js +133 -0
- package/server/routes/demo-showcase.js +332 -332
- package/server/routes/demo-store.js +154 -154
- package/server/routes/diagnose.js +373 -0
- package/server/routes/discovery.js +2348 -2348
- package/server/routes/enterprise-mesh.js +170 -0
- package/server/routes/gateway.js +173 -173
- package/server/routes/governance-saas.js +203 -0
- package/server/routes/governance.js +208 -208
- package/server/routes/growth.js +1048 -0
- package/server/routes/intent.js +328 -0
- package/server/routes/license.js +251 -251
- package/server/routes/mesh.js +469 -469
- package/server/routes/noscript.js +543 -543
- package/server/routes/partners.js +201 -0
- package/server/routes/plans.js +33 -33
- package/server/routes/premium-v2.js +686 -686
- package/server/routes/premium.js +724 -724
- package/server/routes/providers.js +650 -650
- package/server/routes/reputation.js +411 -0
- package/server/routes/ring4.js +885 -0
- package/server/routes/runtime.js +2148 -2148
- package/server/routes/shieldlink.js +70 -0
- package/server/routes/shieldqr.js +88 -88
- package/server/routes/sovereign.js +465 -465
- package/server/routes/transactions.js +233 -0
- package/server/routes/truth-layer.js +670 -0
- package/server/routes/universal.js +200 -200
- package/server/routes/unsubscribe.js +51 -0
- package/server/routes/wab-api.js +850 -850
- package/server/routes/wab-cache.js +282 -0
- package/server/runtime/container-worker.js +111 -111
- package/server/runtime/container.js +448 -448
- package/server/runtime/distributed-worker.js +362 -362
- package/server/runtime/event-bus.js +210 -210
- package/server/runtime/index.js +253 -253
- package/server/runtime/queue.js +599 -599
- package/server/runtime/replay.js +666 -666
- package/server/runtime/sandbox.js +266 -266
- package/server/runtime/scheduler.js +534 -534
- package/server/runtime/session-engine.js +293 -293
- package/server/runtime/state-manager.js +188 -188
- package/server/secrets/wab-signing-key.pem +3 -0
- package/server/secrets/wab-signing-pub.pem +3 -0
- package/server/security/cross-site-redactor.js +196 -196
- package/server/security/dry-run.js +180 -180
- package/server/security/human-gate-rate-limit.js +147 -147
- package/server/security/human-gate-transports.js +178 -178
- package/server/security/human-gate.js +281 -281
- package/server/security/index.js +368 -368
- package/server/security/intent-engine.js +245 -245
- package/server/security/reward-guard.js +171 -171
- package/server/security/rollback-store.js +239 -239
- package/server/security/token-scope.js +404 -404
- package/server/security/url-policy.js +139 -139
- package/server/services/adoption-agent.js +182 -0
- package/server/services/agent-chat.js +506 -506
- package/server/services/agent-learning.js +601 -601
- package/server/services/agent-memory.js +625 -625
- package/server/services/agent-mesh.js +555 -555
- package/server/services/agent-symphony.js +717 -717
- package/server/services/agent-tasks.js +1807 -1807
- package/server/services/api-key-engine.js +292 -292
- package/server/services/cluster.js +894 -894
- package/server/services/commander.js +738 -738
- package/server/services/edge-compute.js +440 -440
- package/server/services/email.js +233 -233
- package/server/services/fairness-engine.js +409 -0
- package/server/services/fairness.js +420 -0
- package/server/services/governance.js +466 -466
- package/server/services/hosted-runtime.js +205 -205
- package/server/services/lfd.js +635 -635
- package/server/services/local-ai.js +389 -389
- package/server/services/marketplace.js +270 -270
- package/server/services/metering.js +182 -182
- package/server/services/modules/affiliate-intelligence.js +93 -93
- package/server/services/modules/agent-firewall.js +90 -90
- package/server/services/modules/bounty.js +89 -89
- package/server/services/modules/collective-bargaining.js +92 -92
- package/server/services/modules/dark-pattern.js +66 -66
- package/server/services/modules/gov-intelligence.js +45 -45
- package/server/services/modules/neural.js +55 -55
- package/server/services/modules/notary.js +49 -49
- package/server/services/modules/price-time-machine.js +86 -86
- package/server/services/modules/protocol.js +104 -104
- package/server/services/negotiation.js +439 -439
- package/server/services/outreach-agent.js +312 -0
- package/server/services/plans.js +214 -214
- package/server/services/plugins.js +771 -771
- package/server/services/price-intelligence.js +566 -566
- package/server/services/price-shield.js +1137 -1137
- package/server/services/provider-clients.js +740 -740
- package/server/services/reputation.js +465 -465
- package/server/services/search-engine.js +357 -357
- package/server/services/security.js +513 -513
- package/server/services/self-healing.js +843 -843
- package/server/services/shieldlink.js +492 -0
- package/server/services/shieldqr.js +322 -322
- package/server/services/sovereign-shield.js +542 -542
- package/server/services/ssl-ct-monitor.js +224 -0
- package/server/services/ssl-inspector.js +42 -42
- package/server/services/ssl-monitor.js +167 -167
- package/server/services/stripe.js +206 -205
- package/server/services/swarm.js +788 -788
- package/server/services/transactions.js +525 -0
- package/server/services/universal-scraper.js +662 -662
- package/server/services/verification.js +481 -481
- package/server/services/vision.js +1163 -1163
- package/server/services/wab-crypto.js +178 -178
- package/server/utils/cache.js +125 -125
- package/server/utils/migrate.js +81 -81
- package/server/utils/safe-fetch.js +228 -228
- package/server/utils/secureFields.js +50 -50
- package/server/ws.js +161 -161
- package/templates/artisan-marketplace.yaml +104 -104
- package/templates/book-price-scout.yaml +98 -98
- package/templates/electronics-price-tracker.yaml +108 -108
- package/templates/flight-deal-hunter.yaml +113 -113
- package/templates/freelancer-direct.yaml +116 -116
- package/templates/grocery-price-compare.yaml +93 -93
- package/templates/hotel-direct-booking.yaml +113 -113
- package/templates/local-services.yaml +98 -98
- package/templates/olive-oil-tunisia.yaml +88 -88
- package/templates/organic-farm-fresh.yaml +101 -101
- package/templates/restaurant-direct.yaml +97 -97
- package/templates/ring4/banking-sovereign.yaml +55 -0
- package/templates/ring4/ecommerce-sovereign.yaml +58 -0
- package/templates/ring4/healthcare-sovereign.yaml +60 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* server/routes/wab-cache.js
|
|
3
|
+
* WAB Memory Cache Layer + Offline Mode + Sync
|
|
4
|
+
*
|
|
5
|
+
* Mounted at /api/cache and /api/offline
|
|
6
|
+
*
|
|
7
|
+
* Memory Cache Layer:
|
|
8
|
+
* GET /api/cache/manifest/:domain — Versioned manifest fetch with ETag
|
|
9
|
+
* POST /api/cache/validate — Batch-validate cached domains
|
|
10
|
+
* GET /api/cache/status/:domain — Single-domain freshness check
|
|
11
|
+
* POST /api/cache/store — Agent pushes a manifest to cache registry
|
|
12
|
+
*
|
|
13
|
+
* Offline Mode + Sync:
|
|
14
|
+
* GET /api/offline/status/:domain — Check if cached version is stale
|
|
15
|
+
* POST /api/offline/sync — Bulk sync: send {domains:[...], cached_etags:{}}
|
|
16
|
+
* GET /api/offline/bundle — Download offline bundle (JSON) for given domains
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const express = require('express');
|
|
22
|
+
const crypto = require('crypto');
|
|
23
|
+
const cacheRouter = express.Router();
|
|
24
|
+
const offlineRouter = express.Router();
|
|
25
|
+
const { db } = require('../models/db');
|
|
26
|
+
const { safeFetch } = require('../utils/safe-fetch');
|
|
27
|
+
|
|
28
|
+
// ─── Schema bootstrap ─────────────────────────────────────────────────────────
|
|
29
|
+
db.exec(`
|
|
30
|
+
CREATE TABLE IF NOT EXISTS manifest_versions (
|
|
31
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT NOT NULL,
|
|
32
|
+
etag TEXT NOT NULL, manifest_json TEXT NOT NULL, content_hash TEXT NOT NULL,
|
|
33
|
+
key_id TEXT, issued_at TEXT, expires_at TEXT,
|
|
34
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
35
|
+
);
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_manifest_ver_domain ON manifest_versions(domain, created_at DESC);
|
|
37
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_manifest_ver_etag ON manifest_versions(domain, etag);
|
|
38
|
+
`);
|
|
39
|
+
|
|
40
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
41
|
+
function normDomain(d) {
|
|
42
|
+
return String(d || '').toLowerCase().trim()
|
|
43
|
+
.replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/^www\./, '');
|
|
44
|
+
}
|
|
45
|
+
function validDomain(d) {
|
|
46
|
+
return /^[a-z0-9.-]{3,253}$/.test(d) && d.includes('.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function etag(json) {
|
|
50
|
+
return crypto.createHash('sha256').update(json).digest('hex').slice(0, 16);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isFresh(row) {
|
|
54
|
+
if (!row) return false;
|
|
55
|
+
// Check expires_at from manifest
|
|
56
|
+
if (row.expires_at) {
|
|
57
|
+
return new Date(row.expires_at) > new Date();
|
|
58
|
+
}
|
|
59
|
+
// Default freshness: 24 hours from cache time
|
|
60
|
+
return (Date.now() - new Date(row.created_at).getTime()) < 24 * 3600 * 1000;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fetch wab.json from a live domain and store a versioned snapshot.
|
|
65
|
+
* Returns { row, fresh, fetched } or null on error.
|
|
66
|
+
*/
|
|
67
|
+
async function fetchAndStoreManifest(domain) {
|
|
68
|
+
const urls = [
|
|
69
|
+
`https://${domain}/.well-known/wab.json`,
|
|
70
|
+
`https://www.${domain}/.well-known/wab.json`,
|
|
71
|
+
];
|
|
72
|
+
for (const url of urls) {
|
|
73
|
+
try {
|
|
74
|
+
const resp = await safeFetch(url, { timeout: 8000 });
|
|
75
|
+
if (!resp.ok) continue;
|
|
76
|
+
const text = await resp.text();
|
|
77
|
+
let parsed;
|
|
78
|
+
try { parsed = JSON.parse(text); } catch (_) { continue; }
|
|
79
|
+
const json = JSON.stringify(parsed);
|
|
80
|
+
const hash = crypto.createHash('sha256').update(json).digest('hex');
|
|
81
|
+
const tag = hash.slice(0, 16);
|
|
82
|
+
|
|
83
|
+
const existing = db.prepare(`SELECT id FROM manifest_versions WHERE domain=? AND etag=?`).get(domain, tag);
|
|
84
|
+
if (!existing) {
|
|
85
|
+
db.prepare(`INSERT OR IGNORE INTO manifest_versions (domain, etag, manifest_json, content_hash, key_id, issued_at, expires_at) VALUES (?,?,?,?,?,?,?)`)
|
|
86
|
+
.run(domain, tag, json, hash, parsed.signature?.key_id || null, parsed.issued_at || null, parsed.expires_at || null);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const row = db.prepare(`SELECT * FROM manifest_versions WHERE domain=? ORDER BY created_at DESC LIMIT 1`).get(domain);
|
|
90
|
+
return { row, fresh: true, fetched: true };
|
|
91
|
+
} catch (_) {}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Cache endpoints ──────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
// GET /api/cache/manifest/:domain
|
|
99
|
+
cacheRouter.get('/manifest/:domain', async (req, res) => {
|
|
100
|
+
const domain = normDomain(req.params.domain);
|
|
101
|
+
if (!validDomain(domain)) return res.status(400).json({ error: 'invalid_domain' });
|
|
102
|
+
|
|
103
|
+
const force = req.query.force === '1';
|
|
104
|
+
let row = db.prepare(`SELECT * FROM manifest_versions WHERE domain = ? ORDER BY created_at DESC LIMIT 1`).get(domain);
|
|
105
|
+
|
|
106
|
+
if (!row || !isFresh(row) || force) {
|
|
107
|
+
const fetched = await fetchAndStoreManifest(domain);
|
|
108
|
+
if (fetched) row = fetched.row;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!row) return res.status(404).json({ error: 'manifest_not_found', domain, hint: 'Domain has no registered WAB manifest.' });
|
|
112
|
+
|
|
113
|
+
// ETag + conditional GET support
|
|
114
|
+
const clientEtag = req.headers['if-none-match'];
|
|
115
|
+
if (clientEtag && clientEtag === `"${row.etag}"`) {
|
|
116
|
+
return res.status(304).end();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
res.setHeader('ETag', `"${row.etag}"`);
|
|
120
|
+
res.setHeader('Cache-Control', 'public, max-age=300');
|
|
121
|
+
res.setHeader('X-WAB-Cache-Version', row.id);
|
|
122
|
+
res.setHeader('X-WAB-Cached-At', row.created_at);
|
|
123
|
+
|
|
124
|
+
let manifest;
|
|
125
|
+
try { manifest = JSON.parse(row.manifest_json); } catch (_) { return res.status(500).json({ error: 'parse_error' }); }
|
|
126
|
+
|
|
127
|
+
return res.json({
|
|
128
|
+
domain, etag: row.etag, content_hash: row.content_hash,
|
|
129
|
+
cached_at: row.created_at, expires_at: row.expires_at,
|
|
130
|
+
fresh: isFresh(row), manifest,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// POST /api/cache/validate — batch ETag validation
|
|
135
|
+
cacheRouter.post('/validate', express.json({ limit: '16kb' }), async (req, res) => {
|
|
136
|
+
const { domains } = req.body || {};
|
|
137
|
+
if (!Array.isArray(domains) || domains.length === 0)
|
|
138
|
+
return res.status(400).json({ error: 'domains must be a non-empty array' });
|
|
139
|
+
if (domains.length > 50) return res.status(400).json({ error: 'max 50 domains per request' });
|
|
140
|
+
|
|
141
|
+
const results = {};
|
|
142
|
+
for (const entry of domains) {
|
|
143
|
+
const domain = normDomain(typeof entry === 'string' ? entry : entry.domain);
|
|
144
|
+
const cachedEtag = typeof entry === 'object' ? entry.etag : null;
|
|
145
|
+
if (!validDomain(domain)) { results[domain] = { valid: false, error: 'invalid_domain' }; continue; }
|
|
146
|
+
|
|
147
|
+
const row = db.prepare(`SELECT etag, created_at, expires_at, content_hash FROM manifest_versions WHERE domain=? ORDER BY created_at DESC LIMIT 1`).get(domain);
|
|
148
|
+
if (!row) {
|
|
149
|
+
results[domain] = { valid: false, reason: 'not_cached', fresh: false };
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const fresh = isFresh(row);
|
|
153
|
+
const match = cachedEtag ? (cachedEtag === row.etag) : true;
|
|
154
|
+
results[domain] = { valid: true, fresh, etag_match: match, etag: row.etag, cached_at: row.created_at, expires_at: row.expires_at };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return res.json({ results, validated_at: new Date().toISOString() });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// GET /api/cache/status/:domain
|
|
161
|
+
cacheRouter.get('/status/:domain', (req, res) => {
|
|
162
|
+
const domain = normDomain(req.params.domain);
|
|
163
|
+
if (!validDomain(domain)) return res.status(400).json({ error: 'invalid_domain' });
|
|
164
|
+
const row = db.prepare(`SELECT etag, content_hash, created_at, expires_at FROM manifest_versions WHERE domain=? ORDER BY created_at DESC LIMIT 1`).get(domain);
|
|
165
|
+
if (!row) return res.json({ domain, cached: false, fresh: false });
|
|
166
|
+
|
|
167
|
+
const fresh = isFresh(row);
|
|
168
|
+
const ageMs = Date.now() - new Date(row.created_at).getTime();
|
|
169
|
+
return res.json({ domain, cached: true, fresh, etag: row.etag, content_hash: row.content_hash, cached_at: row.created_at, expires_at: row.expires_at, age_seconds: Math.floor(ageMs / 1000) });
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// POST /api/cache/store — Agent pushes a manifest they fetched
|
|
173
|
+
cacheRouter.post('/store', express.json({ limit: '64kb' }), (req, res) => {
|
|
174
|
+
const { domain, manifest } = req.body || {};
|
|
175
|
+
if (!domain || !manifest || typeof manifest !== 'object')
|
|
176
|
+
return res.status(400).json({ error: 'missing fields: domain, manifest' });
|
|
177
|
+
const d = normDomain(domain);
|
|
178
|
+
if (!validDomain(d)) return res.status(400).json({ error: 'invalid_domain' });
|
|
179
|
+
|
|
180
|
+
const json = JSON.stringify(manifest);
|
|
181
|
+
const hash = crypto.createHash('sha256').update(json).digest('hex');
|
|
182
|
+
const tag = hash.slice(0, 16);
|
|
183
|
+
|
|
184
|
+
db.prepare(`INSERT OR IGNORE INTO manifest_versions (domain, etag, manifest_json, content_hash, key_id, issued_at, expires_at) VALUES (?,?,?,?,?,?,?)`)
|
|
185
|
+
.run(d, tag, json, hash, manifest.signature?.key_id || null, manifest.issued_at || null, manifest.expires_at || null);
|
|
186
|
+
|
|
187
|
+
return res.status(201).json({ ok: true, domain: d, etag: tag, content_hash: hash });
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// ─── Offline Mode + Sync ──────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
// GET /api/offline/status/:domain
|
|
193
|
+
offlineRouter.get('/status/:domain', (req, res) => {
|
|
194
|
+
const domain = normDomain(req.params.domain);
|
|
195
|
+
if (!validDomain(domain)) return res.status(400).json({ error: 'invalid_domain' });
|
|
196
|
+
const row = db.prepare(`SELECT etag, created_at, expires_at FROM manifest_versions WHERE domain=? ORDER BY created_at DESC LIMIT 1`).get(domain);
|
|
197
|
+
if (!row) return res.json({ domain, offline_ready: false, reason: 'no_cached_manifest' });
|
|
198
|
+
|
|
199
|
+
const fresh = isFresh(row);
|
|
200
|
+
const expiresIn = row.expires_at ? Math.floor((new Date(row.expires_at) - Date.now()) / 1000) : null;
|
|
201
|
+
return res.json({
|
|
202
|
+
domain, offline_ready: true, fresh, etag: row.etag,
|
|
203
|
+
cached_at: row.created_at, expires_at: row.expires_at,
|
|
204
|
+
expires_in_seconds: expiresIn,
|
|
205
|
+
should_sync: !fresh,
|
|
206
|
+
checked_at: new Date().toISOString(),
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// POST /api/offline/sync — bulk sync check
|
|
211
|
+
offlineRouter.post('/sync', express.json({ limit: '16kb' }), async (req, res) => {
|
|
212
|
+
const { domains = [], cached_etags = {} } = req.body || {};
|
|
213
|
+
if (!Array.isArray(domains) || domains.length === 0)
|
|
214
|
+
return res.status(400).json({ error: 'domains must be a non-empty array' });
|
|
215
|
+
if (domains.length > 30) return res.status(400).json({ error: 'max 30 domains per sync' });
|
|
216
|
+
|
|
217
|
+
const updates = [];
|
|
218
|
+
const upToDate = [];
|
|
219
|
+
const notFound = [];
|
|
220
|
+
|
|
221
|
+
for (const rawDomain of domains) {
|
|
222
|
+
const domain = normDomain(rawDomain);
|
|
223
|
+
if (!validDomain(domain)) continue;
|
|
224
|
+
|
|
225
|
+
const row = db.prepare(`SELECT * FROM manifest_versions WHERE domain=? ORDER BY created_at DESC LIMIT 1`).get(domain);
|
|
226
|
+
if (!row) { notFound.push(domain); continue; }
|
|
227
|
+
|
|
228
|
+
const clientEtag = cached_etags[domain] || cached_etags[rawDomain];
|
|
229
|
+
if (clientEtag && clientEtag === row.etag && isFresh(row)) {
|
|
230
|
+
upToDate.push(domain);
|
|
231
|
+
} else {
|
|
232
|
+
// Need to update: return new manifest
|
|
233
|
+
let manifest;
|
|
234
|
+
try { manifest = JSON.parse(row.manifest_json); } catch (_) { manifest = null; }
|
|
235
|
+
updates.push({ domain, etag: row.etag, cached_at: row.created_at, expires_at: row.expires_at, manifest });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return res.json({
|
|
240
|
+
synced_at: new Date().toISOString(),
|
|
241
|
+
updates,
|
|
242
|
+
up_to_date: upToDate,
|
|
243
|
+
not_found: notFound,
|
|
244
|
+
summary: { updates: updates.length, up_to_date: upToDate.length, not_found: notFound.length },
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// GET /api/offline/bundle?domains=a.com,b.com — downloadable JSON bundle
|
|
249
|
+
offlineRouter.get('/bundle', async (req, res) => {
|
|
250
|
+
const raw = String(req.query.domains || '');
|
|
251
|
+
if (!raw) return res.status(400).json({ error: 'domains query param required' });
|
|
252
|
+
const domainList = raw.split(',').slice(0, 20).map(normDomain).filter(validDomain);
|
|
253
|
+
if (!domainList.length) return res.status(400).json({ error: 'no valid domains' });
|
|
254
|
+
|
|
255
|
+
const bundle = {
|
|
256
|
+
wab_bundle_version: '1.0',
|
|
257
|
+
generated_at: new Date().toISOString(),
|
|
258
|
+
domains: {},
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
for (const domain of domainList) {
|
|
262
|
+
let row = db.prepare(`SELECT * FROM manifest_versions WHERE domain=? ORDER BY created_at DESC LIMIT 1`).get(domain);
|
|
263
|
+
if (!row) {
|
|
264
|
+
// Try live fetch
|
|
265
|
+
const fetched = await fetchAndStoreManifest(domain);
|
|
266
|
+
if (fetched) row = fetched.row;
|
|
267
|
+
}
|
|
268
|
+
if (row) {
|
|
269
|
+
let manifest;
|
|
270
|
+
try { manifest = JSON.parse(row.manifest_json); } catch (_) { manifest = null; }
|
|
271
|
+
bundle.domains[domain] = { etag: row.etag, cached_at: row.created_at, expires_at: row.expires_at, fresh: isFresh(row), manifest };
|
|
272
|
+
} else {
|
|
273
|
+
bundle.domains[domain] = { error: 'not_found' };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
res.setHeader('Content-Disposition', 'attachment; filename="wab-offline-bundle.json"');
|
|
278
|
+
res.setHeader('Content-Type', 'application/json');
|
|
279
|
+
return res.json(bundle);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
module.exports = { cacheRouter, offlineRouter };
|
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* WAB Container Worker — Runs inside a forked child process
|
|
5
|
-
*
|
|
6
|
-
* This script is the entry point for process-isolated task execution.
|
|
7
|
-
* It reads the task definition from a JSON file, executes it,
|
|
8
|
-
* and sends results back via IPC.
|
|
9
|
-
*
|
|
10
|
-
* Security:
|
|
11
|
-
* - Runs in a separate process with memory limits (--max-old-space-size)
|
|
12
|
-
* - Limited filesystem access (only its tmp directory)
|
|
13
|
-
* - Can disable network via environment
|
|
14
|
-
* - Timeout enforced by parent
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
|
|
20
|
-
const taskFile = process.argv[2];
|
|
21
|
-
if (!taskFile) {
|
|
22
|
-
process.stderr.write('No task file specified\n');
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
let taskData;
|
|
27
|
-
try {
|
|
28
|
-
taskData = JSON.parse(fs.readFileSync(taskFile, 'utf8'));
|
|
29
|
-
} catch (err) {
|
|
30
|
-
process.stderr.write(`Failed to read task file: ${err.message}\n`);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ─── Sandbox Utilities (available to task code) ──────────────────────
|
|
35
|
-
|
|
36
|
-
const sandbox = {
|
|
37
|
-
taskId: taskData.taskId,
|
|
38
|
-
containerId: taskData.containerId,
|
|
39
|
-
params: taskData.params || {},
|
|
40
|
-
|
|
41
|
-
// Send progress updates
|
|
42
|
-
progress(pct) {
|
|
43
|
-
if (process.send) process.send({ type: 'progress', progress: pct });
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
// Send log messages
|
|
47
|
-
log(message) {
|
|
48
|
-
if (process.send) process.send({ type: 'log', message: String(message).slice(0, 1000) });
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
// Read a param
|
|
52
|
-
param(key, defaultValue) {
|
|
53
|
-
return taskData.params[key] !== undefined ? taskData.params[key] : defaultValue;
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
// Filesystem is restricted to tmpDir
|
|
57
|
-
tmpDir: path.dirname(taskFile),
|
|
58
|
-
|
|
59
|
-
readFile(name) {
|
|
60
|
-
const p = path.join(sandbox.tmpDir, path.basename(name));
|
|
61
|
-
return fs.readFileSync(p, 'utf8');
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
writeFile(name, content) {
|
|
65
|
-
const p = path.join(sandbox.tmpDir, path.basename(name));
|
|
66
|
-
fs.writeFileSync(p, content);
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// ─── Execute Task ────────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
async function execute() {
|
|
73
|
-
try {
|
|
74
|
-
let result;
|
|
75
|
-
|
|
76
|
-
if (taskData.code) {
|
|
77
|
-
// Execute provided code string in a restricted scope
|
|
78
|
-
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
79
|
-
const fn = new AsyncFunction('sandbox', 'params', taskData.code);
|
|
80
|
-
result = await fn(sandbox, taskData.params);
|
|
81
|
-
} else if (taskData.module) {
|
|
82
|
-
// Execute a module (for trusted internal tasks)
|
|
83
|
-
const mod = require(taskData.module);
|
|
84
|
-
if (typeof mod.execute === 'function') {
|
|
85
|
-
result = await mod.execute(taskData.params, sandbox);
|
|
86
|
-
} else {
|
|
87
|
-
result = { error: 'Module has no execute() function' };
|
|
88
|
-
}
|
|
89
|
-
} else {
|
|
90
|
-
result = { echo: taskData.params, message: 'No code or module specified' };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Send result back via IPC
|
|
94
|
-
if (process.send) {
|
|
95
|
-
process.send({ type: 'result', data: result });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Give IPC time to flush
|
|
99
|
-
setTimeout(() => process.exit(0), 100);
|
|
100
|
-
} catch (err) {
|
|
101
|
-
process.stderr.write(`Task error: ${err.message}\n${err.stack}\n`);
|
|
102
|
-
|
|
103
|
-
if (process.send) {
|
|
104
|
-
process.send({ type: 'result', data: null });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
setTimeout(() => process.exit(1), 100);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
execute();
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WAB Container Worker — Runs inside a forked child process
|
|
5
|
+
*
|
|
6
|
+
* This script is the entry point for process-isolated task execution.
|
|
7
|
+
* It reads the task definition from a JSON file, executes it,
|
|
8
|
+
* and sends results back via IPC.
|
|
9
|
+
*
|
|
10
|
+
* Security:
|
|
11
|
+
* - Runs in a separate process with memory limits (--max-old-space-size)
|
|
12
|
+
* - Limited filesystem access (only its tmp directory)
|
|
13
|
+
* - Can disable network via environment
|
|
14
|
+
* - Timeout enforced by parent
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const taskFile = process.argv[2];
|
|
21
|
+
if (!taskFile) {
|
|
22
|
+
process.stderr.write('No task file specified\n');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let taskData;
|
|
27
|
+
try {
|
|
28
|
+
taskData = JSON.parse(fs.readFileSync(taskFile, 'utf8'));
|
|
29
|
+
} catch (err) {
|
|
30
|
+
process.stderr.write(`Failed to read task file: ${err.message}\n`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Sandbox Utilities (available to task code) ──────────────────────
|
|
35
|
+
|
|
36
|
+
const sandbox = {
|
|
37
|
+
taskId: taskData.taskId,
|
|
38
|
+
containerId: taskData.containerId,
|
|
39
|
+
params: taskData.params || {},
|
|
40
|
+
|
|
41
|
+
// Send progress updates
|
|
42
|
+
progress(pct) {
|
|
43
|
+
if (process.send) process.send({ type: 'progress', progress: pct });
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Send log messages
|
|
47
|
+
log(message) {
|
|
48
|
+
if (process.send) process.send({ type: 'log', message: String(message).slice(0, 1000) });
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// Read a param
|
|
52
|
+
param(key, defaultValue) {
|
|
53
|
+
return taskData.params[key] !== undefined ? taskData.params[key] : defaultValue;
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Filesystem is restricted to tmpDir
|
|
57
|
+
tmpDir: path.dirname(taskFile),
|
|
58
|
+
|
|
59
|
+
readFile(name) {
|
|
60
|
+
const p = path.join(sandbox.tmpDir, path.basename(name));
|
|
61
|
+
return fs.readFileSync(p, 'utf8');
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
writeFile(name, content) {
|
|
65
|
+
const p = path.join(sandbox.tmpDir, path.basename(name));
|
|
66
|
+
fs.writeFileSync(p, content);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ─── Execute Task ────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
async function execute() {
|
|
73
|
+
try {
|
|
74
|
+
let result;
|
|
75
|
+
|
|
76
|
+
if (taskData.code) {
|
|
77
|
+
// Execute provided code string in a restricted scope
|
|
78
|
+
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
79
|
+
const fn = new AsyncFunction('sandbox', 'params', taskData.code);
|
|
80
|
+
result = await fn(sandbox, taskData.params);
|
|
81
|
+
} else if (taskData.module) {
|
|
82
|
+
// Execute a module (for trusted internal tasks)
|
|
83
|
+
const mod = require(taskData.module);
|
|
84
|
+
if (typeof mod.execute === 'function') {
|
|
85
|
+
result = await mod.execute(taskData.params, sandbox);
|
|
86
|
+
} else {
|
|
87
|
+
result = { error: 'Module has no execute() function' };
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
result = { echo: taskData.params, message: 'No code or module specified' };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Send result back via IPC
|
|
94
|
+
if (process.send) {
|
|
95
|
+
process.send({ type: 'result', data: result });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Give IPC time to flush
|
|
99
|
+
setTimeout(() => process.exit(0), 100);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
process.stderr.write(`Task error: ${err.message}\n${err.stack}\n`);
|
|
102
|
+
|
|
103
|
+
if (process.send) {
|
|
104
|
+
process.send({ type: 'result', data: null });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setTimeout(() => process.exit(1), 100);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
execute();
|