nitroping

Open-core feedback collection platform for web, iOS, and Android, powered by Cloudflare, Alchemy, and Effect.

8
0
8
TypeScript
public

NitroPing

Your users’ voice, your product’s compass.

NitroPing is an open-core feedback platform embeddable in web, iOS, and Android applications.

Getting started

bun install
bun run check
bun run dev:api

Run the deployment configuration preflight without printing secret values:

bun run preflight
bun run preflight -- --strict

The default mode reports optional integrations that are intentionally disabled. Values kept outside the repository, such as Worker secrets stored with wrangler secret put, cannot be read by the script; when only those are missing, the integration is reported as configured. Strict mode requires every value to be present in the environment, fails on incomplete configuration, and is intended for production deployment gates.

API health check:

curl http://localhost:8787/health

Local D1 migration:

bunx wrangler d1 migrations apply nitroping --local --config apps/api/wrangler.jsonc

Alchemy infrastructure plan/deploy:

set -a; source /home/opensrc/.cf.env; set +a
CLOUDFLARE_ACCOUNT_ID="$ACCOUNT_ID" CLOUDFLARE_API_TOKEN="$ACCOUNT_TOKEN" NITROPING_STAGE=staging bun run infra:plan
CLOUDFLARE_ACCOUNT_ID="$ACCOUNT_ID" CLOUDFLARE_API_TOKEN="$ACCOUNT_TOKEN" NITROPING_STAGE=staging bun run infra:deploy

The verified staging Worker endpoint is https://nitroping-api-staging.srvrun.workers.dev. After deployment, rerun NITROPING_STAGE=staging bun run infra:plan; a healthy stack should report no changes. Staging uses the same migration directory as the repository and is isolated from the production resources.

Production DNS records are opt-in in Alchemy because DNS record management requires a separate Zone DNS Edit permission. After granting that permission, preview the records with:

NITROPING_STAGE=production NITROPING_MANAGE_DNS=true bun run infra:plan

This manages only www.nitroping.dev (CNAME to the apex) and api.nitroping.dev (proxied CNAME to nitroping-api.srvrun.workers.dev). Alchemy refuses to adopt an existing unowned record unless --adopt is explicitly used.

The Alchemy stack is intentionally isolated in infra/ and uses the same Cloudflare account credentials as the operational tooling. Review the plan before deploying: an empty Alchemy state will propose new resources rather than adopting resources already managed by Wrangler. Staging is fully managed by Alchemy. Production infrastructure resources are Alchemy-managed, while the production nitroping-api Worker remains Wrangler-managed because its live Durable Object uses Wrangler’s declarative exports flow; Alchemy beta.77 cannot safely switch that existing Worker to its migration flow. Do not run a production Alchemy deploy against the Worker until that provider limitation is resolved.

Stripe billing is provider-isolated and disabled until production secrets are configured:

bunx wrangler secret put STRIPE_SECRET_KEY --config apps/api/wrangler.jsonc
bunx wrangler secret put STRIPE_PRICE_PRO --config apps/api/wrangler.jsonc
bunx wrangler secret put STRIPE_PRICE_BUSINESS --config apps/api/wrangler.jsonc
bunx wrangler secret put STRIPE_WEBHOOK_SECRET --config apps/api/wrangler.jsonc

Turnstile is opt-in for public feedback. Set the public site key as a Worker variable and the secret with Wrangler; when the secret is present, every public feedback submission must pass server-side Siteverify validation:

bunx wrangler secret put TURNSTILE_SECRET_KEY --config apps/api/wrangler.jsonc
# Add TURNSTILE_SITE_KEY to the deployment variables, then redeploy.

Custom domains are a Business feature backed by Cloudflare for SaaS custom hostnames. After validation completes, the Worker resolves the hostname to its tenant-scoped project and serves the public portal at both / and /portal; API routes remain versioned under /api/v1. The API is intentionally disabled with CUSTOM_DOMAIN_NOT_CONFIGURED until the Cloudflare for SaaS zone, fallback origin, and token are configured. The token must be stored as a secret and must have the custom-hostname certificate permission required by Cloudflare:

Moderation is rule-based by default. When the Workers AI binding is available, dashboard moderators can request an AI assist review for a feedback item. The result is stored as a pending moderation_events record and audit event; it can suggest a type, sentiment, risk, summary, and confidence, but it never changes status, deletes content, or bans a user automatically. Workers AI usage is optional and may incur Cloudflare AI charges.

bunx wrangler secret put CUSTOM_HOSTNAME_API_TOKEN --config apps/api/wrangler.jsonc
# Set these deployment variables before redeploying:
# CUSTOM_HOSTNAME_ZONE_ID=<Cloudflare zone id>
# CUSTOM_HOSTNAME_ZONE_NAME=nitroping.dev
# CUSTOM_HOSTNAME_FALLBACK_ORIGIN=https://origin.nitroping.dev

Customers manage one custom hostname per project through GET, POST, and DELETE /api/v1/dashboard/projects/:projectId/custom-domain. Creation returns the Cloudflare SSL validation records that the customer must publish in DNS. The route is tenant-scoped, Business-plan gated, audited, and removed during organization deletion. Cloudflare for SaaS setup and custom-hostname API permissions are external prerequisites; configuring the variables alone does not activate hostname routing.

Self-hosted deployments can use Cloudflare Access or the built-in OIDC adapter. Set OIDC_ISSUER_URL and OIDC_AUDIENCE when an external identity provider should authenticate dashboard requests; signed bearer tokens are verified against the provider’s discovery document and JWKS before tenant membership is checked. See deploy/self-host/README.md.

Dashboard architecture

The dashboard is a React 19 application built with Vite 8 and Bun. Its interface is built from shadcn/ui components generated on Base UI (style: base-mira, baseColor: neutral, lucide icons) and kept deliberately flat and dense: tables and separator lists instead of cards.

  • apps/dashboard/src/app: application shell — sidebar, topbar, sign-in and workspace setup screens
  • apps/dashboard/src/features/<view>: one folder per dashboard view
  • apps/dashboard/src/portal, src/follow-up: the public surfaces, sharing the same components
  • apps/dashboard/src/components/ui: shadcn components; regenerate with bunx shadcn@4.21.0 add --overwrite <name>
  • apps/dashboard/src/hooks, apps/dashboard/src/lib: workspace and feedback state, API client, status/type class maps
  • packages/tokens/src/tokens.css: the only file in the repository that declares design tokens, shared by the dashboard, the portal, and the marketing site
  • apps/dashboard/dashboard.html: Vite entry document
  • apps/api/public/dashboard.html: generated production entry served by the Worker
  • apps/api/public/assets: generated JavaScript bundle

The customer dashboard at /dashboard uses GitHub OAuth and creates a secure, 30-day session. A first-time GitHub user is taken through organization and project setup; subsequent requests are authorized through organization membership and capability checks. The operator dashboard is the same workspace surface at /admin and remains protected by Cloudflare Access. The first dashboard surface includes Inbox, Insights, Moderation, Roadmap, Changelog, Audit log, Developer controls, Team, Notifications, Billing, Widget Builder, and Settings. It also supports creating organizations and projects from the workspace switcher, linking feedback to roadmap and changelog items, and unlinking it without leaving the tenant boundary.

Configure a GitHub OAuth App with callback URL https://nitroping.dev/auth/github/callback, set the client ID as GITHUB_CLIENT_ID, and store the client secret with bunx wrangler secret put GITHUB_CLIENT_SECRET --config apps/api/wrangler.jsonc. Customer login is intentionally disabled until both values are present.

The hosted public portal is available at /portal?projectId=<project-id>&projectKey=<public-key>. It provides feedback submission, community browsing, voting, roadmap, and changelog views without requiring the customer to build a separate public page.

When a feedback submission includes an email address, NitroPing automatically creates a 24-hour scoped follow-up link, rotates any previous active link for that feedback, records the consent, and queues the notification email. The explicit /follow-up/request endpoint remains available for headless clients that collect consent after submission.

Public clients can load the safe project configuration from /api/v1/projects/:projectId/public/config. The response contains the published widget theme and categories only; retention settings, allowed origins, API keys, and organization data are never exposed. Theme fields, colors, metadata keys, metadata values, and SDK context lengths are validated server-side. The Web SDK wraps this endpoint with loadNitroPingConfig and NitroPing.initAsync.

Run the dashboard locally:

bun run dev:dashboard

Build the complete Worker asset bundle:

bun run build

Packages

  • @nitroping/contracts: shared domain types
  • @nitroping/web: web widget and headless client
  • apps/api: Effect service boundaries and the Cloudflare Worker API
  • apps/site: static marketing site (Vite), deployed as the nitroping-site Worker with bun run deploy:site. It collects its own feedback with @nitroping/web. The public project key lives in apps/site/.env.production (public keys are shipped to browsers by design), and the floating widget loads as a separate chunk; without VITE_NITROPING_PROJECT_KEY the site ships no widget.
  • migrations: D1 schema migrations
  • openapi.yaml: public API contract

Privacy and security documents

Public package artifacts are built into dist/ for @nitroping/contracts and @nitroping/web. A v* tag (or the manually dispatched Release SDKs workflow) publishes the npm packages and, when Maven credentials are configured, the Android artifact to GitHub Packages. Configure NPM_TOKEN, MAVEN_USERNAME, and MAVEN_TOKEN as GitHub Actions secrets before creating a release.

Notes

Production resources are provisioned on Cloudflare. The API Worker serves the dashboard, portal, and follow-up assets and the API under /api/v1; any other path is forwarded to the nitroping-site Worker through the SITE service binding, so deploy the site before the API when setting up a new account. The native SDKs use the same OpenAPI contract.

v0.3.3[beta]