@crvouga/mockingbird-service-formbricks 1.5.1 → 1.6.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/CHANGELOG.md +11 -4
- package/README.md +4 -4
- package/dist/{chunk-PTHSM46L.js → chunk-4PMPNQUG.js} +3 -2
- package/dist/chunk-4PMPNQUG.js.map +7 -0
- package/dist/{chunk-24SBIGNG.js → chunk-L4LQNY2V.js} +63 -20
- package/dist/{chunk-24SBIGNG.js.map → chunk-L4LQNY2V.js.map} +3 -3
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +23 -4
- package/dist/index.js +1 -1
- package/dist/server.d.ts +19 -9
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-PTHSM46L.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-formbricks
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.6.0 (2026-09-30)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- boot Bolt over Socket Mode ([01322df](https://github.com/crvouga/mockingbird/commit/01322df315c66b1f2f5b53094cbbeeeef6d3ae6c))
|
|
8
|
+
- validate every element when a finished response opts in ([a418b5f](https://github.com/crvouga/mockingbird/commit/a418b5f573c69ac52b80334ac59a2347dd00d247))
|
|
4
9
|
|
|
5
10
|
### Fixes and improvements
|
|
6
11
|
|
|
7
|
-
-
|
|
12
|
+
- reject other-option answers longer than 250 characters ([14a86c8](https://github.com/crvouga/mockingbird/commit/14a86c8fac4e6e42d0d5a2647c157e7caa3cfa5d))
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
## 1.5.1 (2026-09-29)
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
### Fixes and improvements
|
|
17
|
+
|
|
18
|
+
- keep hosted pages inside narrow hosts and popups ([6512323](https://github.com/crvouga/mockingbird/commit/6512323198cb00b99005f3d2cfa5b9f1e6e321a7))
|
|
12
19
|
|
|
13
20
|
## 1.5.0 (2026-09-29)
|
|
14
21
|
|
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ resolve to the same workspace.
|
|
|
55
55
|
| Route | Behaviour |
|
|
56
56
|
| --- | --- |
|
|
57
57
|
| `GET /api/v1/client/{workspace}/environment` | `{data: {data: {surveys, actionClasses, workspace, project}, expiresAt}}` (`project` is upstream's legacy alias of `workspace`). Only `app` surveys `inProgress`, with the SDK's survey fields and `projectOverwrites` mirroring `workspaceOverwrites`. A malformed id is 400 `Invalid ID format`; an unknown one 404 `Workspace not found`. |
|
|
58
|
-
| `POST /api/v2/client/{workspace}/responses` | `{surveyId, finished, data, contactId?, meta?, …}` → `{data: {id, quotaFull: false}}`. An unknown workspace is 404; input errors are 400 `Fields are missing or incorrectly formatted` with zod 4 `details` (`{surveyId: "Invalid cuid2", finished: "Invalid input: expected boolean, received undefined"}`; `userId` is not part of the v2 input and is ignored); a `contactId` while contacts are off is 403 (Enterprise); an unknown survey 404; a survey of another workspace 400 `Survey is part of another workspace`; a survey not `inProgress` 403 `Survey is not accepting submissions`. `data` keeps only element ids and declared hidden fields (`hiddenFields.fieldIds`); other keys are dropped. Element validation
|
|
58
|
+
| `POST /api/v2/client/{workspace}/responses` | `{surveyId, finished, data, contactId?, meta?, …}` → `{data: {id, quotaFull: false}}`. An unknown workspace is 404; input errors are 400 `Fields are missing or incorrectly formatted` with zod 4 `details` (`{surveyId: "Invalid cuid2", finished: "Invalid input: expected boolean, received undefined"}`; `userId` is not part of the v2 input and is ignored); a `contactId` while contacts are off is 403 (Enterprise); an unknown survey 404; a survey of another workspace 400 `Survey is part of another workspace`; a survey not `inProgress` 403 `Survey is not accepting submissions`. `data` keeps only element ids and declared hidden fields (`hiddenFields.fieldIds`); other keys are dropped. Before element validation, a `multipleChoiceSingle` or `multipleChoiceMulti` value (a string, or any string in an array) that equals no choice label in the response language — the body's `language`, or the label's `default` when it is omitted; `getLocalizedValue` does not fall back — and is longer than 250 characters is 400 `Response exceeds character limit` with `details: {questionId}`, and nothing is stored. An exact label match is not length-checked; an unmatched entry of 250 characters or fewer passes this check. Element validation defaults to the elements present in `data` (finished or not). `validation: "finished-validates-all"` (pre-#7292 Formbricks) checks every element when `finished` is true and only the present ones when it is false; a failure is 400 and stores nothing. Either way: required → `Please fill out this field`; a choice outside a choice element's options (when it has no "other") → `Please enter a valid format`; openText email / url / phone → `Please enter a valid …`; 400 `Validation failed` with `details: {"response.data.<elementId>": "<messages joined by '; '>"}`. `meta` keeps `source`, `url`, `action` and the auto-captured page / UTM / screen keys, plus `userAgent` and `country` (from `CF-IPCountry`). `language` is canonicalized (`en` → `en-US`); a finished response's `ttc` gains `_total`. |
|
|
59
59
|
| `GET /api/v1/management/responses?surveyId=&limit=&skip=` | `{data: [response]}`, newest first; an unknown `surveyId` is 404. |
|
|
60
60
|
| `GET /api/v1/management/responses/{id}` | `{data: response}` or 404. |
|
|
61
61
|
| `GET /api/v1/management/surveys?limit=&offset=` | `{data: [survey]}`, most recently updated first. |
|
|
@@ -85,7 +85,7 @@ Retries, `GET /__admin/webhooks`, `…/events`, `…/replay`, `…/flush` and
|
|
|
85
85
|
| `GET /__admin/responses?surveyId=` | Stored responses, oldest first. |
|
|
86
86
|
| `GET\|PUT /__admin/surveys` | Read, or add / replace surveys (`[{id, name, type?, status?, workspaceId?, blocks?, questions?, …}]`; no `workspaceId` = served by every configured workspace). |
|
|
87
87
|
| `GET\|PUT /__admin/contacts` | Read, or add contacts (`[{id, userId?, attributes?, workspaceId?}]`) for `contactId` responses. |
|
|
88
|
-
| `GET\|PUT /__admin/settings` | `{workspaces?, legacyEnvironmentIds?, apiKeys?, webhookId?, contactsEnabled?}`. |
|
|
88
|
+
| `GET\|PUT /__admin/settings` | `{workspaces?, legacyEnvironmentIds?, apiKeys?, webhookId?, contactsEnabled?, validation?}`. `validation` is `present-only` (default, Formbricks after #7292) or `finished-validates-all` (before #7292: a finished response validates every element). `createRuntime({settings})` accepts the same fields. |
|
|
89
89
|
|
|
90
90
|
Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`; `GET /__admin/faults/presets`):
|
|
91
91
|
`rate_limited` (429 `too_many_requests`), `server_error` (500), `missing_response_id` (stored, but
|
|
@@ -105,7 +105,7 @@ string), `management_unauthorized`, `connection_drop`, `duplicate` (the next web
|
|
|
105
105
|
- Displays, contacts / identify, `user` and `storage` routes of the client API (the widget script
|
|
106
106
|
is a no-op, so the SDK never calls them); response updates (`PUT …/responses/{id}`).
|
|
107
107
|
- Quotas (always `quotaFull: false`), single-use links, PIN protection, reCAPTCHA, email
|
|
108
|
-
verification, file-upload checks,
|
|
108
|
+
verification, file-upload checks, custom `validation.rules`,
|
|
109
109
|
survey logic, and the full survey schema on create (only the shapes above are checked).
|
|
110
110
|
- The Embedded Data ingest contract beyond its allow-list (locked fields, coercion, size limits),
|
|
111
111
|
`userAgent` parsing (always `{device: "desktop"}`), and language canonicalization beyond common
|
|
@@ -124,7 +124,7 @@ string), `management_unauthorized`, `connection_drop`, `duplicate` (the next web
|
|
|
124
124
|
| `WEBHOOK_PATH` | string | A conventional receiver path for examples, `/webhooks/formbricks`. |
|
|
125
125
|
| `formbricksCredential` | function | The workspace id or API key a request carries. |
|
|
126
126
|
| `formbricksError` | function | Build Formbricks' `{code, message, details}` error response. |
|
|
127
|
-
| `validateResponseData` | function | Upstream's element validation: `(survey, data, language?)` → `{elementId: messages}` or `null`. |
|
|
127
|
+
| `validateResponseData` | function | Upstream's element validation: `(survey, data, language?, {validation?, finished?})` → `{elementId: messages}` or `null`. |
|
|
128
128
|
| `CORPUS_SURVEYS` | value | The synthetic survey corpus every namespace is seeded with. |
|
|
129
129
|
| `WORKSPACE_ID`, `ENVIRONMENT_ID`, `DEFAULT_SETTINGS` | values | The default workspace, its legacy environment id, and the default settings. |
|
|
130
130
|
| `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_SETTINGS,
|
|
3
3
|
createRuntime
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-L4LQNY2V.js";
|
|
5
5
|
|
|
6
6
|
// ../../adapters/node/dist/cli.js
|
|
7
7
|
import { readFile } from "node:fs/promises";
|
|
@@ -236,6 +236,7 @@ var start = async (target, values, config) => {
|
|
|
236
236
|
onLog: formatLog(config.log)
|
|
237
237
|
});
|
|
238
238
|
const listening = await listen(runtime, { port: config.port, host: config.host });
|
|
239
|
+
target.listening?.(listening);
|
|
239
240
|
console.log(`${target.name} mock listening on ${listening.url}`);
|
|
240
241
|
console.log(`${target.name} health: GET ${listening.url}/health`);
|
|
241
242
|
console.log(`${target.name} admin: ${listening.url}/__admin (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
|
|
@@ -367,4 +368,4 @@ export {
|
|
|
367
368
|
createServer2 as createServer,
|
|
368
369
|
serveTarget
|
|
369
370
|
};
|
|
370
|
-
//# sourceMappingURL=chunk-
|
|
371
|
+
//# sourceMappingURL=chunk-4PMPNQUG.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../adapters/node/src/cli.ts", "../../../adapters/node/src/serve.ts", "../../../adapters/node/src/listen.ts", "../src/server.ts"],
|
|
4
|
+
"sourcesContent": ["import { readFile } from \"node:fs/promises\"\nimport { type ParseArgsConfig, parseArgs } from \"node:util\"\nimport type { RequestLog, ServiceInstance, ServiceRuntime } from \"@crvouga/mockingbird-service\"\nimport { type Listening, listen } from \"./listen.js\"\n\nexport type CliOption = {\n type: \"string\" | \"boolean\"\n description: string\n /** Shown in help; the value placeholder, e.g. `<port>`. */\n value?: string\n default?: string | boolean\n}\n\nexport type CliValues = Record<string, string | boolean | undefined>\n\nexport type CliCommand = {\n summary: string\n usage?: string\n options?: Record<string, CliOption>\n /** Resolves to an exit code; a server command resolves only when it stops. */\n run(values: CliValues, positionals: string[]): Promise<number>\n}\n\nexport type CliSpec = {\n bin: string\n description: string\n commands: Record<string, CliCommand>\n}\n\nconst optionHelp = (options: Record<string, CliOption>): string[] =>\n Object.entries(options).map(([name, option]) => {\n const flag = `--${name}${option.type === \"string\" ? ` ${option.value ?? \"<value>\"}` : \"\"}`\n const fallback = option.default !== undefined ? ` (default: ${String(option.default)})` : \"\"\n return ` ${flag.padEnd(30)} ${option.description}${fallback}`\n })\n\nconst help = (spec: CliSpec): string =>\n [\n `${spec.bin} \u2014 ${spec.description}`,\n \"\",\n \"Usage:\",\n ` ${spec.bin} <command> [options]`,\n \"\",\n \"Commands:\",\n ...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),\n \"\",\n `Run \\`${spec.bin} <command> --help\\` for a command's options.`,\n ].join(\"\\n\")\n\nconst commandHelp = (spec: CliSpec, name: string, command: CliCommand): string =>\n [\n `${spec.bin} ${name} \u2014 ${command.summary}`,\n \"\",\n \"Usage:\",\n ` ${command.usage ?? `${spec.bin} ${name} [options]`}`,\n ...(command.options ? [\"\", \"Options:\", ...optionHelp(command.options)] : []),\n ].join(\"\\n\")\n\n/** Parse `argv` against `spec` and run the chosen command. Resolves to an exit code. */\nexport const runCli = async (spec: CliSpec, argv: string[]): Promise<number> => {\n // Two-word commands (`corpus pull`) win over one-word ones.\n const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : undefined\n const words = pair !== undefined && spec.commands[pair] ? 2 : 1\n const name = words === 2 ? pair : argv[0]\n const rest = argv.slice(words)\n if (name === undefined || name === \"--help\" || name === \"-h\" || name === \"help\") {\n console.log(help(spec))\n return 0\n }\n const command = spec.commands[name]\n if (!command) {\n console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}\\n\\n${help(spec)}`)\n return 2\n }\n if (rest.includes(\"--help\") || rest.includes(\"-h\")) {\n console.log(commandHelp(spec, name, command))\n return 0\n }\n let parsed: { values: CliValues; positionals: string[] }\n try {\n parsed = parseArgs({\n args: rest,\n allowPositionals: true,\n strict: true,\n options: Object.fromEntries(\n Object.entries(command.options ?? {}).map(([key, option]) => [\n key,\n {\n type: option.type,\n ...(option.default !== undefined ? { default: option.default } : {}),\n },\n ]),\n ) as ParseArgsConfig[\"options\"],\n }) as { values: CliValues; positionals: string[] }\n } catch (error) {\n console.error(\n `${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}\\n\\n${commandHelp(spec, name, command)}`,\n )\n return 2\n }\n return command.run(parsed.values, parsed.positionals)\n}\n\n// \u2500\u2500 serve \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type LogFormat = \"pretty\" | \"json\" | \"off\"\n\nexport type CommonServeOptions = {\n adminKey: string | undefined\n seed: string | undefined\n onLog: ((entry: RequestLog) => void) | undefined\n}\n\n/**\n * What a service contributes to `serve`: how to build its runtime from CLI flags,\n * and what to say at startup. Every service's `./server` entry exports one as\n * `serveTarget`, which is also how `serve --config` finds services by name.\n */\nexport type ServeTarget = {\n name: string\n defaultPort: number\n /** Serve flags beyond the common ones. */\n options?: Record<string, CliOption>\n create(\n values: CliValues,\n common: CommonServeOptions,\n ): Promise<ServiceRuntime<ServiceInstance>> | ServiceRuntime<ServiceInstance>\n /** Startup lines after the listen address, e.g. the loaded corpus version. */\n banner?(runtime: ServiceRuntime<ServiceInstance>): string[]\n /** After the HTTP server is bound. A service that speaks more than HTTP attaches it here. */\n listening?(server: Listening): void\n}\n\nconst COMMON_SERVE_OPTIONS: Record<string, CliOption> = {\n port: { type: \"string\", value: \"<port>\", description: \"Port to listen on\" },\n host: { type: \"string\", value: \"<host>\", description: \"Interface to bind\", default: \"127.0.0.1\" },\n \"admin-key\": {\n type: \"string\",\n value: \"<key>\",\n description: \"Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)\",\n },\n seed: { type: \"string\", value: \"<seed>\", description: \"Seed for every random choice\" },\n log: {\n type: \"string\",\n value: \"<pretty|json|off>\",\n description: \"Request log format\",\n default: \"pretty\",\n },\n \"log-requests\": {\n type: \"boolean\",\n description:\n \"One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json\",\n },\n config: {\n type: \"string\",\n value: \"<file>\",\n description: \"Serve every service in a mockingbird.json config instead\",\n },\n}\n\nconst formatLog = (format: LogFormat) => {\n if (format === \"off\") return undefined\n if (format === \"json\") return (entry: RequestLog) => console.log(JSON.stringify(entry))\n return (entry: RequestLog) => {\n const op = entry.operationId ?? (entry.unmatched ? \"UNMATCHED\" : \"-\")\n const ns = entry.namespace === \"default\" ? \"\" : ` [${entry.namespace}]`\n const fault = entry.faultId ? ` fault=${entry.faultId}` : \"\"\n const adopted = entry.adopted ? \" adopted\" : \"\"\n // A rejection says what arrived (never the body) and why the service refused it.\n const received = entry.request\n ? ` received=${entry.request.bodyBytes === null ? \"?\" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? \"-\"}${\n entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : \"\"\n }`\n : \"\"\n const issues = entry.issues?.length\n ? ` issues=${entry.issues\n .slice(0, 3)\n .map((issue) => `${issue.path || \"$\"}: ${issue.message}`)\n .join(\"; \")}${entry.issues.length > 3 ? ` (+${entry.issues.length - 3})` : \"\"}`\n : \"\"\n console.log(\n `${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`,\n )\n }\n}\n\nconst asString = (value: string | boolean | undefined): string | undefined =>\n typeof value === \"string\" ? value : undefined\n\n/** A service entry in `mockingbird.json`. */\nexport type ConfigService = {\n port?: number\n host?: string\n adminKey?: string\n seed?: string\n /** Service-specific serve flags, by long name: `{ \"webhook-url\": \"\u2026\" }`. */\n options?: Record<string, string | boolean>\n}\n\nexport type MockingbirdConfig = {\n /** Keyed by service name: `junction` loads `@crvouga/mockingbird-service-junction`. */\n services: Record<string, ConfigService>\n log?: LogFormat\n}\n\nconst loadTarget = async (name: string, own: ServeTarget): Promise<ServeTarget> => {\n if (name === own.name) return own\n const specifier = `@crvouga/mockingbird-service-${name}/server`\n try {\n const mod = (await import(specifier)) as { serveTarget?: ServeTarget }\n if (!mod.serveTarget) throw new Error(`${specifier} exports no serveTarget`)\n return mod.serveTarget\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n throw new Error(\n `cannot load service \"${name}\": ${reason}. Install @crvouga/mockingbird-service-${name}.`,\n )\n }\n}\n\nconst start = async (\n target: ServeTarget,\n values: CliValues,\n config: { port: number; host: string; adminKey?: string; seed?: string; log: LogFormat },\n): Promise<Listening> => {\n const runtime = await target.create(values, {\n adminKey: config.adminKey,\n seed: config.seed,\n onLog: formatLog(config.log),\n })\n const listening = await listen(runtime, { port: config.port, host: config.host })\n target.listening?.(listening)\n console.log(`${target.name} mock listening on ${listening.url}`)\n console.log(`${target.name} health: GET ${listening.url}/health`)\n console.log(\n `${target.name} admin: ${listening.url}/__admin (${config.adminKey ? \"x-mockingbird-admin-key required\" : \"open \u2014 pass --admin-key to lock\"})`,\n )\n console.log(`${target.name} admin ui: ${listening.url}/__admin/ui`)\n for (const line of target.banner?.(runtime) ?? []) console.log(`${target.name} ${line}`)\n return listening\n}\n\nconst untilSignal = async (servers: Listening[]): Promise<number> =>\n new Promise((resolve) => {\n const stop = async () => {\n await Promise.allSettled(servers.map((s) => s.close()))\n resolve(0)\n }\n process.once(\"SIGINT\", stop)\n process.once(\"SIGTERM\", stop)\n })\n\n/** The standard `serve` command for a service, including multi-service `--config`. */\nexport const serveCommand = (target: ServeTarget): CliCommand => ({\n summary: `Serve the ${target.name} mock over HTTP`,\n options: { ...COMMON_SERVE_OPTIONS, ...target.options },\n async run(values) {\n const log = (\n values[\"log-requests\"] === true ? \"json\" : (asString(values.log) ?? \"pretty\")\n ) as LogFormat\n if (![\"pretty\", \"json\", \"off\"].includes(log)) {\n console.error(`--log must be pretty, json or off (got ${log})`)\n return 2\n }\n const configPath = asString(values.config)\n if (configPath !== undefined) {\n const config = JSON.parse(await readFile(configPath, \"utf8\")) as MockingbirdConfig\n const servers: Listening[] = []\n try {\n for (const [name, entry] of Object.entries(config.services ?? {})) {\n const each = await loadTarget(name, target)\n servers.push(\n await start(each, entry.options ?? {}, {\n port: entry.port ?? each.defaultPort,\n host: entry.host ?? \"127.0.0.1\",\n ...(entry.adminKey !== undefined ? { adminKey: entry.adminKey } : {}),\n ...(entry.seed !== undefined ? { seed: entry.seed } : {}),\n log: config.log ?? log,\n }),\n )\n }\n } catch (error) {\n await Promise.allSettled(servers.map((s) => s.close()))\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal(servers)\n }\n const port = asString(values.port)\n const adminKey = asString(values[\"admin-key\"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY\n const seed = asString(values.seed)\n let listening: Listening\n try {\n listening = await start(target, values, {\n port: port === undefined ? target.defaultPort : Number.parseInt(port, 10),\n host: asString(values.host) ?? \"127.0.0.1\",\n ...(adminKey !== undefined ? { adminKey } : {}),\n ...(seed !== undefined ? { seed } : {}),\n log,\n })\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n return 1\n }\n return untilSignal([listening])\n },\n})\n", "import { createServer } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\n\n/** Options for {@link serve}. */\nexport type NodeServeOptions = {\n port?: number\n host?: string\n}\n\n/**\n * Serve any Mockingbird {@link FetchAPI} over `node:http`.\n * Port defaults to `0`, so the OS assigns an ephemeral port (read from `server.address()`).\n */\nexport const serve = async (api: FetchAPI, options: NodeServeOptions = {}) => {\n const server = createServer(async (req, res) => {\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n chunks.push(Buffer.from(chunk))\n }\n const body = Buffer.concat(chunks)\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : undefined\n const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`\n const raw = req.url ?? \"/\"\n const url = new URL(raw.replace(/^\\/+/, \"/\"), base)\n const method = req.method ?? \"GET\"\n const init: RequestInit = { method, headers: req.headers as Record<string, string> }\n if (method !== \"GET\" && method !== \"HEAD\" && body.length > 0) {\n init.body = body\n }\n const aborted = new AbortController()\n res.once(\"close\", () => {\n if (!res.writableFinished) aborted.abort()\n })\n init.signal = aborted.signal\n const request = new Request(url, init)\n let response: Response\n try {\n response = await api.fetch(request)\n } catch (error) {\n // A `drop` fault: destroy the socket so the client sees the connection die.\n if ((error as { code?: string }).code === \"MOCKINGBIRD_DROP\") {\n req.socket.destroy()\n return\n }\n res.writeHead(500, { \"content-type\": \"application/json\" })\n res.end(\n JSON.stringify({\n error: {\n type: \"mockingbird_internal\",\n message: error instanceof Error ? error.message : String(error),\n },\n }),\n )\n return\n }\n // Headers#entries() joins repeated headers; Set-Cookie must stay one header per cookie.\n const headers: Record<string, string | string[]> = Object.fromEntries(response.headers)\n const cookies = response.headers.getSetCookie()\n if (cookies.length > 0) headers[\"set-cookie\"] = cookies\n if (!response.body) {\n res.writeHead(response.status, headers)\n res.end()\n return\n }\n // Stream the body chunk by chunk: event streams and long-polls must not be buffered.\n res.writeHead(response.status, headers)\n res.flushHeaders()\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (!res.write(value)) await new Promise<void>((resolve) => res.once(\"drain\", resolve))\n }\n res.end()\n } catch {\n res.destroy()\n } finally {\n reader.releaseLock()\n }\n })\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject)\n server.listen(options.port ?? 0, options.host, resolve)\n })\n return server\n}\n", "import type { Server } from \"node:http\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\nimport { serve } from \"./serve.js\"\n\nexport type ListenOptions = {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`: a mock should not be reachable off the machine by accident. */\n host?: string\n}\n\n/** A running server, with the address it actually bound. */\nexport type Listening = {\n url: string\n port: number\n host: string\n server: Server\n close(): Promise<void>\n}\n\nexport const listen = async (api: FetchAPI, options: ListenOptions = {}): Promise<Listening> => {\n const host = options.host ?? \"127.0.0.1\"\n const server = await serve(api, { port: options.port ?? 0, host })\n const address = server.address()\n const port = typeof address === \"object\" && address !== null ? address.port : (options.port ?? 0)\n const shown = host.includes(\":\") ? `[${host}]` : host\n return {\n url: `http://${shown}:${port}`,\n port,\n host,\n server,\n close: () =>\n new Promise<void>((resolve, reject) => {\n // Stop accepting first, then drop keep-alive sockets so close() can finish.\n server.close((error) => {\n if (error && (error as { code?: string }).code !== \"ERR_SERVER_NOT_RUNNING\") reject(error)\n else resolve()\n })\n server.closeAllConnections?.()\n }),\n }\n}\n", "/// <reference types=\"node\" />\nimport { type Listening, listen, type ServeTarget } from \"@crvouga/mockingbird-adapter-node\"\nimport { createRuntime, type FormbricksRuntime, type FormbricksRuntimeOptions } from \"./runtime.js\"\nimport { DEFAULT_SETTINGS } from \"./state.js\"\n\n/** Port `mockingbird-formbricks serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8813\n\nexport type FormbricksServerOptions = FormbricksRuntimeOptions & {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`. */\n host?: string\n}\n\nexport type FormbricksServer = Listening & { runtime: FormbricksRuntime }\n\n/** Serve the Formbricks mock over `node:http`. */\nexport const createServer = async (\n options: FormbricksServerOptions = {},\n): Promise<FormbricksServer> => {\n const { port, host, ...rest } = options\n const runtime = createRuntime(rest)\n const listening = await listen(runtime, {\n port: port ?? 0,\n ...(host !== undefined ? { host } : {}),\n })\n return { ...listening, runtime }\n}\n\nconst text = (value: string | boolean | undefined) =>\n typeof value === \"string\" ? value : undefined\n\n/** How `serve` (and `serve --config`) builds the Formbricks mock from flags. */\nexport const serveTarget: ServeTarget = {\n name: \"formbricks\",\n defaultPort: DEFAULT_PORT,\n options: {\n \"webhook-url\": {\n type: \"string\",\n value: \"<url>\",\n description: \"Deliver webhooks here (e.g. http://127.0.0.1:3000/webhooks/formbricks)\",\n },\n \"webhook-token\": {\n type: \"string\",\n value: \"<token>\",\n description: \"Appended to the webhook url as ?token= (for receivers that check one)\",\n },\n \"webhook-secret\": {\n type: \"string\",\n value: \"<whsec_\u2026>\",\n description: \"Standard Webhooks key that signs webhook-signature (optional)\",\n },\n \"workspace-id\": {\n type: \"string\",\n value: \"<id>\",\n description: \"An extra workspace id that serves the survey corpus\",\n },\n },\n create: (values, common) => {\n const url = text(values[\"webhook-url\"])\n const token = text(values[\"webhook-token\"])\n const secret = text(values[\"webhook-secret\"])\n const workspace = text(values[\"workspace-id\"])\n const target =\n url && token\n ? `${url}${url.includes(\"?\") ? \"&\" : \"?\"}token=${encodeURIComponent(token)}`\n : url\n return createRuntime({\n ...(target ? { webhooks: { url: target, ...(secret ? { secret } : {}) } } : {}),\n ...(workspace\n ? { settings: { workspaces: [workspace, ...DEFAULT_SETTINGS.workspaces] } }\n : {}),\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\n ...(common.seed !== undefined ? { seed: common.seed } : {}),\n ...(common.onLog ? { onLog: common.onLog } : {}),\n })\n },\n banner: () => [\n `point the SDK's appUrl at this url; workspace ${DEFAULT_SETTINGS.workspaces[0]} (legacy environment ${Object.keys(DEFAULT_SETTINGS.legacyEnvironmentIds)[0]}) serves the survey corpus`,\n \"management API: x-api-key <any>; namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<workspace id | api key>: <ns>}\",\n ],\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;AAAA,SAAS,gBAAgB;AACzB,SAA+B,iBAAiB;;;ACDhD,SAAS,oBAAoB;AAatB,IAAM,QAAQ,OAAO,KAAe,UAA4B,CAAA,MAAM;AAC3E,QAAM,SAAS,aAAa,OAAO,KAAK,QAAO;AAC7C,UAAM,SAAmB,CAAA;AACzB,qBAAiB,SAAS,KAAK;AAC7B,aAAO,KAAK,OAAO,KAAK,KAAK,CAAC;IAChC;AACA,UAAM,OAAO,OAAO,OAAO,MAAM;AACjC,UAAM,UAAU,OAAO,QAAO;AAC9B,UAAM,OAAO,OAAO,YAAY,YAAY,YAAY,OAAO,QAAQ,OAAO;AAC9E,UAAM,OAAO,UAAU,IAAI,QAAQ,QAAQ,aAAa,QAAQ,EAAE,EAAE;AACpE,UAAM,MAAM,IAAI,OAAO;AACvB,UAAM,MAAM,IAAI,IAAI,IAAI,QAAQ,QAAQ,GAAG,GAAG,IAAI;AAClD,UAAM,SAAS,IAAI,UAAU;AAC7B,UAAM,OAAoB,EAAE,QAAQ,SAAS,IAAI,QAAiC;AAClF,QAAI,WAAW,SAAS,WAAW,UAAU,KAAK,SAAS,GAAG;AAC5D,WAAK,OAAO;IACd;AACA,UAAM,UAAU,IAAI,gBAAe;AACnC,QAAI,KAAK,SAAS,MAAK;AACrB,UAAI,CAAC,IAAI;AAAkB,gBAAQ,MAAK;IAC1C,CAAC;AACD,SAAK,SAAS,QAAQ;AACtB,UAAM,UAAU,IAAI,QAAQ,KAAK,IAAI;AACrC,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,IAAI,MAAM,OAAO;IACpC,SAAS,OAAO;AAEd,UAAK,MAA4B,SAAS,oBAAoB;AAC5D,YAAI,OAAO,QAAO;AAClB;MACF;AACA,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAkB,CAAE;AACzD,UAAI,IACF,KAAK,UAAU;QACb,OAAO;UACL,MAAM;UACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;;OAEjE,CAAC;AAEJ;IACF;AAEA,UAAM,UAA6C,OAAO,YAAY,SAAS,OAAO;AACtF,UAAM,UAAU,SAAS,QAAQ,aAAY;AAC7C,QAAI,QAAQ,SAAS;AAAG,cAAQ,YAAY,IAAI;AAChD,QAAI,CAAC,SAAS,MAAM;AAClB,UAAI,UAAU,SAAS,QAAQ,OAAO;AACtC,UAAI,IAAG;AACP;IACF;AAEA,QAAI,UAAU,SAAS,QAAQ,OAAO;AACtC,QAAI,aAAY;AAChB,UAAM,SAAS,SAAS,KAAK,UAAS;AACtC,QAAI;AACF,iBAAS;AACP,cAAM,EAAE,MAAM,MAAK,IAAK,MAAM,OAAO,KAAI;AACzC,YAAI;AAAM;AACV,YAAI,CAAC,IAAI,MAAM,KAAK;AAAG,gBAAM,IAAI,QAAc,CAAC,YAAY,IAAI,KAAK,SAAS,OAAO,CAAC;MACxF;AACA,UAAI,IAAG;IACT,QAAQ;AACN,UAAI,QAAO;IACb;AACE,aAAO,YAAW;IACpB;EACF,CAAC;AACD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAU;AAC1C,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,QAAQ,QAAQ,GAAG,QAAQ,MAAM,OAAO;EACxD,CAAC;AACD,SAAO;AACT;;;ACnEO,IAAM,SAAS,OAAO,KAAe,UAAyB,CAAA,MAA0B;AAC7F,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,SAAS,MAAM,MAAM,KAAK,EAAE,MAAM,QAAQ,QAAQ,GAAG,KAAI,CAAE;AACjE,QAAM,UAAU,OAAO,QAAO;AAC9B,QAAM,OAAO,OAAO,YAAY,YAAY,YAAY,OAAO,QAAQ,OAAQ,QAAQ,QAAQ;AAC/F,QAAM,QAAQ,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,MAAM;AACjD,SAAO;IACL,KAAK,UAAU,KAAK,IAAI,IAAI;IAC5B;IACA;IACA;IACA,OAAO,MACL,IAAI,QAAc,CAAC,SAAS,WAAU;AAEpC,aAAO,MAAM,CAAC,UAAS;AACrB,YAAI,SAAU,MAA4B,SAAS;AAA0B,iBAAO,KAAK;;AACpF,kBAAO;MACd,CAAC;AACD,aAAO,sBAAqB;IAC9B,CAAC;;AAEP;;;AFZA,IAAM,aAAa,CAAC,YAClB,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,MAAM,MAAK;AAC7C,QAAM,OAAO,KAAK,IAAI,GAAG,OAAO,SAAS,WAAW,IAAI,OAAO,SAAS,SAAS,KAAK,EAAE;AACxF,QAAM,WAAW,OAAO,YAAY,SAAY,cAAc,OAAO,OAAO,OAAO,CAAC,MAAM;AAC1F,SAAO,KAAK,KAAK,OAAO,EAAE,CAAC,IAAI,OAAO,WAAW,GAAG,QAAQ;AAC9D,CAAC;AAEH,IAAM,OAAO,CAAC,SACZ;EACE,GAAG,KAAK,GAAG,WAAM,KAAK,WAAW;EACjC;EACA;EACA,KAAK,KAAK,GAAG;EACb;EACA;EACA,GAAG,OAAO,QAAQ,KAAK,QAAQ,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,KAAK,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE;EACvF;EACA,SAAS,KAAK,GAAG;EACjB,KAAK,IAAI;AAEb,IAAM,cAAc,CAAC,MAAe,MAAc,YAChD;EACE,GAAG,KAAK,GAAG,IAAI,IAAI,WAAM,QAAQ,OAAO;EACxC;EACA;EACA,KAAK,QAAQ,SAAS,GAAG,KAAK,GAAG,IAAI,IAAI,YAAY;EACrD,GAAI,QAAQ,UAAU,CAAC,IAAI,YAAY,GAAG,WAAW,QAAQ,OAAO,CAAC,IAAI,CAAA;EACzE,KAAK,IAAI;AAGN,IAAM,SAAS,OAAO,MAAe,SAAmC;AAE7E,QAAM,OAAO,KAAK,UAAU,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK;AAC1D,QAAM,QAAQ,SAAS,UAAa,KAAK,SAAS,IAAI,IAAI,IAAI;AAC9D,QAAM,OAAO,UAAU,IAAI,OAAO,KAAK,CAAC;AACxC,QAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,MAAI,SAAS,UAAa,SAAS,YAAY,SAAS,QAAQ,SAAS,QAAQ;AAC/E,YAAQ,IAAI,KAAK,IAAI,CAAC;AACtB,WAAO;EACT;AACA,QAAM,UAAU,KAAK,SAAS,IAAI;AAClC,MAAI,CAAC,SAAS;AACZ,YAAQ,MAAM,GAAG,KAAK,GAAG,qBAAqB,KAAK,UAAU,IAAI,CAAC;;EAAO,KAAK,IAAI,CAAC,EAAE;AACrF,WAAO;EACT;AACA,MAAI,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,IAAI,GAAG;AAClD,YAAQ,IAAI,YAAY,MAAM,MAAM,OAAO,CAAC;AAC5C,WAAO;EACT;AACA,MAAI;AACJ,MAAI;AACF,aAAS,UAAU;MACjB,MAAM;MACN,kBAAkB;MAClB,QAAQ;MACR,SAAS,OAAO,YACd,OAAO,QAAQ,QAAQ,WAAW,CAAA,CAAE,EAAE,IAAI,CAAC,CAAC,KAAK,MAAM,MAAM;QAC3D;QACA;UACE,MAAM,OAAO;UACb,GAAI,OAAO,YAAY,SAAY,EAAE,SAAS,OAAO,QAAO,IAAK,CAAA;;OAEpE,CAAC;KAEL;EACH,SAAS,OAAO;AACd,YAAQ,MACN,GAAG,KAAK,GAAG,IAAI,IAAI,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;;EAAO,YAAY,MAAM,MAAM,OAAO,CAAC,EAAE;AAEzH,WAAO;EACT;AACA,SAAO,QAAQ,IAAI,OAAO,QAAQ,OAAO,WAAW;AACtD;AAgCA,IAAM,uBAAkD;EACtD,MAAM,EAAE,MAAM,UAAU,OAAO,UAAU,aAAa,oBAAmB;EACzE,MAAM,EAAE,MAAM,UAAU,OAAO,UAAU,aAAa,qBAAqB,SAAS,YAAW;EAC/F,aAAa;IACX,MAAM;IACN,OAAO;IACP,aAAa;;EAEf,MAAM,EAAE,MAAM,UAAU,OAAO,UAAU,aAAa,+BAA8B;EACpF,KAAK;IACH,MAAM;IACN,OAAO;IACP,aAAa;IACb,SAAS;;EAEX,gBAAgB;IACd,MAAM;IACN,aACE;;EAEJ,QAAQ;IACN,MAAM;IACN,OAAO;IACP,aAAa;;;AAIjB,IAAM,YAAY,CAAC,WAAqB;AACtC,MAAI,WAAW;AAAO,WAAO;AAC7B,MAAI,WAAW;AAAQ,WAAO,CAAC,UAAsB,QAAQ,IAAI,KAAK,UAAU,KAAK,CAAC;AACtF,SAAO,CAAC,UAAqB;AAC3B,UAAM,KAAK,MAAM,gBAAgB,MAAM,YAAY,cAAc;AACjE,UAAM,KAAK,MAAM,cAAc,YAAY,KAAK,KAAK,MAAM,SAAS;AACpE,UAAM,QAAQ,MAAM,UAAU,UAAU,MAAM,OAAO,KAAK;AAC1D,UAAM,UAAU,MAAM,UAAU,aAAa;AAE7C,UAAM,WAAW,MAAM,UACnB,aAAa,MAAM,QAAQ,cAAc,OAAO,MAAM,GAAG,MAAM,QAAQ,SAAS,GAAG,OAAO,MAAM,QAAQ,eAAe,GAAG,GACxH,MAAM,QAAQ,mBAAmB,OAAO,MAAM,QAAQ,gBAAgB,KAAK,EAC7E,KACA;AACJ,UAAM,SAAS,MAAM,QAAQ,SACzB,WAAW,MAAM,OACd,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,UAAU,GAAG,MAAM,QAAQ,GAAG,KAAK,MAAM,OAAO,EAAE,EACvD,KAAK,IAAI,CAAC,GAAG,MAAM,OAAO,SAAS,IAAI,MAAM,MAAM,OAAO,SAAS,CAAC,MAAM,EAAE,KAC/E;AACJ,YAAQ,IACN,GAAG,MAAM,OAAO,IAAI,MAAM,MAAM,IAAI,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI,EAAE,IAAI,MAAM,UAAU,KAAK,EAAE,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,EAAE;EAE3I;AACF;AAEA,IAAM,WAAW,CAAC,UAChB,OAAO,UAAU,WAAW,QAAQ;AAkBtC,IAAM,aAAa,OAAO,MAAc,QAA0C;AAChF,MAAI,SAAS,IAAI;AAAM,WAAO;AAC9B,QAAM,YAAY,gCAAgC,IAAI;AACtD,MAAI;AACF,UAAM,MAAO,MAAM,OAAO;AAC1B,QAAI,CAAC,IAAI;AAAa,YAAM,IAAI,MAAM,GAAG,SAAS,yBAAyB;AAC3E,WAAO,IAAI;EACb,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,IAAI,MACR,wBAAwB,IAAI,MAAM,MAAM,0CAA0C,IAAI,GAAG;EAE7F;AACF;AAEA,IAAM,QAAQ,OACZ,QACA,QACA,WACsB;AACtB,QAAM,UAAU,MAAM,OAAO,OAAO,QAAQ;IAC1C,UAAU,OAAO;IACjB,MAAM,OAAO;IACb,OAAO,UAAU,OAAO,GAAG;GAC5B;AACD,QAAM,YAAY,MAAM,OAAO,SAAS,EAAE,MAAM,OAAO,MAAM,MAAM,OAAO,KAAI,CAAE;AAChF,SAAO,YAAY,SAAS;AAC5B,UAAQ,IAAI,GAAG,OAAO,IAAI,sBAAsB,UAAU,GAAG,EAAE;AAC/D,UAAQ,IAAI,GAAG,OAAO,IAAI,gBAAgB,UAAU,GAAG,SAAS;AAChE,UAAQ,IACN,GAAG,OAAO,IAAI,WAAW,UAAU,GAAG,aAAa,OAAO,WAAW,qCAAqC,sCAAiC,GAAG;AAEhJ,UAAQ,IAAI,GAAG,OAAO,IAAI,cAAc,UAAU,GAAG,aAAa;AAClE,aAAW,QAAQ,OAAO,SAAS,OAAO,KAAK,CAAA;AAAI,YAAQ,IAAI,GAAG,OAAO,IAAI,IAAI,IAAI,EAAE;AACvF,SAAO;AACT;AAEA,IAAM,cAAc,OAAO,YACzB,IAAI,QAAQ,CAAC,YAAW;AACtB,QAAM,OAAO,YAAW;AACtB,UAAM,QAAQ,WAAW,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAK,CAAE,CAAC;AACtD,YAAQ,CAAC;EACX;AACA,UAAQ,KAAK,UAAU,IAAI;AAC3B,UAAQ,KAAK,WAAW,IAAI;AAC9B,CAAC;AAGI,IAAM,eAAe,CAAC,YAAqC;EAChE,SAAS,aAAa,OAAO,IAAI;EACjC,SAAS,EAAE,GAAG,sBAAsB,GAAG,OAAO,QAAO;EACrD,MAAM,IAAI,QAAM;AACd,UAAM,MACJ,OAAO,cAAc,MAAM,OAAO,SAAU,SAAS,OAAO,GAAG,KAAK;AAEtE,QAAI,CAAC,CAAC,UAAU,QAAQ,KAAK,EAAE,SAAS,GAAG,GAAG;AAC5C,cAAQ,MAAM,0CAA0C,GAAG,GAAG;AAC9D,aAAO;IACT;AACA,UAAM,aAAa,SAAS,OAAO,MAAM;AACzC,QAAI,eAAe,QAAW;AAC5B,YAAM,SAAS,KAAK,MAAM,MAAM,SAAS,YAAY,MAAM,CAAC;AAC5D,YAAM,UAAuB,CAAA;AAC7B,UAAI;AACF,mBAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,YAAY,CAAA,CAAE,GAAG;AACjE,gBAAM,OAAO,MAAM,WAAW,MAAM,MAAM;AAC1C,kBAAQ,KACN,MAAM,MAAM,MAAM,MAAM,WAAW,CAAA,GAAI;YACrC,MAAM,MAAM,QAAQ,KAAK;YACzB,MAAM,MAAM,QAAQ;YACpB,GAAI,MAAM,aAAa,SAAY,EAAE,UAAU,MAAM,SAAQ,IAAK,CAAA;YAClE,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,MAAM,KAAI,IAAK,CAAA;YACtD,KAAK,OAAO,OAAO;WACpB,CAAC;QAEN;MACF,SAAS,OAAO;AACd,cAAM,QAAQ,WAAW,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAK,CAAE,CAAC;AACtD,gBAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AACpE,eAAO;MACT;AACA,aAAO,YAAY,OAAO;IAC5B;AACA,UAAM,OAAO,SAAS,OAAO,IAAI;AACjC,UAAM,WAAW,SAAS,OAAO,WAAW,CAAC,KAAK,QAAQ,IAAI;AAC9D,UAAM,OAAO,SAAS,OAAO,IAAI;AACjC,QAAI;AACJ,QAAI;AACF,kBAAY,MAAM,MAAM,QAAQ,QAAQ;QACtC,MAAM,SAAS,SAAY,OAAO,cAAc,OAAO,SAAS,MAAM,EAAE;QACxE,MAAM,SAAS,OAAO,IAAI,KAAK;QAC/B,GAAI,aAAa,SAAY,EAAE,SAAQ,IAAK,CAAA;QAC5C,GAAI,SAAS,SAAY,EAAE,KAAI,IAAK,CAAA;QACpC;OACD;IACH,SAAS,OAAO;AACd,cAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AACpE,aAAO;IACT;AACA,WAAO,YAAY,CAAC,SAAS,CAAC;EAChC;;;;AG3SK,IAAM,eAAe;AAYrB,IAAMA,gBAAe,OAC1B,UAAmC,CAAC,MACN;AAC9B,QAAM,EAAE,MAAM,MAAM,GAAG,KAAK,IAAI;AAChC,QAAM,UAAU,cAAc,IAAI;AAClC,QAAM,YAAY,MAAM,OAAO,SAAS;AAAA,IACtC,MAAM,QAAQ;AAAA,IACd,GAAI,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC;AAAA,EACvC,CAAC;AACD,SAAO,EAAE,GAAG,WAAW,QAAQ;AACjC;AAEA,IAAM,OAAO,CAAC,UACZ,OAAO,UAAU,WAAW,QAAQ;AAG/B,IAAM,cAA2B;AAAA,EACtC,MAAM;AAAA,EACN,aAAa;AAAA,EACb,SAAS;AAAA,IACP,eAAe;AAAA,MACb,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,iBAAiB;AAAA,MACf,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,kBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,gBAAgB;AAAA,MACd,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,QAAQ,CAAC,QAAQ,WAAW;AAC1B,UAAM,MAAM,KAAK,OAAO,aAAa,CAAC;AACtC,UAAM,QAAQ,KAAK,OAAO,eAAe,CAAC;AAC1C,UAAM,SAAS,KAAK,OAAO,gBAAgB,CAAC;AAC5C,UAAM,YAAY,KAAK,OAAO,cAAc,CAAC;AAC7C,UAAM,SACJ,OAAO,QACH,GAAG,GAAG,GAAG,IAAI,SAAS,GAAG,IAAI,MAAM,GAAG,SAAS,mBAAmB,KAAK,CAAC,KACxE;AACN,WAAO,cAAc;AAAA,MACnB,GAAI,SAAS,EAAE,UAAU,EAAE,KAAK,QAAQ,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,EAAG,EAAE,IAAI,CAAC;AAAA,MAC7E,GAAI,YACA,EAAE,UAAU,EAAE,YAAY,CAAC,WAAW,GAAG,iBAAiB,UAAU,EAAE,EAAE,IACxE,CAAC;AAAA,MACL,GAAI,OAAO,aAAa,SAAY,EAAE,UAAU,OAAO,SAAS,IAAI,CAAC;AAAA,MACrE,GAAI,OAAO,SAAS,SAAY,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;AAAA,MACzD,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;AAAA,IAChD,CAAC;AAAA,EACH;AAAA,EACA,QAAQ,MAAM;AAAA,IACZ,iDAAiD,iBAAiB,WAAW,CAAC,CAAC,wBAAwB,OAAO,KAAK,iBAAiB,oBAAoB,EAAE,CAAC,CAAC;AAAA,IAC5J;AAAA,EACF;AACF;",
|
|
6
|
+
"names": ["createServer"]
|
|
7
|
+
}
|
|
@@ -3467,7 +3467,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
3467
3467
|
};
|
|
3468
3468
|
|
|
3469
3469
|
// ../core/dist/version.js
|
|
3470
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3470
|
+
var PACKAGE_VERSION = true ? "1.6.0" : UNRELEASED_VERSION;
|
|
3471
3471
|
|
|
3472
3472
|
// ../core/dist/signing.js
|
|
3473
3473
|
var encoder = new TextEncoder();
|
|
@@ -5232,7 +5232,8 @@ var DEFAULT_SETTINGS = {
|
|
|
5232
5232
|
legacyEnvironmentIds: { [ENVIRONMENT_ID]: WORKSPACE_ID },
|
|
5233
5233
|
apiKeys: [],
|
|
5234
5234
|
webhookId: "cwebhook00000000000000001",
|
|
5235
|
-
contactsEnabled: false
|
|
5235
|
+
contactsEnabled: false,
|
|
5236
|
+
validation: "present-only"
|
|
5236
5237
|
};
|
|
5237
5238
|
var CORPUS_SURVEYS = surveys_default.surveys;
|
|
5238
5239
|
var workspaceSettings = (workspaceId) => ({
|
|
@@ -5367,10 +5368,40 @@ var invalidOption = (element, value, language) => {
|
|
|
5367
5368
|
const submitted = Array.isArray(value) ? value : [value];
|
|
5368
5369
|
return submitted.some((v) => v !== "" && (typeof v !== "string" || !known.has(v)));
|
|
5369
5370
|
};
|
|
5370
|
-
var
|
|
5371
|
-
|
|
5371
|
+
var MAX_OTHER_OPTION_LENGTH = 250;
|
|
5372
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5373
|
+
var localizedLabel = (label, language) => {
|
|
5374
|
+
if (!isRecord6(label) || !Object.hasOwn(label, "default")) return "";
|
|
5375
|
+
const text = label[language];
|
|
5376
|
+
return typeof text === "string" && text !== "" ? text : "";
|
|
5377
|
+
};
|
|
5378
|
+
var matchesChoiceLabel = (choices, value, language) => choices.some(
|
|
5379
|
+
(choice) => isRecord6(choice) && isRecord6(choice.label) && localizedLabel(choice.label, language) === value
|
|
5380
|
+
);
|
|
5381
|
+
var entryOverLimit = (value, choices, language) => !matchesChoiceLabel(choices, value, language) && value.length > MAX_OTHER_OPTION_LENGTH;
|
|
5382
|
+
var answerOverLimit = (answer, choices, language) => {
|
|
5383
|
+
if (typeof answer === "string") return entryOverLimit(answer, choices, language);
|
|
5384
|
+
if (!Array.isArray(answer)) return false;
|
|
5385
|
+
return answer.some((item) => typeof item === "string" && entryOverLimit(item, choices, language));
|
|
5386
|
+
};
|
|
5387
|
+
var otherOptionOverLimit = (survey, data, language) => {
|
|
5388
|
+
const responseLanguage = language ?? "default";
|
|
5389
|
+
for (const [questionId, answer] of Object.entries(data)) {
|
|
5390
|
+
const question = surveyElements(survey).find((element) => element.id === questionId);
|
|
5391
|
+
if (!question?.choices) continue;
|
|
5392
|
+
if (question.type !== "multipleChoiceSingle" && question.type !== "multipleChoiceMulti") {
|
|
5393
|
+
continue;
|
|
5394
|
+
}
|
|
5395
|
+
if (answerOverLimit(answer, question.choices, responseLanguage)) return questionId;
|
|
5396
|
+
}
|
|
5397
|
+
return void 0;
|
|
5398
|
+
};
|
|
5399
|
+
var validateResponseData = (survey, data, language = "en", options = {}) => {
|
|
5400
|
+
const elements = surveyElements(survey);
|
|
5401
|
+
const presentIds = new Set(Object.keys(data));
|
|
5402
|
+
const selected = options.validation === "finished-validates-all" && options.finished === true ? elements : elements.filter((element) => presentIds.has(element.id));
|
|
5372
5403
|
const errors = {};
|
|
5373
|
-
for (const element of
|
|
5404
|
+
for (const element of selected) {
|
|
5374
5405
|
const value = data[element.id];
|
|
5375
5406
|
const messages = [];
|
|
5376
5407
|
if (requiredError(element, value)) messages.push(REQUIRED);
|
|
@@ -5467,7 +5498,7 @@ var FORMBRICKS_PRESETS = {
|
|
|
5467
5498
|
};
|
|
5468
5499
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
5469
5500
|
var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
|
|
5470
|
-
var
|
|
5501
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5471
5502
|
var adminRoutes = (runtime) => ({
|
|
5472
5503
|
"GET /responses": ({ url, namespace }) => {
|
|
5473
5504
|
const surveyId = url.searchParams.get("surveyId");
|
|
@@ -5477,11 +5508,11 @@ var adminRoutes = (runtime) => ({
|
|
|
5477
5508
|
},
|
|
5478
5509
|
"GET /surveys": ({ namespace }) => json5(200, { surveys: runtime.instance(namespace).state.allSurveys() }),
|
|
5479
5510
|
"PUT /surveys": ({ body, namespace }) => {
|
|
5480
|
-
const list = Array.isArray(body) ? body :
|
|
5511
|
+
const list = Array.isArray(body) ? body : isRecord7(body) ? body.surveys : void 0;
|
|
5481
5512
|
if (!Array.isArray(list)) return adminError4(400, "expected [survey] or {surveys: [survey]}");
|
|
5482
5513
|
const state = runtime.instance(namespace).state;
|
|
5483
5514
|
for (const [index, survey] of list.entries()) {
|
|
5484
|
-
if (!
|
|
5515
|
+
if (!isRecord7(survey) || typeof survey.id !== "string" || typeof survey.name !== "string") {
|
|
5485
5516
|
return adminError4(400, `surveys[${index}]: id and name are required`);
|
|
5486
5517
|
}
|
|
5487
5518
|
}
|
|
@@ -5501,16 +5532,16 @@ var adminRoutes = (runtime) => ({
|
|
|
5501
5532
|
contacts: runtime.instance(namespace).state.contacts.list({ order: "oldest" }).map((row) => row.value)
|
|
5502
5533
|
}),
|
|
5503
5534
|
"PUT /contacts": ({ body, namespace }) => {
|
|
5504
|
-
const list = Array.isArray(body) ? body :
|
|
5535
|
+
const list = Array.isArray(body) ? body : isRecord7(body) ? body.contacts : void 0;
|
|
5505
5536
|
if (!Array.isArray(list)) return adminError4(400, "expected [contact] or {contacts: [contact]}");
|
|
5506
5537
|
const state = runtime.instance(namespace).state;
|
|
5507
5538
|
for (const [index, contact] of list.entries()) {
|
|
5508
|
-
if (!
|
|
5539
|
+
if (!isRecord7(contact) || typeof contact.id !== "string") {
|
|
5509
5540
|
return adminError4(400, `contacts[${index}]: id is required`);
|
|
5510
5541
|
}
|
|
5511
5542
|
}
|
|
5512
5543
|
for (const contact of list) {
|
|
5513
|
-
const attributes =
|
|
5544
|
+
const attributes = isRecord7(contact.attributes) ? Object.fromEntries(Object.entries(contact.attributes).map(([k, v]) => [k, String(v)])) : {};
|
|
5514
5545
|
if (typeof contact.userId === "string") attributes.userId = contact.userId;
|
|
5515
5546
|
state.contacts.insert(contact.id, {
|
|
5516
5547
|
id: contact.id,
|
|
@@ -5522,10 +5553,10 @@ var adminRoutes = (runtime) => ({
|
|
|
5522
5553
|
},
|
|
5523
5554
|
"GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
|
|
5524
5555
|
"PUT /settings": ({ body, namespace }) => {
|
|
5525
|
-
if (!
|
|
5556
|
+
if (!isRecord7(body)) return adminError4(400, "expected a JSON object");
|
|
5526
5557
|
const patch = {};
|
|
5527
5558
|
if (Array.isArray(body.workspaces)) patch.workspaces = body.workspaces.map(String);
|
|
5528
|
-
if (
|
|
5559
|
+
if (isRecord7(body.legacyEnvironmentIds)) {
|
|
5529
5560
|
patch.legacyEnvironmentIds = Object.fromEntries(
|
|
5530
5561
|
Object.entries(body.legacyEnvironmentIds).map(([k, v]) => [k, String(v)])
|
|
5531
5562
|
);
|
|
@@ -5533,6 +5564,12 @@ var adminRoutes = (runtime) => ({
|
|
|
5533
5564
|
if (Array.isArray(body.apiKeys)) patch.apiKeys = body.apiKeys.map(String);
|
|
5534
5565
|
if (typeof body.webhookId === "string") patch.webhookId = body.webhookId;
|
|
5535
5566
|
if (typeof body.contactsEnabled === "boolean") patch.contactsEnabled = body.contactsEnabled;
|
|
5567
|
+
const validation = body.validation;
|
|
5568
|
+
if (validation === "present-only" || validation === "finished-validates-all") {
|
|
5569
|
+
patch.validation = validation;
|
|
5570
|
+
} else if (validation !== void 0) {
|
|
5571
|
+
return adminError4(400, 'validation must be "present-only" or "finished-validates-all"');
|
|
5572
|
+
}
|
|
5536
5573
|
return json5(200, runtime.instance(namespace).state.update(patch));
|
|
5537
5574
|
}
|
|
5538
5575
|
});
|
|
@@ -5964,11 +6001,18 @@ var FormbricksAPI = class {
|
|
|
5964
6001
|
surveyId: survey.id
|
|
5965
6002
|
});
|
|
5966
6003
|
}
|
|
5967
|
-
const
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
6004
|
+
const finished = body.finished;
|
|
6005
|
+
const responseLanguage = typeof body.language === "string" ? body.language : void 0;
|
|
6006
|
+
const tooLong = otherOptionOverLimit(survey, data, responseLanguage);
|
|
6007
|
+
if (tooLong) {
|
|
6008
|
+
return formbricksError(400, "bad_request", "Response exceeds character limit", {
|
|
6009
|
+
questionId: tooLong
|
|
6010
|
+
});
|
|
6011
|
+
}
|
|
6012
|
+
const errors = validateResponseData(survey, data, responseLanguage ?? "en", {
|
|
6013
|
+
validation: this.state.current().validation ?? "present-only",
|
|
6014
|
+
finished
|
|
6015
|
+
});
|
|
5972
6016
|
if (errors) {
|
|
5973
6017
|
const details = {};
|
|
5974
6018
|
for (const [elementId, messages] of Object.entries(errors)) {
|
|
@@ -5977,7 +6021,6 @@ var FormbricksAPI = class {
|
|
|
5977
6021
|
return formbricksError(400, "bad_request", "Validation failed", details);
|
|
5978
6022
|
}
|
|
5979
6023
|
const now = this.iso();
|
|
5980
|
-
const finished = body.finished;
|
|
5981
6024
|
const input = body.meta ?? {};
|
|
5982
6025
|
const contact = contactId ? this.state.contactOf(contactId, workspaceId) : void 0;
|
|
5983
6026
|
const ttcIn = body.ttc ?? {};
|
|
@@ -6200,4 +6243,4 @@ export {
|
|
|
6200
6243
|
formbricksError,
|
|
6201
6244
|
FormbricksAPI
|
|
6202
6245
|
};
|
|
6203
|
-
//# sourceMappingURL=chunk-
|
|
6246
|
+
//# sourceMappingURL=chunk-L4LQNY2V.js.map
|