web-agent-bridge 3.3.0 → 3.8.1
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 -72
- package/README.ar.md +1563 -1286
- package/README.md +137 -1764
- package/bin/agent-runner.js +474 -474
- package/bin/cli.js +237 -237
- package/bin/wab-init.js +244 -0
- package/bin/wab.js +80 -80
- package/examples/azure-dns-wab.js +83 -0
- package/examples/bidi-agent.js +119 -119
- package/examples/cloudflare-wab-dns.js +121 -0
- package/examples/cpanel-wab-dns.js +114 -0
- package/examples/cross-site-agent.js +91 -91
- package/examples/dns-discovery-agent.js +166 -0
- package/examples/gcp-dns-wab.js +76 -0
- package/examples/governance-agent.js +169 -0
- package/examples/mcp-agent.js +94 -94
- package/examples/next-app-router/README.md +44 -44
- package/examples/plesk-wab-dns.js +103 -0
- package/examples/puppeteer-agent.js +108 -108
- package/examples/route53-wab-dns.js +144 -0
- package/examples/saas-dashboard/README.md +55 -55
- package/examples/safe-mode-agent.js +96 -0
- 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 -0
- package/examples/wab-verify.js +60 -0
- 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 -0
- package/public/activate.html +448 -0
- package/public/adopt.html +236 -0
- package/public/adoption-metrics.html +188 -0
- package/public/agent-workspace.html +359 -349
- package/public/ai.html +198 -198
- package/public/api.html +397 -413
- package/public/azure-dns-integration.html +289 -0
- package/public/browser.html +486 -486
- package/public/cloudflare-integration.html +380 -0
- package/public/commander-dashboard.html +243 -243
- package/public/cookies.html +210 -210
- package/public/cpanel-integration.html +398 -0
- package/public/css/agent-workspace.css +1713 -1713
- package/public/css/premium.css +317 -317
- package/public/css/styles.css +1401 -1235
- package/public/dashboard-shieldlink.html +295 -0
- package/public/dashboard.html +711 -706
- package/public/dns.html +436 -507
- package/public/docs.html +588 -587
- 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 -0
- package/public/governance.ar.html +70 -0
- package/public/governance.html +69 -0
- package/public/growth.html +465 -463
- package/public/index.html +1372 -1070
- package/public/integrations.html +556 -556
- package/public/js/activate.js +449 -0
- package/public/js/agent-workspace.js +1740 -1740
- package/public/js/auth-nav.js +117 -31
- package/public/js/auth-redirect.js +12 -12
- package/public/js/cookie-consent.js +56 -56
- package/public/js/dns.js +438 -0
- 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 -580
- 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 -0
- 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 -0
- package/public/provider-sandbox.html +134 -0
- package/public/providers.html +359 -0
- package/public/refusals.html +172 -0
- package/public/register.html +105 -105
- package/public/registrar-integrations.html +141 -0
- package/public/ring4.html +292 -0
- package/public/robots.txt +99 -87
- package/public/route53-integration.html +531 -0
- 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 -0
- package/public/sitemap.xml +19 -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 -0
- package/public/wab-truth.html +375 -0
- package/public/wab-vs-protocols.html +210 -0
- package/public/whitepaper.html +449 -0
- package/script/ai-agent-bridge.js +1754 -1754
- package/sdk/README.md +99 -99
- package/sdk/agent-mesh.js +449 -449
- package/sdk/auto-discovery.js +301 -0
- package/sdk/commander.js +262 -262
- package/sdk/governance.js +262 -0
- package/sdk/index.d.ts +464 -464
- package/sdk/index.js +649 -636
- package/sdk/multi-agent.js +318 -318
- package/sdk/package.json +2 -2
- package/sdk/safe-mode.js +221 -0
- 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 +790 -531
- 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 -0
- package/server/migrations/008_plans.sql +144 -0
- package/server/migrations/009_shieldqr.sql +30 -0
- package/server/migrations/010_extended_trust.sql +33 -0
- 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/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 -681
- 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 -0
- package/server/routes/admin-premium.js +674 -671
- package/server/routes/admin-shieldlink.js +137 -0
- package/server/routes/admin-shieldqr.js +90 -0
- package/server/routes/admin-trust-monitor.js +139 -0
- package/server/routes/admin.js +550 -261
- 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 -45
- 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 -417
- 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 -0
- 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 -0
- package/server/routes/premium-v2.js +686 -686
- package/server/routes/premium.js +724 -724
- package/server/routes/providers.js +650 -0
- 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 -0
- package/server/routes/sovereign.js +465 -465
- 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 -204
- package/server/services/fairness-engine.js +409 -0
- package/server/services/fairness.js +420 -0
- package/server/services/governance.js +466 -0
- 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 -0
- package/server/services/plugins.js +771 -771
- package/server/services/premium.js +1 -1
- package/server/services/price-intelligence.js +566 -566
- package/server/services/price-shield.js +1137 -1137
- package/server/services/provider-clients.js +740 -0
- 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 -0
- package/server/services/sovereign-shield.js +542 -542
- package/server/services/ssl-ct-monitor.js +224 -0
- package/server/services/ssl-inspector.js +42 -0
- package/server/services/ssl-monitor.js +167 -0
- package/server/services/stripe.js +206 -192
- package/server/services/swarm.js +788 -788
- 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 -0
- 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
|
@@ -1,200 +1,200 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WAB Universal Agent — Server Routes
|
|
3
|
-
* ═══════════════════════════════════════════════════════════════════
|
|
4
|
-
* API endpoints for the Universal Agent mode.
|
|
5
|
-
* Used by: WAB Browser, Chrome Extension, direct API calls.
|
|
6
|
-
*
|
|
7
|
-
* All endpoints work WITHOUT requiring the target site to install any script.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const express = require('express');
|
|
11
|
-
const router = express.Router();
|
|
12
|
-
const scraper = require('../services/universal-scraper');
|
|
13
|
-
const priceIntel = require('../services/price-intelligence');
|
|
14
|
-
let urlPolicy;
|
|
15
|
-
try { urlPolicy = require('../security/url-policy'); } catch { urlPolicy = null; }
|
|
16
|
-
let fairness;
|
|
17
|
-
try { fairness = require('../services/fairness-engine'); } catch {
|
|
18
|
-
fairness = {
|
|
19
|
-
calculateFairnessScore: () => ({ score: 0, label: 'unrated' }),
|
|
20
|
-
rankWithFairness: (_items) => _items,
|
|
21
|
-
detectDarkPatterns: () => [],
|
|
22
|
-
getTopFairSites: () => []
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ─── POST /api/universal/extract ─────────────────────────────────────
|
|
27
|
-
// Extract prices/products from a URL (server-side fetch)
|
|
28
|
-
router.post('/extract', async (req, res) => {
|
|
29
|
-
const { url } = req.body;
|
|
30
|
-
if (!url) return res.status(400).json({ error: 'URL required' });
|
|
31
|
-
|
|
32
|
-
if (urlPolicy) {
|
|
33
|
-
const v = urlPolicy.check(url, { actor: urlPolicy.actorFromReq(req) });
|
|
34
|
-
if (!v.ok) {
|
|
35
|
-
return res.status(v.code === 'RATE_LIMITED' ? 429 : 400).json({ error: v.reason, code: v.code });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const result = await scraper.fetchAndExtract(url);
|
|
41
|
-
res.json(result);
|
|
42
|
-
} catch (err) {
|
|
43
|
-
res.status(500).json({ error: err.message });
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// ─── POST /api/universal/analyze ─────────────────────────────────────
|
|
48
|
-
// Full analysis: extract + fraud detection + trust score
|
|
49
|
-
// Accepts either a URL (server-side fetch) or pre-extracted data (from browser)
|
|
50
|
-
router.post('/analyze', async (req, res) => {
|
|
51
|
-
const { url, extraction } = req.body;
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
let result;
|
|
55
|
-
|
|
56
|
-
if (extraction) {
|
|
57
|
-
// Data already extracted by browser/extension
|
|
58
|
-
const processed = scraper.processBrowserExtraction(extraction);
|
|
59
|
-
result = await priceIntel.analyzePrice(extraction.url || url || '', processed);
|
|
60
|
-
|
|
61
|
-
// Add dark pattern detection if text available
|
|
62
|
-
if (extraction.darkPatterns) {
|
|
63
|
-
result.darkPatterns = extraction.darkPatterns;
|
|
64
|
-
}
|
|
65
|
-
} else if (url) {
|
|
66
|
-
// Server-side fetch and analyze
|
|
67
|
-
if (urlPolicy) {
|
|
68
|
-
const v = urlPolicy.check(url, { actor: urlPolicy.actorFromReq(req) });
|
|
69
|
-
if (!v.ok) {
|
|
70
|
-
return res.status(v.code === 'RATE_LIMITED' ? 429 : 400).json({ error: v.reason, code: v.code });
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
result = await priceIntel.analyzePrice(url);
|
|
74
|
-
} else {
|
|
75
|
-
return res.status(400).json({ error: 'URL or extraction data required' });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Add fairness score for the domain
|
|
79
|
-
if (result.domain) {
|
|
80
|
-
result.fairness = fairness.calculateFairnessScore(result.domain, {
|
|
81
|
-
fraudAlerts: (result.alerts || []).length,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
res.json(result);
|
|
86
|
-
} catch (err) {
|
|
87
|
-
res.status(500).json({ error: err.message });
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// ─── POST /api/universal/compare ─────────────────────────────────────
|
|
92
|
-
// Compare prices across multiple sources
|
|
93
|
-
router.post('/compare', async (req, res) => {
|
|
94
|
-
const { query, category, maxSources } = req.body;
|
|
95
|
-
if (!query) return res.status(400).json({ error: 'Query required' });
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const result = await priceIntel.compareAcrossSources(
|
|
99
|
-
query,
|
|
100
|
-
category || 'product',
|
|
101
|
-
{ maxSources: maxSources || 8 }
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
// Apply fairness ranking
|
|
105
|
-
if (result.results && result.results.length > 0) {
|
|
106
|
-
result.results = fairness.rankWithFairness(result.results, {
|
|
107
|
-
avgPrice: result.avgPrice,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
res.json(result);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
res.status(500).json({ error: err.message });
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// ─── POST /api/universal/deals ───────────────────────────────────────
|
|
118
|
-
// Find best deals with fairness ranking + fraud detection
|
|
119
|
-
router.post('/deals', async (req, res) => {
|
|
120
|
-
const { query, category, lang } = req.body;
|
|
121
|
-
if (!query) return res.status(400).json({ error: 'Query required' });
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const result = await priceIntel.findBestDeals(
|
|
125
|
-
query,
|
|
126
|
-
category || 'product',
|
|
127
|
-
{ lang: lang || 'en' }
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
// Apply fairness ranking to deals
|
|
131
|
-
if (result.deals && result.deals.length > 0) {
|
|
132
|
-
result.deals = fairness.rankWithFairness(result.deals, {
|
|
133
|
-
avgPrice: result.deals.reduce((s, d) => s + (d.priceUsd || 0), 0) / result.deals.length,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
res.json(result);
|
|
138
|
-
} catch (err) {
|
|
139
|
-
res.status(500).json({ error: err.message });
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// ─── POST /api/universal/fairness ────────────────────────────────────
|
|
144
|
-
// Get fairness score for a domain
|
|
145
|
-
router.post('/fairness', (req, res) => {
|
|
146
|
-
const { domain, url } = req.body;
|
|
147
|
-
const d = domain || (url ? (() => { try { return new URL(url).hostname; } catch (_) { return ''; } })() : '');
|
|
148
|
-
if (!d) return res.status(400).json({ error: 'Domain or URL required' });
|
|
149
|
-
|
|
150
|
-
const score = fairness.calculateFairnessScore(d);
|
|
151
|
-
res.json(score);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// ─── POST /api/universal/dark-patterns ───────────────────────────────
|
|
155
|
-
// Detect dark patterns in page text
|
|
156
|
-
router.post('/dark-patterns', (req, res) => {
|
|
157
|
-
const { text, lang } = req.body;
|
|
158
|
-
if (!text) return res.status(400).json({ error: 'Text required' });
|
|
159
|
-
|
|
160
|
-
const patterns = fairness.detectDarkPatterns(text, lang || 'en');
|
|
161
|
-
res.json({ patterns, count: patterns.length });
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// ─── GET /api/universal/history ──────────────────────────────────────
|
|
165
|
-
// Get price history for a URL
|
|
166
|
-
router.get('/history', (req, res) => {
|
|
167
|
-
const { url } = req.query;
|
|
168
|
-
if (!url) return res.status(400).json({ error: 'URL required' });
|
|
169
|
-
|
|
170
|
-
const history = scraper.getPriceHistory(url, 30);
|
|
171
|
-
res.json({ url, history });
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// ─── GET /api/universal/top-fair ─────────────────────────────────────
|
|
175
|
-
// Get top fairness-ranked sites
|
|
176
|
-
router.get('/top-fair', (req, res) => {
|
|
177
|
-
const limit = parseInt(req.query.limit) || 20;
|
|
178
|
-
const sites = fairness.getTopFairSites(limit);
|
|
179
|
-
res.json({ sites });
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// ─── GET /api/universal/extraction-script ────────────────────────────
|
|
183
|
-
// Get the browser extraction script (for dynamic injection)
|
|
184
|
-
router.get('/extraction-script', (req, res) => {
|
|
185
|
-
res.set('Content-Type', 'application/javascript');
|
|
186
|
-
res.send(scraper.getBrowserExtractionScript());
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// ─── GET /api/universal/sources ──────────────────────────────────────
|
|
190
|
-
// List all competing sources by category
|
|
191
|
-
router.get('/sources', (req, res) => {
|
|
192
|
-
const { category } = req.query;
|
|
193
|
-
if (category && priceIntel.COMPETING_SOURCES[category]) {
|
|
194
|
-
res.json({ category, sources: priceIntel.COMPETING_SOURCES[category] });
|
|
195
|
-
} else {
|
|
196
|
-
res.json(priceIntel.COMPETING_SOURCES);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
module.exports = router;
|
|
1
|
+
/**
|
|
2
|
+
* WAB Universal Agent — Server Routes
|
|
3
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
4
|
+
* API endpoints for the Universal Agent mode.
|
|
5
|
+
* Used by: WAB Browser, Chrome Extension, direct API calls.
|
|
6
|
+
*
|
|
7
|
+
* All endpoints work WITHOUT requiring the target site to install any script.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const express = require('express');
|
|
11
|
+
const router = express.Router();
|
|
12
|
+
const scraper = require('../services/universal-scraper');
|
|
13
|
+
const priceIntel = require('../services/price-intelligence');
|
|
14
|
+
let urlPolicy;
|
|
15
|
+
try { urlPolicy = require('../security/url-policy'); } catch { urlPolicy = null; }
|
|
16
|
+
let fairness;
|
|
17
|
+
try { fairness = require('../services/fairness-engine'); } catch {
|
|
18
|
+
fairness = {
|
|
19
|
+
calculateFairnessScore: () => ({ score: 0, label: 'unrated' }),
|
|
20
|
+
rankWithFairness: (_items) => _items,
|
|
21
|
+
detectDarkPatterns: () => [],
|
|
22
|
+
getTopFairSites: () => []
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── POST /api/universal/extract ─────────────────────────────────────
|
|
27
|
+
// Extract prices/products from a URL (server-side fetch)
|
|
28
|
+
router.post('/extract', async (req, res) => {
|
|
29
|
+
const { url } = req.body;
|
|
30
|
+
if (!url) return res.status(400).json({ error: 'URL required' });
|
|
31
|
+
|
|
32
|
+
if (urlPolicy) {
|
|
33
|
+
const v = urlPolicy.check(url, { actor: urlPolicy.actorFromReq(req) });
|
|
34
|
+
if (!v.ok) {
|
|
35
|
+
return res.status(v.code === 'RATE_LIMITED' ? 429 : 400).json({ error: v.reason, code: v.code });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const result = await scraper.fetchAndExtract(url);
|
|
41
|
+
res.json(result);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
res.status(500).json({ error: err.message });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ─── POST /api/universal/analyze ─────────────────────────────────────
|
|
48
|
+
// Full analysis: extract + fraud detection + trust score
|
|
49
|
+
// Accepts either a URL (server-side fetch) or pre-extracted data (from browser)
|
|
50
|
+
router.post('/analyze', async (req, res) => {
|
|
51
|
+
const { url, extraction } = req.body;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
let result;
|
|
55
|
+
|
|
56
|
+
if (extraction) {
|
|
57
|
+
// Data already extracted by browser/extension
|
|
58
|
+
const processed = scraper.processBrowserExtraction(extraction);
|
|
59
|
+
result = await priceIntel.analyzePrice(extraction.url || url || '', processed);
|
|
60
|
+
|
|
61
|
+
// Add dark pattern detection if text available
|
|
62
|
+
if (extraction.darkPatterns) {
|
|
63
|
+
result.darkPatterns = extraction.darkPatterns;
|
|
64
|
+
}
|
|
65
|
+
} else if (url) {
|
|
66
|
+
// Server-side fetch and analyze
|
|
67
|
+
if (urlPolicy) {
|
|
68
|
+
const v = urlPolicy.check(url, { actor: urlPolicy.actorFromReq(req) });
|
|
69
|
+
if (!v.ok) {
|
|
70
|
+
return res.status(v.code === 'RATE_LIMITED' ? 429 : 400).json({ error: v.reason, code: v.code });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
result = await priceIntel.analyzePrice(url);
|
|
74
|
+
} else {
|
|
75
|
+
return res.status(400).json({ error: 'URL or extraction data required' });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Add fairness score for the domain
|
|
79
|
+
if (result.domain) {
|
|
80
|
+
result.fairness = fairness.calculateFairnessScore(result.domain, {
|
|
81
|
+
fraudAlerts: (result.alerts || []).length,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
res.json(result);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
res.status(500).json({ error: err.message });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ─── POST /api/universal/compare ─────────────────────────────────────
|
|
92
|
+
// Compare prices across multiple sources
|
|
93
|
+
router.post('/compare', async (req, res) => {
|
|
94
|
+
const { query, category, maxSources } = req.body;
|
|
95
|
+
if (!query) return res.status(400).json({ error: 'Query required' });
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const result = await priceIntel.compareAcrossSources(
|
|
99
|
+
query,
|
|
100
|
+
category || 'product',
|
|
101
|
+
{ maxSources: maxSources || 8 }
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Apply fairness ranking
|
|
105
|
+
if (result.results && result.results.length > 0) {
|
|
106
|
+
result.results = fairness.rankWithFairness(result.results, {
|
|
107
|
+
avgPrice: result.avgPrice,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
res.json(result);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
res.status(500).json({ error: err.message });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ─── POST /api/universal/deals ───────────────────────────────────────
|
|
118
|
+
// Find best deals with fairness ranking + fraud detection
|
|
119
|
+
router.post('/deals', async (req, res) => {
|
|
120
|
+
const { query, category, lang } = req.body;
|
|
121
|
+
if (!query) return res.status(400).json({ error: 'Query required' });
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const result = await priceIntel.findBestDeals(
|
|
125
|
+
query,
|
|
126
|
+
category || 'product',
|
|
127
|
+
{ lang: lang || 'en' }
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Apply fairness ranking to deals
|
|
131
|
+
if (result.deals && result.deals.length > 0) {
|
|
132
|
+
result.deals = fairness.rankWithFairness(result.deals, {
|
|
133
|
+
avgPrice: result.deals.reduce((s, d) => s + (d.priceUsd || 0), 0) / result.deals.length,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
res.json(result);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
res.status(500).json({ error: err.message });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// ─── POST /api/universal/fairness ────────────────────────────────────
|
|
144
|
+
// Get fairness score for a domain
|
|
145
|
+
router.post('/fairness', (req, res) => {
|
|
146
|
+
const { domain, url } = req.body;
|
|
147
|
+
const d = domain || (url ? (() => { try { return new URL(url).hostname; } catch (_) { return ''; } })() : '');
|
|
148
|
+
if (!d) return res.status(400).json({ error: 'Domain or URL required' });
|
|
149
|
+
|
|
150
|
+
const score = fairness.calculateFairnessScore(d);
|
|
151
|
+
res.json(score);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ─── POST /api/universal/dark-patterns ───────────────────────────────
|
|
155
|
+
// Detect dark patterns in page text
|
|
156
|
+
router.post('/dark-patterns', (req, res) => {
|
|
157
|
+
const { text, lang } = req.body;
|
|
158
|
+
if (!text) return res.status(400).json({ error: 'Text required' });
|
|
159
|
+
|
|
160
|
+
const patterns = fairness.detectDarkPatterns(text, lang || 'en');
|
|
161
|
+
res.json({ patterns, count: patterns.length });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ─── GET /api/universal/history ──────────────────────────────────────
|
|
165
|
+
// Get price history for a URL
|
|
166
|
+
router.get('/history', (req, res) => {
|
|
167
|
+
const { url } = req.query;
|
|
168
|
+
if (!url) return res.status(400).json({ error: 'URL required' });
|
|
169
|
+
|
|
170
|
+
const history = scraper.getPriceHistory(url, 30);
|
|
171
|
+
res.json({ url, history });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ─── GET /api/universal/top-fair ─────────────────────────────────────
|
|
175
|
+
// Get top fairness-ranked sites
|
|
176
|
+
router.get('/top-fair', (req, res) => {
|
|
177
|
+
const limit = parseInt(req.query.limit) || 20;
|
|
178
|
+
const sites = fairness.getTopFairSites(limit);
|
|
179
|
+
res.json({ sites });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// ─── GET /api/universal/extraction-script ────────────────────────────
|
|
183
|
+
// Get the browser extraction script (for dynamic injection)
|
|
184
|
+
router.get('/extraction-script', (req, res) => {
|
|
185
|
+
res.set('Content-Type', 'application/javascript');
|
|
186
|
+
res.send(scraper.getBrowserExtractionScript());
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ─── GET /api/universal/sources ──────────────────────────────────────
|
|
190
|
+
// List all competing sources by category
|
|
191
|
+
router.get('/sources', (req, res) => {
|
|
192
|
+
const { category } = req.query;
|
|
193
|
+
if (category && priceIntel.COMPETING_SOURCES[category]) {
|
|
194
|
+
res.json({ category, sources: priceIntel.COMPETING_SOURCES[category] });
|
|
195
|
+
} else {
|
|
196
|
+
res.json(priceIntel.COMPETING_SOURCES);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
module.exports = router;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public unsubscribe — token-based, GET so it works from email links.
|
|
3
|
+
* RFC 8058 List-Unsubscribe-Post=One-Click also supports POST (no auth).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const express = require('express');
|
|
9
|
+
const router = express.Router();
|
|
10
|
+
const { db } = require('../models/db');
|
|
11
|
+
|
|
12
|
+
function _process(token) {
|
|
13
|
+
if (!token || !/^[a-f0-9]{8,64}$/i.test(token)) return { ok: false, code: 400 };
|
|
14
|
+
const row = db.prepare('SELECT id, contact_email, host FROM outreach_targets WHERE unsubscribe_token = ?').get(token);
|
|
15
|
+
if (!row) return { ok: false, code: 404 };
|
|
16
|
+
if (row.contact_email) {
|
|
17
|
+
db.prepare('INSERT OR IGNORE INTO outreach_suppression (email_or_host, reason) VALUES (?, ?)').run(String(row.contact_email).toLowerCase(), 'unsubscribe');
|
|
18
|
+
}
|
|
19
|
+
if (row.host) {
|
|
20
|
+
db.prepare('INSERT OR IGNORE INTO outreach_suppression (email_or_host, reason) VALUES (?, ?)').run(String(row.host).toLowerCase(), 'unsubscribe');
|
|
21
|
+
}
|
|
22
|
+
db.prepare("UPDATE outreach_targets SET status='suppressed', updated_at=datetime('now') WHERE unsubscribe_token = ?").run(token);
|
|
23
|
+
try { db.prepare('INSERT INTO outreach_log (target_id, event, details) VALUES (?, ?, ?)').run(row.id, 'unsubscribed', null); } catch { /* ignore */ }
|
|
24
|
+
return { ok: true, email: row.contact_email, host: row.host };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const _page = (msg, color) => `<!doctype html><html><head><meta charset="utf-8"><title>Unsubscribed — Web Agent Bridge</title>
|
|
28
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
29
|
+
<style>body{margin:0;background:#0a0e1a;color:#e8eeff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;display:grid;place-items:center;padding:20px}
|
|
30
|
+
.card{background:#0f1626;border:1px solid #1f2a44;border-radius:16px;padding:36px;max-width:520px;text-align:center}
|
|
31
|
+
.ic{width:64px;height:64px;margin:0 auto 18px;border-radius:50%;background:${color};display:grid;place-items:center;font-size:32px}
|
|
32
|
+
h1{margin:0 0 10px;font-size:22px}p{color:#8a94b0;line-height:1.6}a{color:#22d3ee}</style></head>
|
|
33
|
+
<body><div class="card"><div class="ic">✓</div><h1>${msg.title}</h1><p>${msg.body}</p><p style="margin-top:22px"><a href="https://www.webagentbridge.com">webagentbridge.com</a></p></div></body></html>`;
|
|
34
|
+
|
|
35
|
+
router.get('/unsubscribe', (req, res) => {
|
|
36
|
+
const r = _process(req.query.token);
|
|
37
|
+
if (!r.ok) {
|
|
38
|
+
res.status(r.code).type('html').send(_page({ title: 'Link not valid', body: 'This unsubscribe link is invalid or has expired. Please contact support@webagentbridge.com if you continue to receive emails.' }, '#ef4444'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
res.type('html').send(_page({ title: 'You are unsubscribed', body: `${r.email || r.host} has been added to our suppression list. You will not receive further outreach from us.` }, '#22c55e'));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// RFC 8058 one-click POST (used by Gmail/Outlook auto-unsubscribe buttons)
|
|
45
|
+
router.post('/unsubscribe', express.urlencoded({ extended: false }), (req, res) => {
|
|
46
|
+
const token = (req.body && req.body.token) || req.query.token;
|
|
47
|
+
const r = _process(token);
|
|
48
|
+
res.status(r.ok ? 200 : (r.code || 400)).json({ ok: r.ok });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
module.exports = router;
|