@commentbox/server-worker 0.0.0-stage → 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Solutions Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,10 @@
1
- # Temporary Holding Version
1
+ # @commentbox/server-worker
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [CommentBox](https://commentbox.dev) server for Cloudflare Workers + D1. Usually scaffolded for you:
4
+
5
+ ```bash
6
+ npx @commentbox/cli init --origin https://your-site.com --provision
7
+ ```
8
+
9
+ Manual use: `export { default } from '@commentbox/server-worker'` as your Worker entry, with a D1 binding `DB`,
10
+ the migration in `migrations/` and the `[[rules]]` Text rule from `wrangler.toml`. MIT licensed.
@@ -0,0 +1,54 @@
1
+ -- CommentBox schema (SQLite / Cloudflare D1). Applied by both servers on start or via wrangler d1 migrations apply.
2
+ CREATE TABLE IF NOT EXISTS comments (
3
+ id TEXT PRIMARY KEY,
4
+ created_at TEXT NOT NULL,
5
+ updated_at TEXT NOT NULL,
6
+ status TEXT NOT NULL DEFAULT 'new',
7
+ type TEXT NOT NULL,
8
+ text TEXT NOT NULL,
9
+ name TEXT NOT NULL DEFAULT '',
10
+ page_url TEXT NOT NULL,
11
+ pathname TEXT NOT NULL,
12
+ selector TEXT,
13
+ tag_name TEXT,
14
+ element_text TEXT,
15
+ rect TEXT,
16
+ scroll_y REAL NOT NULL DEFAULT 0,
17
+ viewport_width INTEGER NOT NULL DEFAULT 0,
18
+ viewport_height INTEGER NOT NULL DEFAULT 0,
19
+ device_pixel_ratio REAL NOT NULL DEFAULT 1,
20
+ user_agent TEXT NOT NULL DEFAULT '',
21
+ screenshot_id TEXT,
22
+ meta TEXT,
23
+ note TEXT
24
+ );
25
+ CREATE INDEX IF NOT EXISTS comments_created_at ON comments (created_at);
26
+ CREATE INDEX IF NOT EXISTS comments_status ON comments (status);
27
+
28
+ CREATE TABLE IF NOT EXISTS screenshots (
29
+ id TEXT PRIMARY KEY,
30
+ mime TEXT NOT NULL,
31
+ data TEXT NOT NULL,
32
+ created_at TEXT NOT NULL
33
+ );
34
+
35
+ CREATE TABLE IF NOT EXISTS deliveries (
36
+ id TEXT PRIMARY KEY,
37
+ comment_id TEXT,
38
+ event TEXT NOT NULL,
39
+ url TEXT NOT NULL,
40
+ body TEXT NOT NULL,
41
+ status TEXT NOT NULL DEFAULT 'pending',
42
+ attempts INTEGER NOT NULL DEFAULT 0,
43
+ next_attempt_at INTEGER NOT NULL,
44
+ last_status INTEGER,
45
+ last_error TEXT,
46
+ created_at TEXT NOT NULL,
47
+ updated_at TEXT NOT NULL
48
+ );
49
+ CREATE INDEX IF NOT EXISTS deliveries_due ON deliveries (status, next_attempt_at);
50
+
51
+ CREATE TABLE IF NOT EXISTS settings (
52
+ key TEXT PRIMARY KEY,
53
+ value TEXT NOT NULL
54
+ );
@@ -0,0 +1,15 @@
1
+ -- 0002: session links (CommentBox mode "session"). Only the SHA-256 of a token is stored.
2
+ CREATE TABLE IF NOT EXISTS review_sessions (
3
+ id TEXT PRIMARY KEY,
4
+ name TEXT NOT NULL,
5
+ token_hash TEXT NOT NULL UNIQUE,
6
+ created_at TEXT NOT NULL,
7
+ expires_at INTEGER NOT NULL,
8
+ closed_at INTEGER
9
+ );
10
+
11
+ CREATE TABLE IF NOT EXISTS session_comments (
12
+ comment_id TEXT PRIMARY KEY,
13
+ session_id TEXT NOT NULL
14
+ );
15
+ CREATE INDEX IF NOT EXISTS session_comments_session ON session_comments (session_id);
@@ -0,0 +1,28 @@
1
+ -- CommentBox 0.2: comment rounds (opt-in). Adds the rounds table and two columns on comments.
2
+ -- Servers apply this by themselves on start (the ALTERs only when the columns are missing).
3
+ ALTER TABLE comments ADD COLUMN round_id TEXT;
4
+ ALTER TABLE comments ADD COLUMN screenshot_highlighted INTEGER NOT NULL DEFAULT 0;
5
+ CREATE INDEX IF NOT EXISTS comments_round ON comments (round_id);
6
+ CREATE TABLE IF NOT EXISTS rounds (
7
+ id TEXT PRIMARY KEY,
8
+ project TEXT NOT NULL,
9
+ page_url TEXT NOT NULL,
10
+ session TEXT NOT NULL,
11
+ status TEXT NOT NULL DEFAULT 'open',
12
+ comment_count INTEGER NOT NULL DEFAULT 0,
13
+ created_at TEXT NOT NULL,
14
+ updated_at TEXT NOT NULL,
15
+ last_comment_at INTEGER NOT NULL,
16
+ closed_at TEXT,
17
+ close_reason TEXT
18
+ );
19
+ CREATE INDEX IF NOT EXISTS rounds_open ON rounds (status, project, page_url, session);
20
+ CREATE INDEX IF NOT EXISTS rounds_idle ON rounds (status, last_comment_at);
21
+ CREATE TABLE IF NOT EXISTS screenshot_variants (
22
+ screenshot_id TEXT NOT NULL,
23
+ variant TEXT NOT NULL,
24
+ mime TEXT NOT NULL,
25
+ data TEXT NOT NULL,
26
+ created_at TEXT NOT NULL,
27
+ PRIMARY KEY (screenshot_id, variant)
28
+ );
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@commentbox/server-worker",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.1",
4
+ "description": "CommentBox server for Cloudflare Workers + D1.",
5
+ "keywords": [
6
+ "commentbox",
7
+ "cloudflare-workers",
8
+ "d1",
9
+ "feedback",
10
+ "webhook"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Solutions Lab (https://solutionslab.net)",
14
+ "homepage": "https://commentbox.dev",
15
+ "bugs": {
16
+ "url": "https://github.com/loek-solutionslab/commentbox/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/loek-solutionslab/commentbox.git",
21
+ "directory": "packages/server-worker"
22
+ },
23
+ "type": "module",
24
+ "main": "./src/index.ts",
25
+ "types": "./src/index.ts",
26
+ "exports": {
27
+ ".": "./src/index.ts",
28
+ "./worker": "./src/worker.ts",
29
+ "./migrations/0001_init.sql": "./migrations/0001_init.sql",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "files": [
33
+ "LICENSE",
34
+ "README.md",
35
+ "migrations",
36
+ "src",
37
+ "wrangler.toml"
38
+ ],
39
+ "engines": {
40
+ "node": ">=20"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "dependencies": {
46
+ "@commentbox/core": "0.2.1",
47
+ "@commentbox/widget": "0.2.1"
48
+ },
49
+ "devDependencies": {
50
+ "@cloudflare/workers-types": "^4.20241011.0",
51
+ "@types/better-sqlite3": "^7.6.11",
52
+ "better-sqlite3": "^11.5.0",
53
+ "wrangler": "^3.80.0"
54
+ },
55
+ "scripts": {
56
+ "build": "node build.mjs",
57
+ "typecheck": "tsc -p tsconfig.json --noEmit",
58
+ "dev": "wrangler dev",
59
+ "db:migrate:local": "wrangler d1 migrations apply commentbox --local"
60
+ }
6
61
  }
package/src/d1.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { SqlDriver } from '@commentbox/core'
2
+
3
+ /** Adapts a Cloudflare D1 binding to the core SqlDriver. */
4
+ export function d1Driver(db: D1Database): SqlDriver {
5
+ return {
6
+ async run(sql, params = []) {
7
+ const res = await db.prepare(sql).bind(...params).run()
8
+ return { changes: res.meta.changes ?? 0 }
9
+ },
10
+ async all(sql, params = []) {
11
+ const res = await db.prepare(sql).bind(...params).all<Record<string, unknown>>()
12
+ return res.results
13
+ },
14
+ async exec(statements) {
15
+ await db.batch(statements.map((s) => db.prepare(s)))
16
+ },
17
+ }
18
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ // The widget bundle is inlined (wrangler "Text" rule), so GET /widget.js works with no CDN.
2
+ import widgetJs from '@commentbox/widget/dist/commentbox.global.js'
3
+
4
+ import { createWorker } from './worker.js'
5
+
6
+ export default createWorker(widgetJs)
@@ -0,0 +1,4 @@
1
+ declare module '*.global.js' {
2
+ const source: string
3
+ export default source
4
+ }
package/src/worker.ts ADDED
@@ -0,0 +1,72 @@
1
+ import { applyRetention, configFromEnv, configWarnings, createHandler, createSqlStore, processDeliveries, processRounds, resolveConfig } from '@commentbox/core'
2
+
3
+ import { d1Driver } from './d1.js'
4
+
5
+ export type Env = {
6
+ DB: D1Database
7
+ ALLOWED_ORIGINS?: string
8
+ PUBLIC_URL?: string
9
+ PROJECT_NAME?: string
10
+ WIDGET_ENABLED?: string
11
+ RATE_LIMIT_PER_HOUR?: string
12
+ WEBHOOK_URL?: string
13
+ WEBHOOK_MAX_ATTEMPTS?: string
14
+ WEBHOOK_INTEGRATION?: string
15
+ WEBHOOK_ALLOW_INSECURE?: string
16
+ // Opt-in install count (off by default; DO_NOT_TRACK=1 always disables). See docs/telemetry.md.
17
+ COMMENTBOX_TELEMETRY?: string
18
+ COMMENTBOX_TELEMETRY_URL?: string
19
+ DO_NOT_TRACK?: string
20
+ // Comment rounds and the Donna integration (opt-in). See integrations/donna/README.md.
21
+ COMMENTBOX_ROUNDS?: string
22
+ COMMENTBOX_DONNA?: string
23
+ COMMENTBOX_ROUND_IDLE_MINUTES?: string
24
+ COMMENTBOX_ROUND_MAX_COMMENTS?: string
25
+ // Limits for public demos.
26
+ MAX_COMMENT_LENGTH?: string
27
+ MAX_SCREENSHOT_BYTES?: string
28
+ RETENTION_HOURS?: string
29
+ // Secrets (wrangler secret put …):
30
+ ADMIN_TOKEN?: string
31
+ WEBHOOK_SECRET?: string
32
+ WEBHOOK_BEARER_TOKEN?: string
33
+ // Extra header on every delivery, `Header-Name: value` (e.g. `X-Api-Key: abc`). A secret: wrangler secret put.
34
+ WEBHOOK_AUTH_HEADER?: string
35
+ }
36
+
37
+ /** Worker entry, without the widget import so tests can run it against a fake D1. */
38
+ export function createWorker(widgetJs?: string) {
39
+ // One handler per isolate (env is stable per isolate): keeps the schema check and rate limiter warm.
40
+ const cache = new WeakMap<Env, { handle: ReturnType<typeof createHandler>; store: ReturnType<typeof createSqlStore>; ready: Promise<void> | null }>()
41
+ const get = (env: Env) => {
42
+ let hit = cache.get(env)
43
+ if (!hit) {
44
+ const store = createSqlStore(d1Driver(env.DB))
45
+ // Once per isolate, never with secret values.
46
+ for (const w of configWarnings(env)) console.warn(w)
47
+ hit = { store, handle: createHandler({ store, config: configFromEnv(env), widgetJs }), ready: null }
48
+ cache.set(env, hit)
49
+ }
50
+ return hit
51
+ }
52
+
53
+ return {
54
+ async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
55
+ return get(env).handle(req, { waitUntil: (p) => ctx.waitUntil(p) })
56
+ },
57
+ /** Cron trigger (every minute): closes idle comment rounds and sends webhook retries that are due. */
58
+ async scheduled(_event: ScheduledController, env: Env, ctx: ExecutionContext): Promise<void> {
59
+ const hit = get(env)
60
+ hit.ready ??= hit.store.init()
61
+ await hit.ready
62
+ const config = configFromEnv(env)
63
+ // Idle rounds first (they queue round.closed), then every due delivery, retries included.
64
+ ctx.waitUntil(
65
+ processRounds(hit.store, config)
66
+ .then(() => resolveConfig(hit.store, config))
67
+ .then((cfg) => processDeliveries(hit.store, cfg))
68
+ .then(() => applyRetention(hit.store, config)),
69
+ )
70
+ },
71
+ } satisfies ExportedHandler<Env>
72
+ }
package/wrangler.toml ADDED
@@ -0,0 +1,50 @@
1
+ # CommentBox on Cloudflare Workers + D1.
2
+ # 1. npx wrangler d1 create commentbox -> paste the database_id below
3
+ # 2. npx wrangler d1 migrations apply commentbox --remote
4
+ # 3. npx wrangler secret put ADMIN_TOKEN / WEBHOOK_SECRET (never commit secrets)
5
+ # Optional secrets: WEBHOOK_BEARER_TOKEN, and WEBHOOK_AUTH_HEADER for an extra header on every delivery,
6
+ # format "Header-Name: value", e.g. "X-Api-Key: abc" or "Authorization: Bearer abc123" (wins over the bearer token).
7
+ # 4. npx wrangler deploy
8
+ # `npx @commentbox/cli init --origin https://your-site.com --provision` runs these steps for you.
9
+ name = "commentbox"
10
+ main = "src/index.ts"
11
+ compatibility_date = "2024-10-01"
12
+ # nodejs_compat: Buffer for the server-side screenshot highlighter (jpeg-js) used by comment rounds.
13
+ compatibility_flags = ["nodejs_compat"]
14
+
15
+ [vars]
16
+ # Comma-separated origins allowed to post comments (no trailing slash).
17
+ ALLOWED_ORIGINS = "http://localhost:3000"
18
+ PROJECT_NAME = "my-site"
19
+ # Hard switch; the soft switch is PUT /v1/settings {"enabled": false} (or `npx @commentbox/cli toggle off`).
20
+ WIDGET_ENABLED = "true"
21
+ RATE_LIMIT_PER_HOUR = "20"
22
+ # Where every new comment is POSTed (your Grok Bot or any agent endpoint). Empty = no webhook.
23
+ WEBHOOK_URL = ""
24
+ WEBHOOK_MAX_ATTEMPTS = "6"
25
+ # "grok-bot" or "webhook": what WEBHOOK_URL points at.
26
+ WEBHOOK_INTEGRATION = "webhook"
27
+ # Opt-in anonymous install count, OFF by default. "1" sends one ping per verified webhook registration
28
+ # (random install id, version, integration, event kind; nothing else). DO_NOT_TRACK = "1" always wins. See docs/telemetry.md.
29
+ COMMENTBOX_TELEMETRY = "0"
30
+ # Comment rounds + Donna review cards (opt-in, off by default). DONNA=1 implies ROUNDS=1 and shows "Send round".
31
+ COMMENTBOX_ROUNDS = "0"
32
+ COMMENTBOX_DONNA = "0"
33
+ COMMENTBOX_ROUND_IDLE_MINUTES = "15"
34
+ # Set when the Worker runs behind a custom domain, e.g. "https://comments.example.com".
35
+ PUBLIC_URL = ""
36
+
37
+ [[d1_databases]]
38
+ binding = "DB"
39
+ database_name = "commentbox"
40
+ database_id = "00000000-0000-0000-0000-000000000000"
41
+ migrations_dir = "migrations"
42
+
43
+ # Retries for failed webhook deliveries.
44
+ [triggers]
45
+ crons = ["* * * * *"]
46
+
47
+ [[rules]]
48
+ type = "Text"
49
+ globs = ["**/*.global.js"]
50
+ fallthrough = true