s3db.js 13.6.0 → 14.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +139 -43
  2. package/dist/s3db.cjs +72425 -38970
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72177 -38764
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +94 -49
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/opengraph-helper.js +116 -0
  34. package/src/plugins/api/concerns/route-context.js +601 -0
  35. package/src/plugins/api/concerns/state-machine.js +288 -0
  36. package/src/plugins/api/index.js +180 -41
  37. package/src/plugins/api/routes/auth-routes.js +198 -30
  38. package/src/plugins/api/routes/resource-routes.js +19 -4
  39. package/src/plugins/api/server/health-manager.class.js +163 -0
  40. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  41. package/src/plugins/api/server/router.class.js +472 -0
  42. package/src/plugins/api/server.js +280 -1303
  43. package/src/plugins/api/utils/custom-routes.js +17 -5
  44. package/src/plugins/api/utils/guards.js +76 -17
  45. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  46. package/src/plugins/api/utils/openapi-generator.js +7 -6
  47. package/src/plugins/api/utils/template-engine.js +77 -3
  48. package/src/plugins/audit.plugin.js +30 -8
  49. package/src/plugins/backup.plugin.js +110 -14
  50. package/src/plugins/cache/cache.class.js +22 -5
  51. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  52. package/src/plugins/cache/memory-cache.class.js +211 -57
  53. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  54. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  55. package/src/plugins/cache/redis-cache.class.js +552 -0
  56. package/src/plugins/cache/s3-cache.class.js +17 -8
  57. package/src/plugins/cache.plugin.js +176 -61
  58. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  59. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  60. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  62. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  66. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  67. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  68. package/src/plugins/cloud-inventory/index.js +29 -8
  69. package/src/plugins/cloud-inventory/registry.js +64 -42
  70. package/src/plugins/cloud-inventory.plugin.js +240 -138
  71. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  72. package/src/plugins/concerns/resource-names.js +100 -0
  73. package/src/plugins/consumers/index.js +10 -2
  74. package/src/plugins/consumers/sqs-consumer.js +12 -2
  75. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  76. package/src/plugins/cookie-farm.errors.js +73 -0
  77. package/src/plugins/cookie-farm.plugin.js +869 -0
  78. package/src/plugins/costs.plugin.js +7 -1
  79. package/src/plugins/eventual-consistency/analytics.js +94 -19
  80. package/src/plugins/eventual-consistency/config.js +15 -7
  81. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  82. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  83. package/src/plugins/eventual-consistency/helpers.js +39 -14
  84. package/src/plugins/eventual-consistency/install.js +21 -2
  85. package/src/plugins/eventual-consistency/utils.js +32 -10
  86. package/src/plugins/fulltext.plugin.js +38 -11
  87. package/src/plugins/geo.plugin.js +61 -9
  88. package/src/plugins/identity/concerns/config.js +61 -0
  89. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  90. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  91. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  92. package/src/plugins/identity/concerns/token-generator.js +29 -4
  93. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  94. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  95. package/src/plugins/identity/drivers/index.js +18 -0
  96. package/src/plugins/identity/drivers/password-driver.js +122 -0
  97. package/src/plugins/identity/email-service.js +17 -2
  98. package/src/plugins/identity/index.js +413 -69
  99. package/src/plugins/identity/oauth2-server.js +413 -30
  100. package/src/plugins/identity/oidc-discovery.js +16 -8
  101. package/src/plugins/identity/rsa-keys.js +115 -35
  102. package/src/plugins/identity/server.js +166 -45
  103. package/src/plugins/identity/session-manager.js +53 -7
  104. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  105. package/src/plugins/identity/ui/routes.js +363 -255
  106. package/src/plugins/importer/index.js +153 -20
  107. package/src/plugins/index.js +9 -2
  108. package/src/plugins/kubernetes-inventory/index.js +6 -0
  109. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  110. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  111. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  112. package/src/plugins/metrics.plugin.js +64 -16
  113. package/src/plugins/ml/base-model.class.js +25 -15
  114. package/src/plugins/ml/regression-model.class.js +1 -1
  115. package/src/plugins/ml.errors.js +57 -25
  116. package/src/plugins/ml.plugin.js +28 -4
  117. package/src/plugins/namespace.js +210 -0
  118. package/src/plugins/plugin.class.js +180 -8
  119. package/src/plugins/puppeteer/console-monitor.js +729 -0
  120. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  121. package/src/plugins/puppeteer/network-monitor.js +816 -0
  122. package/src/plugins/puppeteer/performance-manager.js +746 -0
  123. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  124. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  125. package/src/plugins/puppeteer.errors.js +81 -0
  126. package/src/plugins/puppeteer.plugin.js +1327 -0
  127. package/src/plugins/queue-consumer.plugin.js +69 -14
  128. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  129. package/src/plugins/recon/concerns/command-runner.js +148 -0
  130. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  131. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  132. package/src/plugins/recon/concerns/process-manager.js +338 -0
  133. package/src/plugins/recon/concerns/report-generator.js +478 -0
  134. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  135. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  136. package/src/plugins/recon/config/defaults.js +321 -0
  137. package/src/plugins/recon/config/resources.js +370 -0
  138. package/src/plugins/recon/index.js +778 -0
  139. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  140. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  141. package/src/plugins/recon/managers/storage-manager.js +745 -0
  142. package/src/plugins/recon/managers/target-manager.js +274 -0
  143. package/src/plugins/recon/stages/asn-stage.js +314 -0
  144. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  145. package/src/plugins/recon/stages/dns-stage.js +107 -0
  146. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  147. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  148. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  149. package/src/plugins/recon/stages/http-stage.js +89 -0
  150. package/src/plugins/recon/stages/latency-stage.js +148 -0
  151. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  152. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  153. package/src/plugins/recon/stages/ports-stage.js +169 -0
  154. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  155. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  156. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  157. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  158. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  159. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  160. package/src/plugins/recon/stages/whois-stage.js +349 -0
  161. package/src/plugins/recon.plugin.js +75 -0
  162. package/src/plugins/recon.plugin.js.backup +2635 -0
  163. package/src/plugins/relation.errors.js +87 -14
  164. package/src/plugins/replicator.plugin.js +514 -137
  165. package/src/plugins/replicators/base-replicator.class.js +89 -1
  166. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  167. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  168. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  169. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  170. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  171. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  172. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  173. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  174. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  175. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  176. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  177. package/src/plugins/s3-queue.plugin.js +464 -65
  178. package/src/plugins/scheduler.plugin.js +20 -6
  179. package/src/plugins/state-machine.plugin.js +40 -9
  180. package/src/plugins/tfstate/README.md +126 -126
  181. package/src/plugins/tfstate/base-driver.js +28 -4
  182. package/src/plugins/tfstate/errors.js +65 -10
  183. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  184. package/src/plugins/tfstate/index.js +163 -90
  185. package/src/plugins/tfstate/s3-driver.js +64 -6
  186. package/src/plugins/ttl.plugin.js +72 -17
  187. package/src/plugins/vector/distances.js +18 -12
  188. package/src/plugins/vector/kmeans.js +26 -4
  189. package/src/resource.class.js +115 -19
  190. package/src/testing/factory.class.js +20 -3
  191. package/src/testing/seeder.class.js +7 -1
  192. package/src/clients/memory-client.md +0 -917
  193. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -32,9 +32,25 @@
32
32
  */
33
33
 
34
34
  import { requirePluginDependency } from '../../concerns/plugin-dependencies.js';
35
+ import tryFn from '../../../concerns/try-fn.js';
36
+ import { resolveResourceNames } from '../../concerns/resource-names.js';
35
37
 
36
38
  export class FailbanManager {
37
39
  constructor(options = {}) {
40
+ this.namespace = options.namespace || null;
41
+ const resourceOverrides = options.resourceNames || options.resources || {};
42
+ this._resourceDescriptors = {
43
+ bans: {
44
+ defaultName: 'plg_api_failban_bans',
45
+ override: resourceOverrides.bans
46
+ },
47
+ violations: {
48
+ defaultName: 'plg_api_failban_violations',
49
+ override: resourceOverrides.violations
50
+ }
51
+ };
52
+ this.resourceNames = this._resolveResourceNames();
53
+
38
54
  this.options = {
39
55
  enabled: options.enabled !== false,
40
56
  database: options.database,
@@ -52,7 +68,8 @@ export class FailbanManager {
52
68
  blockedCountries: options.geo?.blockedCountries || [],
53
69
  blockUnknown: options.geo?.blockUnknown || false,
54
70
  cacheResults: options.geo?.cacheResults !== false
55
- }
71
+ },
72
+ resources: this.resourceNames
56
73
  };
57
74
 
58
75
  this.database = options.database;
@@ -64,6 +81,18 @@ export class FailbanManager {
64
81
  this.cleanupTimer = null;
65
82
  }
66
83
 
84
+ _resolveResourceNames() {
85
+ return resolveResourceNames('api_failban', this._resourceDescriptors, {
86
+ namespace: this.namespace
87
+ });
88
+ }
89
+
90
+ setNamespace(namespace) {
91
+ this.namespace = namespace;
92
+ this.resourceNames = this._resolveResourceNames();
93
+ this.options.resources = this.resourceNames;
94
+ }
95
+
67
96
  /**
68
97
  * Initialize failban manager
69
98
  */
@@ -119,52 +148,61 @@ export class FailbanManager {
119
148
  * @private
120
149
  */
121
150
  async _createBansResource() {
122
- const resourceName = '_api_failban_bans';
123
-
151
+ const resourceName = this.resourceNames.bans;
124
152
  try {
125
153
  return await this.database.getResource(resourceName);
126
154
  } catch (err) {
127
- const resource = await this.database.createResource({
128
- name: resourceName,
129
- attributes: {
130
- ip: 'string|required',
131
- reason: 'string',
132
- violations: 'number',
133
- bannedAt: 'string',
134
- expiresAt: 'string|required',
135
- metadata: {
136
- userAgent: 'string',
137
- path: 'string',
138
- lastViolation: 'string'
139
- }
140
- },
141
- behavior: 'body-overflow',
142
- timestamps: true,
143
- partitions: {
144
- byExpiry: {
145
- fields: { expiresAtCohort: 'string' }
146
- }
155
+ // fall through
156
+ }
157
+
158
+ const [created, createErr, resource] = await tryFn(() => this.database.createResource({
159
+ name: resourceName,
160
+ attributes: {
161
+ ip: 'string|required',
162
+ reason: 'string',
163
+ violations: 'number',
164
+ bannedAt: 'string',
165
+ expiresAt: 'string|required',
166
+ metadata: {
167
+ userAgent: 'string',
168
+ path: 'string',
169
+ lastViolation: 'string'
147
170
  }
148
- });
149
-
150
- // Apply TTL plugin to this resource
151
- const ttlPlugin = this.database.plugins?.ttl || this.database.plugins?.TTLPlugin;
152
- if (ttlPlugin) {
153
- ttlPlugin.options.resources = ttlPlugin.options.resources || {};
154
- ttlPlugin.options.resources[resourceName] = {
155
- enabled: true,
156
- field: 'expiresAt'
157
- };
158
-
159
- if (this.options.verbose) {
160
- console.log('[Failban] TTL configured for bans resource');
171
+ },
172
+ behavior: 'body-overflow',
173
+ timestamps: true,
174
+ partitions: {
175
+ byExpiry: {
176
+ fields: { expiresAtCohort: 'string' }
161
177
  }
162
- } else {
163
- console.warn('[Failban] TTLPlugin not found - bans will not auto-expire from DB');
164
178
  }
179
+ }));
165
180
 
166
- return resource;
181
+ if (!created) {
182
+ const existing = this.database.resources?.[resourceName];
183
+ if (existing) {
184
+ return existing;
185
+ }
186
+ throw createErr;
167
187
  }
188
+
189
+ // Apply TTL plugin to this resource
190
+ const ttlPlugin = this.database.plugins?.ttl || this.database.plugins?.TTLPlugin;
191
+ if (ttlPlugin) {
192
+ ttlPlugin.options.resources = ttlPlugin.options.resources || {};
193
+ ttlPlugin.options.resources[resourceName] = {
194
+ enabled: true,
195
+ field: 'expiresAt'
196
+ };
197
+
198
+ if (this.options.verbose) {
199
+ console.log(`[Failban] TTL configured for bans resource (${resourceName})`);
200
+ }
201
+ } else {
202
+ console.warn('[Failban] TTLPlugin not found - bans will not auto-expire from DB');
203
+ }
204
+
205
+ return resource;
168
206
  }
169
207
 
170
208
  /**
@@ -172,29 +210,40 @@ export class FailbanManager {
172
210
  * @private
173
211
  */
174
212
  async _createViolationsResource() {
175
- const resourceName = '_api_failban_violations';
176
-
213
+ const resourceName = this.resourceNames.violations;
177
214
  try {
178
215
  return await this.database.getResource(resourceName);
179
216
  } catch (err) {
180
- return await this.database.createResource({
181
- name: resourceName,
182
- attributes: {
183
- ip: 'string|required',
184
- timestamp: 'string|required',
185
- type: 'string',
186
- path: 'string',
187
- userAgent: 'string'
188
- },
189
- behavior: 'body-overflow',
190
- timestamps: true,
191
- partitions: {
192
- byIp: {
193
- fields: { ip: 'string' }
194
- }
217
+ // fall through
218
+ }
219
+
220
+ const [created, createErr, resource] = await tryFn(() => this.database.createResource({
221
+ name: resourceName,
222
+ attributes: {
223
+ ip: 'string|required',
224
+ timestamp: 'string|required',
225
+ type: 'string',
226
+ path: 'string',
227
+ userAgent: 'string'
228
+ },
229
+ behavior: 'body-overflow',
230
+ timestamps: true,
231
+ partitions: {
232
+ byIp: {
233
+ fields: { ip: 'string' }
195
234
  }
196
- });
235
+ }
236
+ }));
237
+
238
+ if (!created) {
239
+ const existing = this.database.resources?.[resourceName];
240
+ if (existing) {
241
+ return existing;
242
+ }
243
+ throw createErr;
197
244
  }
245
+
246
+ return resource;
198
247
  }
199
248
 
200
249
  /**
@@ -0,0 +1,116 @@
1
+ /**
2
+ * OpenGraph Helper
3
+ *
4
+ * Generates OpenGraph and Twitter Card meta tags for social media previews.
5
+ *
6
+ * @example
7
+ * const og = new OpenGraphHelper({
8
+ * siteName: 'My Site',
9
+ * locale: 'en_US',
10
+ * twitterSite: '@mysite'
11
+ * });
12
+ *
13
+ * const tags = og.generateTags({
14
+ * title: 'Page Title',
15
+ * description: 'Page description',
16
+ * image: '/og-image.jpg',
17
+ * url: 'https://example.com/page'
18
+ * });
19
+ */
20
+ export class OpenGraphHelper {
21
+ constructor(defaults = {}) {
22
+ this.defaults = {
23
+ siteName: defaults.siteName || 'My Site',
24
+ locale: defaults.locale || 'en_US',
25
+ type: defaults.type || 'website',
26
+ twitterCard: defaults.twitterCard || 'summary_large_image',
27
+ twitterSite: defaults.twitterSite || null,
28
+ defaultImage: defaults.defaultImage || null
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Generate OpenGraph meta tags
34
+ *
35
+ * @param {Object} data - OpenGraph data
36
+ * @param {string} data.title - Page title
37
+ * @param {string} data.description - Page description
38
+ * @param {string} data.image - Image URL (absolute or relative)
39
+ * @param {string} data.url - Canonical URL
40
+ * @param {string} data.type - Content type (default: 'website')
41
+ * @param {string} data.locale - Locale (default: from defaults)
42
+ * @param {string} data.siteName - Site name (default: from defaults)
43
+ * @param {string} data.imageAlt - Image alt text
44
+ * @param {number} data.imageWidth - Image width in pixels
45
+ * @param {number} data.imageHeight - Image height in pixels
46
+ * @param {string} data.twitterCard - Twitter card type (default: from defaults)
47
+ * @param {string} data.twitterSite - Twitter @username (default: from defaults)
48
+ * @param {string} data.twitterCreator - Twitter creator @username
49
+ * @returns {string} HTML meta tags
50
+ */
51
+ generateTags(data = {}) {
52
+ const og = { ...this.defaults, ...data };
53
+
54
+ // Use default image if none provided
55
+ const image = og.image || og.defaultImage;
56
+
57
+ const tags = [
58
+ // Basic OpenGraph
59
+ og.title && `<meta property="og:title" content="${this._escape(og.title)}">`,
60
+ og.description && `<meta property="og:description" content="${this._escape(og.description)}">`,
61
+ image && `<meta property="og:image" content="${this._escape(image)}">`,
62
+ og.url && `<meta property="og:url" content="${this._escape(og.url)}">`,
63
+ `<meta property="og:type" content="${this._escape(og.type)}">`,
64
+ `<meta property="og:site_name" content="${this._escape(og.siteName)}">`,
65
+ `<meta property="og:locale" content="${this._escape(og.locale)}">`,
66
+
67
+ // Image metadata
68
+ og.imageAlt && `<meta property="og:image:alt" content="${this._escape(og.imageAlt)}">`,
69
+ og.imageWidth && `<meta property="og:image:width" content="${og.imageWidth}">`,
70
+ og.imageHeight && `<meta property="og:image:height" content="${og.imageHeight}">`,
71
+
72
+ // Twitter Cards
73
+ `<meta name="twitter:card" content="${this._escape(og.twitterCard)}">`,
74
+ og.twitterSite && `<meta name="twitter:site" content="${this._escape(og.twitterSite)}">`,
75
+ og.twitterCreator && `<meta name="twitter:creator" content="${this._escape(og.twitterCreator)}">`,
76
+ og.title && `<meta name="twitter:title" content="${this._escape(og.title)}">`,
77
+ og.description && `<meta name="twitter:description" content="${this._escape(og.description)}">`,
78
+ image && `<meta name="twitter:image" content="${this._escape(image)}">`,
79
+ ];
80
+
81
+ return tags.filter(Boolean).join('\n ');
82
+ }
83
+
84
+ /**
85
+ * Create Hono middleware that injects OG helper into context
86
+ *
87
+ * @example
88
+ * app.use('*', ogHelper.middleware());
89
+ *
90
+ * // In route handler:
91
+ * const ogTags = c.get('og')({ title: 'My Page', ... });
92
+ */
93
+ middleware() {
94
+ return async (c, next) => {
95
+ c.set('og', (data) => this.generateTags(data));
96
+ await next();
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Escape HTML entities to prevent XSS
102
+ * @private
103
+ */
104
+ _escape(str) {
105
+ if (str === null || str === undefined) return '';
106
+
107
+ return String(str)
108
+ .replace(/&/g, '&amp;')
109
+ .replace(/</g, '&lt;')
110
+ .replace(/>/g, '&gt;')
111
+ .replace(/"/g, '&quot;')
112
+ .replace(/'/g, '&#039;');
113
+ }
114
+ }
115
+
116
+ export default OpenGraphHelper;