web-agent-bridge 3.3.0 → 3.4.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 +12 -0
- package/README.ar.md +18 -0
- package/README.md +198 -1664
- package/bin/wab-init.js +223 -0
- package/examples/azure-dns-wab.js +83 -0
- package/examples/cloudflare-wab-dns.js +121 -0
- package/examples/cpanel-wab-dns.js +114 -0
- 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/plesk-wab-dns.js +103 -0
- package/examples/route53-wab-dns.js +144 -0
- package/examples/safe-mode-agent.js +96 -0
- package/examples/wab-sign.js +74 -0
- package/examples/wab-verify.js +60 -0
- package/package.json +5 -5
- package/public/.well-known/wab.json +28 -0
- package/public/activate.html +368 -0
- package/public/adoption-metrics.html +188 -0
- package/public/api.html +1 -1
- package/public/azure-dns-integration.html +289 -0
- package/public/cloudflare-integration.html +380 -0
- package/public/cpanel-integration.html +398 -0
- package/public/css/styles.css +28 -0
- package/public/dashboard.html +1 -0
- package/public/dns.html +101 -172
- package/public/docs.html +1 -0
- package/public/gcp-dns-integration.html +318 -0
- package/public/growth.html +4 -2
- package/public/index.html +227 -31
- package/public/integrations.html +1 -1
- package/public/js/activate.js +145 -0
- package/public/js/auth-nav.js +34 -0
- package/public/js/dns.js +438 -0
- package/public/openapi.json +89 -0
- package/public/plesk-integration.html +375 -0
- package/public/premium.html +1 -1
- package/public/provider-onboarding.html +172 -0
- package/public/provider-sandbox.html +134 -0
- package/public/providers.html +359 -0
- package/public/registrar-integrations.html +141 -0
- package/public/robots.txt +12 -0
- package/public/route53-integration.html +531 -0
- package/public/shieldqr.html +231 -0
- package/public/sitemap.xml +6 -0
- package/public/wab-trust.html +200 -0
- package/public/wab-vs-protocols.html +210 -0
- package/public/whitepaper.html +449 -0
- package/sdk/auto-discovery.js +288 -0
- package/sdk/governance.js +262 -0
- package/sdk/index.js +13 -0
- package/sdk/package.json +2 -2
- package/sdk/safe-mode.js +221 -0
- package/server/index.js +144 -5
- 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/models/adapters/mysql.js +1 -1
- package/server/models/adapters/postgresql.js +1 -1
- package/server/models/db.js +60 -1
- package/server/routes/admin-plans.js +76 -0
- package/server/routes/admin-premium.js +4 -2
- package/server/routes/admin-shieldqr.js +90 -0
- package/server/routes/admin-trust-monitor.js +83 -0
- package/server/routes/admin.js +289 -1
- package/server/routes/billing.js +16 -4
- package/server/routes/discovery.js +1933 -2
- package/server/routes/governance.js +208 -0
- package/server/routes/plans.js +33 -0
- package/server/routes/providers.js +650 -0
- package/server/routes/shieldqr.js +88 -0
- package/server/services/email.js +29 -0
- package/server/services/governance.js +466 -0
- package/server/services/plans.js +214 -0
- package/server/services/premium.js +1 -1
- package/server/services/provider-clients.js +740 -0
- package/server/services/shieldqr.js +322 -0
- package/server/services/ssl-inspector.js +42 -0
- package/server/services/ssl-monitor.js +167 -0
- package/server/services/stripe.js +18 -5
- package/server/services/vision.js +1 -1
- package/server/services/wab-crypto.js +178 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plans Service — DB-backed plan & feature management.
|
|
3
|
+
*
|
|
4
|
+
* Source of truth for: pricing, feature matrix, Stripe price IDs, public
|
|
5
|
+
* pricing page contents and feature-gate decisions.
|
|
6
|
+
*
|
|
7
|
+
* Tables (created by migrations/008_plans.sql):
|
|
8
|
+
* - plans
|
|
9
|
+
* - feature_catalog
|
|
10
|
+
*
|
|
11
|
+
* Falls back gracefully if the migration has not yet been applied (e.g.
|
|
12
|
+
* during boot races or in unit tests that touch a fresh DB) by returning
|
|
13
|
+
* empty arrays — callers stay alive and the legacy hard-coded PLANS object
|
|
14
|
+
* in server/config/plans.js continues to work.
|
|
15
|
+
*/
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const Database = require('better-sqlite3');
|
|
20
|
+
|
|
21
|
+
let _db = null;
|
|
22
|
+
function db() {
|
|
23
|
+
if (_db) return _db;
|
|
24
|
+
const DATA_DIR = process.env.NODE_ENV === 'test'
|
|
25
|
+
? path.join(__dirname, '..', '..', 'data-test')
|
|
26
|
+
: (process.env.DATA_DIR || path.join(__dirname, '..', '..', 'data'));
|
|
27
|
+
const dbFile = process.env.NODE_ENV === 'test' ? 'wab-test.db' : 'wab.db';
|
|
28
|
+
_db = new Database(path.join(DATA_DIR, dbFile));
|
|
29
|
+
return _db;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function tableExists(name) {
|
|
33
|
+
try {
|
|
34
|
+
return !!db().prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(name);
|
|
35
|
+
} catch { return false; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function safeJson(s, fallback) {
|
|
39
|
+
if (s == null) return fallback;
|
|
40
|
+
try { return JSON.parse(s); } catch { return fallback; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function rowToPlan(row) {
|
|
44
|
+
if (!row) return null;
|
|
45
|
+
return {
|
|
46
|
+
id: row.id,
|
|
47
|
+
name: row.name,
|
|
48
|
+
tagline: row.tagline,
|
|
49
|
+
description: row.description,
|
|
50
|
+
price_cents: row.price_cents,
|
|
51
|
+
currency: row.currency,
|
|
52
|
+
billing_period: row.billing_period,
|
|
53
|
+
stripe_price_id: row.stripe_price_id,
|
|
54
|
+
cta_type: row.cta_type,
|
|
55
|
+
cta_label: row.cta_label,
|
|
56
|
+
cta_url: row.cta_url,
|
|
57
|
+
highlight: !!row.highlight,
|
|
58
|
+
is_public: !!row.is_public,
|
|
59
|
+
is_archived: !!row.is_archived,
|
|
60
|
+
sort_order: row.sort_order,
|
|
61
|
+
features: safeJson(row.features_json, {}),
|
|
62
|
+
limits: safeJson(row.limits_json, {}),
|
|
63
|
+
created_at: row.created_at,
|
|
64
|
+
updated_at: row.updated_at,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function listPlans({ includeArchived = false, publicOnly = false } = {}) {
|
|
69
|
+
if (!tableExists('plans')) return [];
|
|
70
|
+
const where = [];
|
|
71
|
+
if (!includeArchived) where.push('is_archived = 0');
|
|
72
|
+
if (publicOnly) where.push('is_public = 1');
|
|
73
|
+
const sql = `SELECT * FROM plans ${where.length ? 'WHERE ' + where.join(' AND ') : ''}
|
|
74
|
+
ORDER BY sort_order ASC, price_cents ASC, id ASC`;
|
|
75
|
+
return db().prepare(sql).all().map(rowToPlan);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getPlan(id) {
|
|
79
|
+
if (!tableExists('plans')) return null;
|
|
80
|
+
const row = db().prepare('SELECT * FROM plans WHERE id = ?').get(id);
|
|
81
|
+
return rowToPlan(row);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function listFeatures() {
|
|
85
|
+
if (!tableExists('feature_catalog')) return [];
|
|
86
|
+
return db().prepare('SELECT * FROM feature_catalog ORDER BY sort_order ASC, label ASC').all()
|
|
87
|
+
.map(r => ({
|
|
88
|
+
key: r.feature_key,
|
|
89
|
+
label: r.label,
|
|
90
|
+
description: r.description,
|
|
91
|
+
category: r.category,
|
|
92
|
+
is_open_source: !!r.is_open_source,
|
|
93
|
+
sort_order: r.sort_order,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const ALLOWED_FIELDS = new Set([
|
|
98
|
+
'name','tagline','description','price_cents','currency','billing_period',
|
|
99
|
+
'stripe_price_id','cta_type','cta_label','cta_url','highlight','is_public',
|
|
100
|
+
'is_archived','sort_order','features','limits',
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const VALID_BILLING = new Set(['month','year','one_time','custom']);
|
|
104
|
+
const VALID_CTA = new Set(['checkout','register','contact','external']);
|
|
105
|
+
|
|
106
|
+
function validatePatch(patch) {
|
|
107
|
+
const errs = [];
|
|
108
|
+
if (patch.billing_period != null && !VALID_BILLING.has(patch.billing_period)) errs.push('invalid billing_period');
|
|
109
|
+
if (patch.cta_type != null && !VALID_CTA.has(patch.cta_type)) errs.push('invalid cta_type');
|
|
110
|
+
if (patch.price_cents != null && (!Number.isInteger(patch.price_cents) || patch.price_cents < 0)) errs.push('price_cents must be a non-negative integer');
|
|
111
|
+
if (patch.currency != null && !/^[A-Z]{3}$/.test(patch.currency)) errs.push('currency must be a 3-letter code (e.g. EUR)');
|
|
112
|
+
if (patch.features != null && (typeof patch.features !== 'object' || Array.isArray(patch.features))) errs.push('features must be an object');
|
|
113
|
+
if (patch.limits != null && (typeof patch.limits !== 'object' || Array.isArray(patch.limits))) errs.push('limits must be an object');
|
|
114
|
+
return errs;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function applyPatchToColumns(patch) {
|
|
118
|
+
const cols = {};
|
|
119
|
+
for (const k of Object.keys(patch)) {
|
|
120
|
+
if (!ALLOWED_FIELDS.has(k)) continue;
|
|
121
|
+
if (k === 'features') cols.features_json = JSON.stringify(patch.features || {});
|
|
122
|
+
else if (k === 'limits') cols.limits_json = JSON.stringify(patch.limits || {});
|
|
123
|
+
else if (k === 'highlight' || k === 'is_public' || k === 'is_archived')
|
|
124
|
+
cols[k] = patch[k] ? 1 : 0;
|
|
125
|
+
else cols[k] = patch[k];
|
|
126
|
+
}
|
|
127
|
+
return cols;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createPlan(input) {
|
|
131
|
+
if (!tableExists('plans')) throw new Error('plans table missing — run migrations');
|
|
132
|
+
if (!input || !input.id || !/^[a-z0-9][a-z0-9_-]{1,40}$/.test(input.id)) {
|
|
133
|
+
throw new Error('id is required (lowercase slug, 2–40 chars)');
|
|
134
|
+
}
|
|
135
|
+
if (!input.name) throw new Error('name is required');
|
|
136
|
+
const errs = validatePatch(input);
|
|
137
|
+
if (errs.length) throw new Error(errs.join('; '));
|
|
138
|
+
|
|
139
|
+
const existing = getPlan(input.id);
|
|
140
|
+
if (existing) throw new Error('plan already exists');
|
|
141
|
+
|
|
142
|
+
const cols = applyPatchToColumns({
|
|
143
|
+
name: input.name,
|
|
144
|
+
tagline: input.tagline || null,
|
|
145
|
+
description: input.description || null,
|
|
146
|
+
price_cents: input.price_cents == null ? 0 : input.price_cents,
|
|
147
|
+
currency: input.currency || 'EUR',
|
|
148
|
+
billing_period: input.billing_period || 'month',
|
|
149
|
+
stripe_price_id: input.stripe_price_id || null,
|
|
150
|
+
cta_type: input.cta_type || 'checkout',
|
|
151
|
+
cta_label: input.cta_label || null,
|
|
152
|
+
cta_url: input.cta_url || null,
|
|
153
|
+
highlight: !!input.highlight,
|
|
154
|
+
is_public: input.is_public === false ? false : true,
|
|
155
|
+
is_archived: false,
|
|
156
|
+
sort_order: input.sort_order == null ? 100 : input.sort_order,
|
|
157
|
+
features: input.features || {},
|
|
158
|
+
limits: input.limits || {},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const fields = ['id', ...Object.keys(cols)];
|
|
162
|
+
const placeholders = fields.map(() => '?').join(',');
|
|
163
|
+
db().prepare(`INSERT INTO plans (${fields.join(',')}) VALUES (${placeholders})`)
|
|
164
|
+
.run(input.id, ...Object.values(cols));
|
|
165
|
+
return getPlan(input.id);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function updatePlan(id, patch) {
|
|
169
|
+
if (!tableExists('plans')) throw new Error('plans table missing — run migrations');
|
|
170
|
+
const existing = getPlan(id);
|
|
171
|
+
if (!existing) throw new Error('plan not found');
|
|
172
|
+
const errs = validatePatch(patch);
|
|
173
|
+
if (errs.length) throw new Error(errs.join('; '));
|
|
174
|
+
|
|
175
|
+
const cols = applyPatchToColumns(patch);
|
|
176
|
+
if (!Object.keys(cols).length) return existing;
|
|
177
|
+
|
|
178
|
+
cols.updated_at = new Date().toISOString().replace('T',' ').slice(0,19);
|
|
179
|
+
const sets = Object.keys(cols).map(k => `${k} = ?`).join(', ');
|
|
180
|
+
db().prepare(`UPDATE plans SET ${sets} WHERE id = ?`).run(...Object.values(cols), id);
|
|
181
|
+
return getPlan(id);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function setPlanFeature(id, featureKey, included) {
|
|
185
|
+
const plan = getPlan(id);
|
|
186
|
+
if (!plan) throw new Error('plan not found');
|
|
187
|
+
const features = Object.assign({}, plan.features);
|
|
188
|
+
if (included) features[featureKey] = true;
|
|
189
|
+
else delete features[featureKey];
|
|
190
|
+
return updatePlan(id, { features });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function deletePlan(id) {
|
|
194
|
+
if (!tableExists('plans')) throw new Error('plans table missing — run migrations');
|
|
195
|
+
// Soft-delete by archiving so legacy tier references stay resolvable.
|
|
196
|
+
return updatePlan(id, { is_archived: true, is_public: false });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function planHasFeature(id, featureKey) {
|
|
200
|
+
const p = getPlan(id);
|
|
201
|
+
if (!p) return false;
|
|
202
|
+
return !!p.features[featureKey];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = {
|
|
206
|
+
listPlans,
|
|
207
|
+
getPlan,
|
|
208
|
+
listFeatures,
|
|
209
|
+
createPlan,
|
|
210
|
+
updatePlan,
|
|
211
|
+
setPlanFeature,
|
|
212
|
+
deletePlan,
|
|
213
|
+
planHasFeature,
|
|
214
|
+
};
|