web-agent-bridge 1.0.0 → 1.1.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/README.ar.md +1 -1
- package/README.md +336 -36
- package/docs/DEPLOY.md +118 -0
- package/docs/SPEC.md +1540 -0
- package/examples/mcp-agent.js +85 -0
- package/examples/vision-agent.js +12 -0
- package/package.json +14 -3
- package/public/admin/dashboard.html +848 -0
- package/public/admin/login.html +84 -0
- package/public/cookies.html +208 -0
- package/public/css/premium.css +317 -0
- package/public/dashboard.html +138 -0
- package/public/docs.html +5 -2
- package/public/index.html +54 -28
- package/public/js/auth-nav.js +31 -0
- package/public/js/auth-redirect.js +12 -0
- package/public/js/cookie-consent.js +56 -0
- package/public/js/ws-client.js +74 -0
- package/public/login.html +4 -2
- package/public/premium-dashboard.html +2075 -0
- package/public/premium.html +791 -0
- package/public/privacy.html +295 -0
- package/public/register.html +11 -2
- package/public/terms.html +254 -0
- package/script/ai-agent-bridge.js +253 -22
- package/sdk/index.js +36 -0
- package/server/config/secrets.js +92 -0
- package/server/index.js +100 -26
- package/server/middleware/adminAuth.js +30 -0
- package/server/middleware/auth.js +4 -7
- package/server/middleware/rateLimits.js +24 -0
- package/server/migrations/001_add_analytics_indexes.sql +7 -0
- package/server/migrations/002_premium_features.sql +418 -0
- package/server/models/db.js +360 -4
- package/server/routes/admin.js +247 -0
- package/server/routes/api.js +26 -9
- package/server/routes/billing.js +45 -0
- package/server/routes/discovery.js +324 -0
- package/server/routes/license.js +200 -11
- package/server/routes/noscript.js +543 -0
- package/server/routes/premium.js +724 -0
- package/server/services/email.js +204 -0
- package/server/services/fairness.js +420 -0
- package/server/services/premium.js +1680 -0
- package/server/services/stripe.js +192 -0
- package/server/utils/cache.js +125 -0
- package/server/utils/migrate.js +81 -0
- package/server/utils/secureFields.js +50 -0
- package/server/ws.js +33 -13
- package/wab-mcp-adapter/README.md +136 -0
- package/wab-mcp-adapter/index.js +528 -0
- package/wab-mcp-adapter/package.json +17 -0
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Web Agent Bridge v1.
|
|
3
|
-
* Open
|
|
2
|
+
* Web Agent Bridge v1.1.0
|
|
3
|
+
* Open protocol + runtime for AI agent ↔ website interaction
|
|
4
4
|
* https://github.com/web-agent-bridge
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
7
7
|
(function (global) {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
-
const VERSION = '1.
|
|
10
|
+
const VERSION = '1.1.0';
|
|
11
|
+
const PROTOCOL_VERSION = '1.0';
|
|
11
12
|
const LICENSING_SERVER = 'https://api.webagentbridge.com';
|
|
13
|
+
const DISCOVERY_PATHS = ['/agent-bridge.json', '/.well-known/wab.json'];
|
|
12
14
|
|
|
13
15
|
// ─── Default Configuration ────────────────────────────────────────────
|
|
14
16
|
const DEFAULT_CONFIG = {
|
|
@@ -22,12 +24,6 @@
|
|
|
22
24
|
automatedLogin: false,
|
|
23
25
|
extractData: false
|
|
24
26
|
},
|
|
25
|
-
features: {
|
|
26
|
-
advancedAnalytics: false,
|
|
27
|
-
realTimeUpdates: false,
|
|
28
|
-
customActions: false,
|
|
29
|
-
webhooks: false
|
|
30
|
-
},
|
|
31
27
|
restrictions: {
|
|
32
28
|
allowedSelectors: [],
|
|
33
29
|
blockedSelectors: ['.private', '[data-private]', '[data-no-agent]'],
|
|
@@ -39,7 +35,19 @@
|
|
|
39
35
|
level: 'basic'
|
|
40
36
|
},
|
|
41
37
|
subscriptionTier: 'free',
|
|
42
|
-
licenseKey: null
|
|
38
|
+
licenseKey: null,
|
|
39
|
+
/** Public site id from dashboard (preferred). Used with configEndpoint for token exchange — license key stays off the page. */
|
|
40
|
+
siteId: null,
|
|
41
|
+
/** Base URL for /api/license/* (verify, track). Default: current origin, else LICENSING_SERVER. */
|
|
42
|
+
apiBaseUrl: null,
|
|
43
|
+
features: {
|
|
44
|
+
advancedAnalytics: false,
|
|
45
|
+
realTimeUpdates: false,
|
|
46
|
+
customActions: false,
|
|
47
|
+
webhooks: false,
|
|
48
|
+
/** Send execute events to POST /api/license/track (populates admin analytics). */
|
|
49
|
+
reportUsage: true
|
|
50
|
+
}
|
|
43
51
|
};
|
|
44
52
|
|
|
45
53
|
// ─── Rate Limiter ─────────────────────────────────────────────────────
|
|
@@ -348,15 +356,28 @@
|
|
|
348
356
|
}
|
|
349
357
|
|
|
350
358
|
// ─── Stealth / Human-like Interaction ─────────────────────────────────
|
|
351
|
-
//
|
|
359
|
+
// ⚠️ ETHICAL USE POLICY:
|
|
360
|
+
// Stealth mode simulates human-like interaction patterns for LEGITIMATE uses:
|
|
361
|
+
// - Accessibility testing - QA automation on YOUR OWN sites
|
|
362
|
+
// - UX research with consent - Authorized penetration testing
|
|
363
|
+
// DO NOT use to bypass anti-bot protections on sites you do not own or control.
|
|
364
|
+
// Misuse violates the MIT license terms and is the user's legal responsibility.
|
|
352
365
|
const Stealth = {
|
|
353
366
|
_enabled: false,
|
|
367
|
+
_consentGiven: false,
|
|
354
368
|
|
|
355
|
-
enable() {
|
|
369
|
+
enable(consent) {
|
|
370
|
+
if (consent !== true) {
|
|
371
|
+
console.warn('[WAB] Stealth mode requires explicit consent: stealth.enable(true)');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
this._consentGiven = true;
|
|
375
|
+
this._enabled = true;
|
|
376
|
+
},
|
|
356
377
|
disable() { this._enabled = false; },
|
|
357
378
|
get isEnabled() { return this._enabled; },
|
|
358
379
|
|
|
359
|
-
// Random delay between min and max ms
|
|
380
|
+
// Random delay between min and max ms
|
|
360
381
|
delay(min = 50, max = 300) {
|
|
361
382
|
if (!this._enabled) return Promise.resolve();
|
|
362
383
|
const duration = min + Math.floor(Math.random() * (max - min));
|
|
@@ -557,13 +578,15 @@
|
|
|
557
578
|
this.authenticated = false;
|
|
558
579
|
this.agentInfo = null;
|
|
559
580
|
this._licenseVerified = null;
|
|
581
|
+
this._discoveryDoc = null;
|
|
560
582
|
this._ready = false;
|
|
561
583
|
this._readyCallbacks = [];
|
|
562
584
|
this._mutationObserver = null;
|
|
585
|
+
this._eventSubscriptions = new Map();
|
|
563
586
|
|
|
564
|
-
// Enable stealth mode if configured
|
|
565
|
-
if (this.config.stealth?.enabled) {
|
|
566
|
-
this.stealth.enable();
|
|
587
|
+
// Enable stealth mode if configured (requires consent: true)
|
|
588
|
+
if (this.config.stealth?.enabled && this.config.stealth?.consent === true) {
|
|
589
|
+
this.stealth.enable(true);
|
|
567
590
|
}
|
|
568
591
|
|
|
569
592
|
this._init();
|
|
@@ -571,7 +594,11 @@
|
|
|
571
594
|
|
|
572
595
|
// ── Initialization ──────────────────────────────────────────────────
|
|
573
596
|
async _init() {
|
|
574
|
-
|
|
597
|
+
await this._fetchDiscoveryDocument();
|
|
598
|
+
|
|
599
|
+
if (this.config.configEndpoint && (this.config.siteId || this.config._licenseKey || this.config.licenseKey)) {
|
|
600
|
+
await this._secureLicenseExchange();
|
|
601
|
+
} else if (this.config.licenseKey) {
|
|
575
602
|
await this._verifyLicense();
|
|
576
603
|
} else {
|
|
577
604
|
this._licenseVerified = { tier: 'free', valid: true };
|
|
@@ -583,8 +610,54 @@
|
|
|
583
610
|
this._ready = true;
|
|
584
611
|
this._readyCallbacks.forEach(cb => cb());
|
|
585
612
|
this._readyCallbacks = [];
|
|
586
|
-
this.events.emit('ready', { version: VERSION, tier: this.getEffectiveTier() });
|
|
587
|
-
this.logger.log('init', { version: VERSION, tier: this.getEffectiveTier(), security: 'sandbox-active' });
|
|
613
|
+
this.events.emit('ready', { version: VERSION, protocol: PROTOCOL_VERSION, tier: this.getEffectiveTier() });
|
|
614
|
+
this.logger.log('init', { version: VERSION, protocol: PROTOCOL_VERSION, tier: this.getEffectiveTier(), security: 'sandbox-active' });
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async _fetchDiscoveryDocument() {
|
|
618
|
+
var base = this._getLicenseApiBase();
|
|
619
|
+
for (var i = 0; i < DISCOVERY_PATHS.length; i++) {
|
|
620
|
+
try {
|
|
621
|
+
var res = await fetch(base + DISCOVERY_PATHS[i], { method: 'GET', credentials: 'omit' });
|
|
622
|
+
if (res.ok) {
|
|
623
|
+
this._discoveryDoc = await res.json();
|
|
624
|
+
this.logger.log('discovery', { path: DISCOVERY_PATHS[i], provider: this._discoveryDoc.provider });
|
|
625
|
+
this.events.emit('discovery', this._discoveryDoc);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
} catch (_) {}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Secure license exchange: POST license key to server, get session token back
|
|
633
|
+
// License key is transmitted once via POST (not visible in page source)
|
|
634
|
+
async _secureLicenseExchange() {
|
|
635
|
+
try {
|
|
636
|
+
const endpoint = this.config.configEndpoint;
|
|
637
|
+
const body = this.config.siteId
|
|
638
|
+
? { siteId: this.config.siteId }
|
|
639
|
+
: { licenseKey: this.config._licenseKey || this.config.licenseKey };
|
|
640
|
+
const res = await fetch(endpoint, {
|
|
641
|
+
method: 'POST',
|
|
642
|
+
headers: { 'Content-Type': 'application/json' },
|
|
643
|
+
body: JSON.stringify(body)
|
|
644
|
+
});
|
|
645
|
+
if (res.ok) {
|
|
646
|
+
const data = await res.json();
|
|
647
|
+
this._licenseVerified = { tier: data.tier, valid: true, sessionToken: data.sessionToken };
|
|
648
|
+
delete this.config._licenseKey;
|
|
649
|
+
if (data.expiresIn) {
|
|
650
|
+
if (this._tokenRefreshTimer) clearTimeout(this._tokenRefreshTimer);
|
|
651
|
+
this._tokenRefreshTimer = setTimeout(() => {
|
|
652
|
+
this._secureLicenseExchange();
|
|
653
|
+
}, Math.floor(data.expiresIn * 800));
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
this._licenseVerified = { tier: 'free', valid: false, error: 'Token exchange failed' };
|
|
657
|
+
}
|
|
658
|
+
} catch (e) {
|
|
659
|
+
this._licenseVerified = { tier: this.config.subscriptionTier || 'free', valid: false, error: 'Offline' };
|
|
660
|
+
}
|
|
588
661
|
}
|
|
589
662
|
|
|
590
663
|
// Store fingerprints for all discovered actions (self-healing)
|
|
@@ -642,10 +715,42 @@
|
|
|
642
715
|
return result;
|
|
643
716
|
}
|
|
644
717
|
|
|
718
|
+
_getLicenseApiBase() {
|
|
719
|
+
const c = this.config;
|
|
720
|
+
if (c.apiBaseUrl) return String(c.apiBaseUrl).replace(/\/$/, '');
|
|
721
|
+
if (typeof global.location !== 'undefined' && location && location.origin) return location.origin;
|
|
722
|
+
return LICENSING_SERVER;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Report action execution to WAB (fills platform analytics in admin).
|
|
727
|
+
*/
|
|
728
|
+
_maybeReportUsage(actionName, triggerType, success) {
|
|
729
|
+
try {
|
|
730
|
+
const sessionToken = this._licenseVerified && this._licenseVerified.sessionToken;
|
|
731
|
+
if (!sessionToken || (this.config.features && this.config.features.reportUsage === false)) return;
|
|
732
|
+
const base = this._getLicenseApiBase();
|
|
733
|
+
const payload = JSON.stringify({
|
|
734
|
+
sessionToken,
|
|
735
|
+
actionName,
|
|
736
|
+
triggerType: triggerType || 'unknown',
|
|
737
|
+
success: success !== false
|
|
738
|
+
});
|
|
739
|
+
fetch(`${base}/api/license/track`, {
|
|
740
|
+
method: 'POST',
|
|
741
|
+
headers: { 'Content-Type': 'application/json' },
|
|
742
|
+
body: payload,
|
|
743
|
+
keepalive: true,
|
|
744
|
+
mode: 'cors',
|
|
745
|
+
credentials: 'omit'
|
|
746
|
+
}).catch(function () {});
|
|
747
|
+
} catch (e) { /* ignore */ }
|
|
748
|
+
}
|
|
749
|
+
|
|
645
750
|
// ── License Verification ────────────────────────────────────────────
|
|
646
751
|
async _verifyLicense() {
|
|
647
752
|
try {
|
|
648
|
-
const res = await fetch(`${
|
|
753
|
+
const res = await fetch(`${this._getLicenseApiBase()}/api/license/verify`, {
|
|
649
754
|
method: 'POST',
|
|
650
755
|
headers: { 'Content-Type': 'application/json' },
|
|
651
756
|
body: JSON.stringify({
|
|
@@ -903,12 +1008,14 @@
|
|
|
903
1008
|
|
|
904
1009
|
this.events.emit('action:after', { action: actionName, result });
|
|
905
1010
|
this.logger.log('execute_result', { action: actionName, success: result.success }, 'detailed');
|
|
1011
|
+
this._maybeReportUsage(actionName, action.trigger, !!(result && result.success !== false));
|
|
906
1012
|
return result;
|
|
907
1013
|
|
|
908
1014
|
} catch (err) {
|
|
909
1015
|
const error = { success: false, error: err.message };
|
|
910
1016
|
this.events.emit('error', { action: actionName, error: err.message });
|
|
911
1017
|
this.logger.log('execute_error', { action: actionName, error: err.message });
|
|
1018
|
+
this._maybeReportUsage(actionName, action.trigger, false);
|
|
912
1019
|
return error;
|
|
913
1020
|
}
|
|
914
1021
|
}
|
|
@@ -1090,6 +1197,50 @@
|
|
|
1090
1197
|
this.events.emit('action:unregistered', { name });
|
|
1091
1198
|
}
|
|
1092
1199
|
|
|
1200
|
+
// ── Protocol Discovery ──────────────────────────────────────────────
|
|
1201
|
+
discover() {
|
|
1202
|
+
return {
|
|
1203
|
+
wab_version: PROTOCOL_VERSION,
|
|
1204
|
+
runtime_version: VERSION,
|
|
1205
|
+
discovery_document: this._discoveryDoc,
|
|
1206
|
+
page: this.getPageInfo(),
|
|
1207
|
+
actions: this.getActions(),
|
|
1208
|
+
fairness: this._discoveryDoc ? this._discoveryDoc.fairness : null,
|
|
1209
|
+
transport: {
|
|
1210
|
+
js_global: { enabled: true, interface: 'window.AICommands' },
|
|
1211
|
+
bidi: { enabled: true, interface: 'window.__wab_bidi' },
|
|
1212
|
+
noscript: { enabled: !!(this.config.siteId) }
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
subscribe(eventName, callback) {
|
|
1218
|
+
if (typeof callback !== 'function') return { success: false, error: 'callback must be a function' };
|
|
1219
|
+
var id = 'sub_' + (++this.security._commandCounter);
|
|
1220
|
+
this._eventSubscriptions.set(id, { event: eventName, callback });
|
|
1221
|
+
this.events.on(eventName, callback);
|
|
1222
|
+
return { success: true, subscriptionId: id, event: eventName };
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
unsubscribe(subscriptionId) {
|
|
1226
|
+
var sub = this._eventSubscriptions.get(subscriptionId);
|
|
1227
|
+
if (!sub) return { success: false, error: 'Subscription not found' };
|
|
1228
|
+
this.events.off(sub.event, sub.callback);
|
|
1229
|
+
this._eventSubscriptions.delete(subscriptionId);
|
|
1230
|
+
return { success: true };
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
ping() {
|
|
1234
|
+
return {
|
|
1235
|
+
pong: true,
|
|
1236
|
+
version: VERSION,
|
|
1237
|
+
protocol: PROTOCOL_VERSION,
|
|
1238
|
+
timestamp: Date.now(),
|
|
1239
|
+
ready: this._ready,
|
|
1240
|
+
locked: this.security.isLocked
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1093
1244
|
// ── Discovery / Info ────────────────────────────────────────────────
|
|
1094
1245
|
getActions(category) {
|
|
1095
1246
|
if (category) return this.registry.getByCategory(category);
|
|
@@ -1167,8 +1318,13 @@
|
|
|
1167
1318
|
toJSON() {
|
|
1168
1319
|
return {
|
|
1169
1320
|
version: VERSION,
|
|
1321
|
+
protocol: PROTOCOL_VERSION,
|
|
1170
1322
|
page: this.getPageInfo(),
|
|
1171
|
-
actions: this.getActions()
|
|
1323
|
+
actions: this.getActions(),
|
|
1324
|
+
discovery: this._discoveryDoc ? {
|
|
1325
|
+
provider: this._discoveryDoc.provider,
|
|
1326
|
+
fairness: this._discoveryDoc.fairness
|
|
1327
|
+
} : null
|
|
1172
1328
|
};
|
|
1173
1329
|
}
|
|
1174
1330
|
|
|
@@ -1178,6 +1334,7 @@
|
|
|
1178
1334
|
return {
|
|
1179
1335
|
type: 'wab:context',
|
|
1180
1336
|
version: VERSION,
|
|
1337
|
+
protocol: PROTOCOL_VERSION,
|
|
1181
1338
|
context: {
|
|
1182
1339
|
url: location.href,
|
|
1183
1340
|
title: document.title,
|
|
@@ -1192,7 +1349,9 @@
|
|
|
1192
1349
|
})),
|
|
1193
1350
|
permissions: this._getEffectivePermissions(),
|
|
1194
1351
|
tier: this.getEffectiveTier()
|
|
1195
|
-
}
|
|
1352
|
+
},
|
|
1353
|
+
discovery: this._discoveryDoc || null,
|
|
1354
|
+
fairness: this._discoveryDoc ? this._discoveryDoc.fairness : null
|
|
1196
1355
|
};
|
|
1197
1356
|
}
|
|
1198
1357
|
|
|
@@ -1234,6 +1393,24 @@
|
|
|
1234
1393
|
case 'wab.getPageInfo':
|
|
1235
1394
|
return { ...responseBase, result: this.getPageInfo() };
|
|
1236
1395
|
|
|
1396
|
+
case 'wab.discover':
|
|
1397
|
+
return { ...responseBase, result: this.discover() };
|
|
1398
|
+
|
|
1399
|
+
case 'wab.authenticate':
|
|
1400
|
+
if (!command.params?.key) {
|
|
1401
|
+
return { id: command.id, error: { code: 'invalid argument', message: 'Agent key required' } };
|
|
1402
|
+
}
|
|
1403
|
+
return { ...responseBase, result: this.authenticate(command.params.key, command.params.meta || {}) };
|
|
1404
|
+
|
|
1405
|
+
case 'wab.subscribe':
|
|
1406
|
+
if (!command.params?.event) {
|
|
1407
|
+
return { id: command.id, error: { code: 'invalid argument', message: 'Event name required' } };
|
|
1408
|
+
}
|
|
1409
|
+
return { ...responseBase, result: this.subscribe(command.params.event, command.params.callback || function() {}) };
|
|
1410
|
+
|
|
1411
|
+
case 'wab.ping':
|
|
1412
|
+
return { ...responseBase, result: this.ping() };
|
|
1413
|
+
|
|
1237
1414
|
default:
|
|
1238
1415
|
return { id: command.id, error: { code: 'unknown command', message: `Unknown method: ${command.method}` } };
|
|
1239
1416
|
}
|
|
@@ -1261,18 +1438,72 @@
|
|
|
1261
1438
|
global.AICommands = bridge;
|
|
1262
1439
|
global.WebAgentBridge = WebAgentBridge;
|
|
1263
1440
|
|
|
1441
|
+
// WAB Protocol interface
|
|
1442
|
+
global.__wab_protocol = {
|
|
1443
|
+
version: VERSION,
|
|
1444
|
+
protocol: PROTOCOL_VERSION,
|
|
1445
|
+
discover: () => bridge.discover(),
|
|
1446
|
+
ping: () => bridge.ping()
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1264
1449
|
// WebDriver BiDi compatibility: expose via __wab_bidi channel
|
|
1265
1450
|
global.__wab_bidi = {
|
|
1266
1451
|
version: VERSION,
|
|
1452
|
+
protocol: PROTOCOL_VERSION,
|
|
1267
1453
|
send: async (command) => bridge.executeBiDi(command),
|
|
1268
1454
|
getContext: () => bridge.toBiDi()
|
|
1269
1455
|
};
|
|
1270
1456
|
|
|
1457
|
+
// Inject NoJS fallback elements for pages that might disable JS later or
|
|
1458
|
+
// for hybrid environments (SSR, partial hydration, headless crawlers).
|
|
1459
|
+
if (config.siteId) {
|
|
1460
|
+
injectNoScriptFallback(config);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1271
1463
|
if (typeof CustomEvent !== 'undefined') {
|
|
1272
1464
|
document.dispatchEvent(new CustomEvent('wab:ready', { detail: { version: VERSION } }));
|
|
1273
1465
|
}
|
|
1274
1466
|
}
|
|
1275
1467
|
|
|
1468
|
+
function injectNoScriptFallback(config) {
|
|
1469
|
+
try {
|
|
1470
|
+
var siteId = config.siteId;
|
|
1471
|
+
var base = config.apiBaseUrl || '';
|
|
1472
|
+
|
|
1473
|
+
// Add <noscript> block if not already present
|
|
1474
|
+
if (!document.querySelector('noscript [src*="noscript/pixel/' + siteId + '"]')) {
|
|
1475
|
+
var ns = document.createElement('noscript');
|
|
1476
|
+
ns.innerHTML =
|
|
1477
|
+
'<link rel="stylesheet" href="' + base + '/api/noscript/css/' + siteId + '">' +
|
|
1478
|
+
'<img src="' + base + '/api/noscript/pixel/' + siteId + '?action=pageview&t=noscript" width="1" height="1" alt="" style="position:absolute;opacity:0">';
|
|
1479
|
+
document.body.appendChild(ns);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// Expose noscript endpoints on the bridge for AI agents
|
|
1483
|
+
global.__wab_noscript = {
|
|
1484
|
+
pixel: base + '/api/noscript/pixel/' + siteId,
|
|
1485
|
+
css: base + '/api/noscript/css/' + siteId,
|
|
1486
|
+
bridge: base + '/api/noscript/bridge/' + siteId,
|
|
1487
|
+
embed: base + '/api/noscript/embed/' + siteId,
|
|
1488
|
+
serverTrack: base + '/api/noscript/server-track',
|
|
1489
|
+
status: base + '/api/noscript/status/' + siteId
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1492
|
+
// Add meta tags for crawlers/agents that read HTML
|
|
1493
|
+
if (!document.querySelector('meta[name="wab:noscript"]')) {
|
|
1494
|
+
var meta1 = document.createElement('meta');
|
|
1495
|
+
meta1.name = 'wab:noscript';
|
|
1496
|
+
meta1.content = 'true';
|
|
1497
|
+
document.head.appendChild(meta1);
|
|
1498
|
+
|
|
1499
|
+
var meta2 = document.createElement('meta');
|
|
1500
|
+
meta2.name = 'wab:bridge';
|
|
1501
|
+
meta2.content = base + '/api/noscript/bridge/' + siteId;
|
|
1502
|
+
document.head.appendChild(meta2);
|
|
1503
|
+
}
|
|
1504
|
+
} catch (_) {}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1276
1507
|
if (document.readyState === 'loading') {
|
|
1277
1508
|
document.addEventListener('DOMContentLoaded', autoInit);
|
|
1278
1509
|
} else {
|
package/sdk/index.js
CHANGED
|
@@ -149,6 +149,30 @@ class WABAgent {
|
|
|
149
149
|
return results;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Get the WAB discovery document for the current page.
|
|
154
|
+
* @returns {Promise<object>}
|
|
155
|
+
*/
|
|
156
|
+
async discover() {
|
|
157
|
+
if (this.useBiDi) {
|
|
158
|
+
const result = await this._bidiSend('wab.discover');
|
|
159
|
+
return result.result || result;
|
|
160
|
+
}
|
|
161
|
+
return this.page.evaluate(() => window.AICommands.discover());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Ping the bridge for a health check.
|
|
166
|
+
* @returns {Promise<object>}
|
|
167
|
+
*/
|
|
168
|
+
async ping() {
|
|
169
|
+
if (this.useBiDi) {
|
|
170
|
+
const result = await this._bidiSend('wab.ping');
|
|
171
|
+
return result.result || result;
|
|
172
|
+
}
|
|
173
|
+
return this.page.evaluate(() => window.AICommands.ping());
|
|
174
|
+
}
|
|
175
|
+
|
|
152
176
|
/**
|
|
153
177
|
* Get BiDi context (only available when useBiDi is true).
|
|
154
178
|
* @returns {Promise<object>}
|
|
@@ -157,6 +181,18 @@ class WABAgent {
|
|
|
157
181
|
return this.page.evaluate(() => window.__wab_bidi.getContext());
|
|
158
182
|
}
|
|
159
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Get the WAB protocol interface data.
|
|
186
|
+
* @returns {Promise<object>}
|
|
187
|
+
*/
|
|
188
|
+
async getProtocolInfo() {
|
|
189
|
+
return this.page.evaluate(() => window.__wab_protocol ? {
|
|
190
|
+
version: window.__wab_protocol.version,
|
|
191
|
+
protocol: window.__wab_protocol.protocol,
|
|
192
|
+
discovery: window.__wab_protocol.discover()
|
|
193
|
+
} : null);
|
|
194
|
+
}
|
|
195
|
+
|
|
160
196
|
/** @private */
|
|
161
197
|
async _bidiSend(method, params = {}) {
|
|
162
198
|
const cmd = { id: ++this._biDiId, method, params };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central JWT and startup secret checks.
|
|
3
|
+
* User tokens and admin tokens use different secrets and audiences in production.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const jwt = require('jsonwebtoken');
|
|
7
|
+
|
|
8
|
+
const JWT_ISSUER = 'wab';
|
|
9
|
+
const JWT_AUD_USER = 'wab:user';
|
|
10
|
+
const JWT_AUD_ADMIN = 'wab:admin';
|
|
11
|
+
|
|
12
|
+
const jwtVerifyUser = { issuer: JWT_ISSUER, audience: JWT_AUD_USER };
|
|
13
|
+
const jwtVerifyAdmin = { issuer: JWT_ISSUER, audience: JWT_AUD_ADMIN };
|
|
14
|
+
|
|
15
|
+
function isTest() {
|
|
16
|
+
return process.env.NODE_ENV === 'test';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isProd() {
|
|
20
|
+
return process.env.NODE_ENV === 'production';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function assertSecretsAtStartup() {
|
|
24
|
+
if (isTest()) return;
|
|
25
|
+
if (isProd()) {
|
|
26
|
+
if (!process.env.JWT_SECRET) {
|
|
27
|
+
throw new Error('FATAL: JWT_SECRET is required in production');
|
|
28
|
+
}
|
|
29
|
+
if (!process.env.JWT_SECRET_ADMIN) {
|
|
30
|
+
throw new Error('FATAL: JWT_SECRET_ADMIN is required in production');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getJwtUserSecret() {
|
|
36
|
+
if (isTest()) {
|
|
37
|
+
return process.env.JWT_SECRET || 'test-secret-key-for-testing';
|
|
38
|
+
}
|
|
39
|
+
if (isProd()) {
|
|
40
|
+
return process.env.JWT_SECRET;
|
|
41
|
+
}
|
|
42
|
+
return process.env.JWT_SECRET || 'dev-user-secret-change-in-development';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getJwtAdminSecret() {
|
|
46
|
+
if (isTest()) {
|
|
47
|
+
return process.env.JWT_SECRET_ADMIN || process.env.JWT_SECRET || 'test-secret-key-for-testing-admin';
|
|
48
|
+
}
|
|
49
|
+
if (isProd()) {
|
|
50
|
+
return process.env.JWT_SECRET_ADMIN;
|
|
51
|
+
}
|
|
52
|
+
return process.env.JWT_SECRET_ADMIN || process.env.JWT_SECRET || 'dev-admin-secret-change-in-development';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function signUserToken(payload, options = {}) {
|
|
56
|
+
return jwt.sign(
|
|
57
|
+
{ ...payload },
|
|
58
|
+
getJwtUserSecret(),
|
|
59
|
+
{ expiresIn: options.expiresIn || '7d', issuer: JWT_ISSUER, audience: JWT_AUD_USER }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function signAdminToken(payload, options = {}) {
|
|
64
|
+
return jwt.sign(
|
|
65
|
+
{ ...payload },
|
|
66
|
+
getJwtAdminSecret(),
|
|
67
|
+
{ expiresIn: options.expiresIn || '12h', issuer: JWT_ISSUER, audience: JWT_AUD_ADMIN }
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function verifyUserToken(token) {
|
|
72
|
+
return jwt.verify(token, getJwtUserSecret(), jwtVerifyUser);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function verifyAdminToken(token) {
|
|
76
|
+
return jwt.verify(token, getJwtAdminSecret(), jwtVerifyAdmin);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
assertSecretsAtStartup,
|
|
81
|
+
getJwtUserSecret,
|
|
82
|
+
getJwtAdminSecret,
|
|
83
|
+
signUserToken,
|
|
84
|
+
signAdminToken,
|
|
85
|
+
verifyUserToken,
|
|
86
|
+
verifyAdminToken,
|
|
87
|
+
JWT_ISSUER,
|
|
88
|
+
JWT_AUD_USER,
|
|
89
|
+
JWT_AUD_ADMIN,
|
|
90
|
+
jwtVerifyUser,
|
|
91
|
+
jwtVerifyAdmin
|
|
92
|
+
};
|