@crvouga/mockingbird-service-formbricks 1.5.1 → 1.6.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/CHANGELOG.md +19 -4
- package/README.md +4 -4
- package/dist/{chunk-PTHSM46L.js → chunk-6PARZPBY.js} +3 -2
- package/dist/chunk-6PARZPBY.js.map +7 -0
- package/dist/{chunk-24SBIGNG.js → chunk-JAVNDNOU.js} +150 -28
- package/dist/chunk-JAVNDNOU.js.map +7 -0
- 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-24SBIGNG.js.map +0 -7
- package/dist/chunk-PTHSM46L.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-formbricks
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.6.1 (2026-09-30)
|
|
4
4
|
|
|
5
5
|
### Fixes and improvements
|
|
6
6
|
|
|
7
|
-
- keep
|
|
7
|
+
- keep admin buttons pressed until the action finishes ([0aabfcb](https://github.com/crvouga/mockingbird/commit/0aabfcb5f392aa51e193f9c3f817d5877d92bc6a))
|
|
8
|
+
- size admin select fields like the text inputs beside them ([0fb3ef2](https://github.com/crvouga/mockingbird/commit/0fb3ef2ddbf43376c93a2ae6d87c6c9bf890dafc))
|
|
9
|
+
- keep admin section tabs wired when boot is renamed ([4eda25e](https://github.com/crvouga/mockingbird/commit/4eda25eacd59da048d196f7cc8369d7a02b47bfc))
|
|
10
|
+
|
|
11
|
+
## 1.6.0 (2026-09-29)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- boot Bolt over Socket Mode ([01322df](https://github.com/crvouga/mockingbird/commit/01322df315c66b1f2f5b53094cbbeeeef6d3ae6c))
|
|
16
|
+
- validate every element when a finished response opts in ([a418b5f](https://github.com/crvouga/mockingbird/commit/a418b5f573c69ac52b80334ac59a2347dd00d247))
|
|
8
17
|
|
|
9
|
-
###
|
|
18
|
+
### Fixes and improvements
|
|
10
19
|
|
|
11
|
-
-
|
|
20
|
+
- reject other-option answers longer than 250 characters ([14a86c8](https://github.com/crvouga/mockingbird/commit/14a86c8fac4e6e42d0d5a2647c157e7caa3cfa5d))
|
|
21
|
+
|
|
22
|
+
## 1.5.1 (2026-09-29)
|
|
23
|
+
|
|
24
|
+
### Fixes and improvements
|
|
25
|
+
|
|
26
|
+
- keep hosted pages inside narrow hosts and popups ([6512323](https://github.com/crvouga/mockingbird/commit/6512323198cb00b99005f3d2cfa5b9f1e6e321a7))
|
|
12
27
|
|
|
13
28
|
## 1.5.0 (2026-09-29)
|
|
14
29
|
|
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-JAVNDNOU.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-6PARZPBY.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": "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",
|
|
6
|
+
"names": ["createServer"]
|
|
7
|
+
}
|
|
@@ -2107,6 +2107,50 @@ function bootAdmin(config) {
|
|
|
2107
2107
|
if (render)
|
|
2108
2108
|
await render();
|
|
2109
2109
|
}
|
|
2110
|
+
const delay = (ms) => new Promise((resolve) => {
|
|
2111
|
+
window.setTimeout(resolve, ms);
|
|
2112
|
+
});
|
|
2113
|
+
const confirmAction = async (button, pending, done, work) => {
|
|
2114
|
+
if (button.dataset.pending === "1")
|
|
2115
|
+
return;
|
|
2116
|
+
const label = button.textContent ?? "";
|
|
2117
|
+
const width = button.offsetWidth;
|
|
2118
|
+
button.dataset.pending = "1";
|
|
2119
|
+
button.classList.add("pressed");
|
|
2120
|
+
button.setAttribute("aria-busy", "true");
|
|
2121
|
+
button.style.minWidth = `${width}px`;
|
|
2122
|
+
button.textContent = pending;
|
|
2123
|
+
try {
|
|
2124
|
+
await work();
|
|
2125
|
+
button.textContent = done;
|
|
2126
|
+
await delay(1e3);
|
|
2127
|
+
} catch (error2) {
|
|
2128
|
+
showError(error2);
|
|
2129
|
+
} finally {
|
|
2130
|
+
button.textContent = label;
|
|
2131
|
+
button.style.minWidth = "";
|
|
2132
|
+
button.classList.remove("pressed");
|
|
2133
|
+
button.removeAttribute("aria-busy");
|
|
2134
|
+
delete button.dataset.pending;
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
document.addEventListener("click", (event) => {
|
|
2138
|
+
const target = event.target;
|
|
2139
|
+
if (!(target instanceof Element))
|
|
2140
|
+
return;
|
|
2141
|
+
const button = target.closest("button");
|
|
2142
|
+
if (!(button instanceof HTMLButtonElement) || button.dataset.pending === "1")
|
|
2143
|
+
return;
|
|
2144
|
+
button.classList.add("pressed");
|
|
2145
|
+
const token = String(Date.now());
|
|
2146
|
+
button.dataset.press = token;
|
|
2147
|
+
window.setTimeout(() => {
|
|
2148
|
+
if (button.dataset.press !== token || button.dataset.pending === "1")
|
|
2149
|
+
return;
|
|
2150
|
+
button.classList.remove("pressed");
|
|
2151
|
+
delete button.dataset.press;
|
|
2152
|
+
}, 450);
|
|
2153
|
+
});
|
|
2110
2154
|
el("namespace", HTMLSelectElement).addEventListener("change", () => {
|
|
2111
2155
|
state.namespace = el("namespace", HTMLSelectElement).value;
|
|
2112
2156
|
refresh().catch(showError);
|
|
@@ -2117,7 +2161,7 @@ function bootAdmin(config) {
|
|
|
2117
2161
|
refresh().catch(showError);
|
|
2118
2162
|
});
|
|
2119
2163
|
el("refresh", HTMLButtonElement).addEventListener("click", () => {
|
|
2120
|
-
refresh().catch(showError);
|
|
2164
|
+
confirmAction(el("refresh", HTMLButtonElement), "Refreshing...", "Updated", () => refresh()).catch(showError);
|
|
2121
2165
|
});
|
|
2122
2166
|
el("collections", HTMLElement).addEventListener("click", (event) => {
|
|
2123
2167
|
const target = event.target;
|
|
@@ -2240,10 +2284,10 @@ function bootAdmin(config) {
|
|
|
2240
2284
|
}).then(() => renderFaults()).catch(showError);
|
|
2241
2285
|
});
|
|
2242
2286
|
el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2243
|
-
api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
|
|
2287
|
+
confirmAction(el("fault-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/faults").then(() => renderFaults())).catch(showError);
|
|
2244
2288
|
});
|
|
2245
2289
|
el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2246
|
-
api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
|
|
2290
|
+
confirmAction(el("journal-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/requests").then(() => renderJournal())).catch(showError);
|
|
2247
2291
|
});
|
|
2248
2292
|
el("route-list", HTMLElement).addEventListener("click", (event) => {
|
|
2249
2293
|
const target = event.target;
|
|
@@ -2358,9 +2402,13 @@ function bootAdmin(config) {
|
|
|
2358
2402
|
var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
|
|
2359
2403
|
var adminClientSource = (config) => {
|
|
2360
2404
|
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2405
|
+
const entry = bootAdmin.name;
|
|
2406
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(entry)) {
|
|
2407
|
+
throw new Error("admin client cannot call its boot function");
|
|
2408
|
+
}
|
|
2361
2409
|
return `<script>
|
|
2362
2410
|
${clientPrelude}
|
|
2363
|
-
|
|
2411
|
+
${entry}(${payload});
|
|
2364
2412
|
</script>`;
|
|
2365
2413
|
};
|
|
2366
2414
|
|
|
@@ -2574,9 +2622,24 @@ var renderAdminDocument = (service) => {
|
|
|
2574
2622
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
2575
2623
|
label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
|
|
2576
2624
|
label.field input, label.field select, .controls input[type="password"] {
|
|
2577
|
-
background: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
|
|
2578
|
-
padding: 7px 8px; min-height: 36px;
|
|
2579
|
-
}
|
|
2625
|
+
background-color: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
|
|
2626
|
+
padding: 7px 8px; min-height: 36px; line-height: 1.2;
|
|
2627
|
+
}
|
|
2628
|
+
/* A native menu ignores min-height, so Preset rendered about half as tall as
|
|
2629
|
+
the text fields in the same form. Paint it like those fields. */
|
|
2630
|
+
label.field select {
|
|
2631
|
+
-webkit-appearance: none; appearance: none;
|
|
2632
|
+
padding-right: 28px;
|
|
2633
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%2371717a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E");
|
|
2634
|
+
background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
|
|
2635
|
+
}
|
|
2636
|
+
.stack > .field > input,
|
|
2637
|
+
.stack > .field > select,
|
|
2638
|
+
.stack > .field > textarea,
|
|
2639
|
+
dialog .field > input,
|
|
2640
|
+
dialog .field > select,
|
|
2641
|
+
dialog .field > textarea { width: 100%; }
|
|
2642
|
+
.icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger, nav.side button { cursor: pointer; }
|
|
2580
2643
|
.icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger {
|
|
2581
2644
|
border-radius: 9px; min-height: 36px; padding: 0 12px; border: 1px solid var(--line);
|
|
2582
2645
|
background: var(--bg-elev);
|
|
@@ -2584,6 +2647,14 @@ var renderAdminDocument = (service) => {
|
|
|
2584
2647
|
.btn-primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
|
|
2585
2648
|
.btn-quiet { background: transparent; }
|
|
2586
2649
|
.btn-danger { color: var(--err); }
|
|
2650
|
+
/* Held after mouseup. Clearing an empty journal changes nothing on screen,
|
|
2651
|
+
so the press itself has to stay visible. */
|
|
2652
|
+
.icon-btn:active, .btn:active, .btn-quiet:active, .btn-danger:active, nav.side button:active,
|
|
2653
|
+
.icon-btn.pressed, .btn.pressed, .btn-quiet.pressed, .btn-danger.pressed, nav.side button.pressed {
|
|
2654
|
+
background: var(--bg-inset); border-color: var(--line-strong); color: var(--ink);
|
|
2655
|
+
}
|
|
2656
|
+
.btn-danger:active, .btn-danger.pressed { background: var(--err-soft); color: var(--err); }
|
|
2657
|
+
.btn-primary:active, .btn-primary.pressed { filter: brightness(0.88); color: var(--accent-ink); }
|
|
2587
2658
|
/* minmax(0, 1fr): a 1fr column will not shrink below its content, so a long
|
|
2588
2659
|
record or the full nav row would widen the page past the viewport. */
|
|
2589
2660
|
.layout {
|
|
@@ -2672,8 +2743,16 @@ var renderAdminDocument = (service) => {
|
|
|
2672
2743
|
input, select, textarea { font-size: 16px; }
|
|
2673
2744
|
.brand, .vendor, .controls { width: 100%; }
|
|
2674
2745
|
label.field, label.field select, label.field input { flex: 1; min-width: 0; }
|
|
2675
|
-
|
|
2746
|
+
/* The layout fills the screen. A single-column grid stretches every auto
|
|
2747
|
+
row, so the tab bar grew with the leftover height and the buttons floated
|
|
2748
|
+
in the middle of it. The bar stays one row; the page below takes the rest. */
|
|
2749
|
+
.layout {
|
|
2750
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2751
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
2752
|
+
align-content: start;
|
|
2753
|
+
}
|
|
2676
2754
|
nav.side {
|
|
2755
|
+
align-self: start; height: auto;
|
|
2677
2756
|
flex-direction: row; align-items: center; overflow-x: auto; min-width: 0;
|
|
2678
2757
|
border-right: 0; border-bottom: 1px solid var(--line);
|
|
2679
2758
|
padding: 8px;
|
|
@@ -3467,7 +3546,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
3467
3546
|
};
|
|
3468
3547
|
|
|
3469
3548
|
// ../core/dist/version.js
|
|
3470
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3549
|
+
var PACKAGE_VERSION = true ? "1.6.1" : UNRELEASED_VERSION;
|
|
3471
3550
|
|
|
3472
3551
|
// ../core/dist/signing.js
|
|
3473
3552
|
var encoder = new TextEncoder();
|
|
@@ -5232,7 +5311,8 @@ var DEFAULT_SETTINGS = {
|
|
|
5232
5311
|
legacyEnvironmentIds: { [ENVIRONMENT_ID]: WORKSPACE_ID },
|
|
5233
5312
|
apiKeys: [],
|
|
5234
5313
|
webhookId: "cwebhook00000000000000001",
|
|
5235
|
-
contactsEnabled: false
|
|
5314
|
+
contactsEnabled: false,
|
|
5315
|
+
validation: "present-only"
|
|
5236
5316
|
};
|
|
5237
5317
|
var CORPUS_SURVEYS = surveys_default.surveys;
|
|
5238
5318
|
var workspaceSettings = (workspaceId) => ({
|
|
@@ -5367,10 +5447,40 @@ var invalidOption = (element, value, language) => {
|
|
|
5367
5447
|
const submitted = Array.isArray(value) ? value : [value];
|
|
5368
5448
|
return submitted.some((v) => v !== "" && (typeof v !== "string" || !known.has(v)));
|
|
5369
5449
|
};
|
|
5370
|
-
var
|
|
5371
|
-
|
|
5450
|
+
var MAX_OTHER_OPTION_LENGTH = 250;
|
|
5451
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5452
|
+
var localizedLabel = (label, language) => {
|
|
5453
|
+
if (!isRecord6(label) || !Object.hasOwn(label, "default")) return "";
|
|
5454
|
+
const text = label[language];
|
|
5455
|
+
return typeof text === "string" && text !== "" ? text : "";
|
|
5456
|
+
};
|
|
5457
|
+
var matchesChoiceLabel = (choices, value, language) => choices.some(
|
|
5458
|
+
(choice) => isRecord6(choice) && isRecord6(choice.label) && localizedLabel(choice.label, language) === value
|
|
5459
|
+
);
|
|
5460
|
+
var entryOverLimit = (value, choices, language) => !matchesChoiceLabel(choices, value, language) && value.length > MAX_OTHER_OPTION_LENGTH;
|
|
5461
|
+
var answerOverLimit = (answer, choices, language) => {
|
|
5462
|
+
if (typeof answer === "string") return entryOverLimit(answer, choices, language);
|
|
5463
|
+
if (!Array.isArray(answer)) return false;
|
|
5464
|
+
return answer.some((item) => typeof item === "string" && entryOverLimit(item, choices, language));
|
|
5465
|
+
};
|
|
5466
|
+
var otherOptionOverLimit = (survey, data, language) => {
|
|
5467
|
+
const responseLanguage = language ?? "default";
|
|
5468
|
+
for (const [questionId, answer] of Object.entries(data)) {
|
|
5469
|
+
const question = surveyElements(survey).find((element) => element.id === questionId);
|
|
5470
|
+
if (!question?.choices) continue;
|
|
5471
|
+
if (question.type !== "multipleChoiceSingle" && question.type !== "multipleChoiceMulti") {
|
|
5472
|
+
continue;
|
|
5473
|
+
}
|
|
5474
|
+
if (answerOverLimit(answer, question.choices, responseLanguage)) return questionId;
|
|
5475
|
+
}
|
|
5476
|
+
return void 0;
|
|
5477
|
+
};
|
|
5478
|
+
var validateResponseData = (survey, data, language = "en", options = {}) => {
|
|
5479
|
+
const elements = surveyElements(survey);
|
|
5480
|
+
const presentIds = new Set(Object.keys(data));
|
|
5481
|
+
const selected = options.validation === "finished-validates-all" && options.finished === true ? elements : elements.filter((element) => presentIds.has(element.id));
|
|
5372
5482
|
const errors = {};
|
|
5373
|
-
for (const element of
|
|
5483
|
+
for (const element of selected) {
|
|
5374
5484
|
const value = data[element.id];
|
|
5375
5485
|
const messages = [];
|
|
5376
5486
|
if (requiredError(element, value)) messages.push(REQUIRED);
|
|
@@ -5467,7 +5577,7 @@ var FORMBRICKS_PRESETS = {
|
|
|
5467
5577
|
};
|
|
5468
5578
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
5469
5579
|
var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
|
|
5470
|
-
var
|
|
5580
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5471
5581
|
var adminRoutes = (runtime) => ({
|
|
5472
5582
|
"GET /responses": ({ url, namespace }) => {
|
|
5473
5583
|
const surveyId = url.searchParams.get("surveyId");
|
|
@@ -5477,11 +5587,11 @@ var adminRoutes = (runtime) => ({
|
|
|
5477
5587
|
},
|
|
5478
5588
|
"GET /surveys": ({ namespace }) => json5(200, { surveys: runtime.instance(namespace).state.allSurveys() }),
|
|
5479
5589
|
"PUT /surveys": ({ body, namespace }) => {
|
|
5480
|
-
const list = Array.isArray(body) ? body :
|
|
5590
|
+
const list = Array.isArray(body) ? body : isRecord7(body) ? body.surveys : void 0;
|
|
5481
5591
|
if (!Array.isArray(list)) return adminError4(400, "expected [survey] or {surveys: [survey]}");
|
|
5482
5592
|
const state = runtime.instance(namespace).state;
|
|
5483
5593
|
for (const [index, survey] of list.entries()) {
|
|
5484
|
-
if (!
|
|
5594
|
+
if (!isRecord7(survey) || typeof survey.id !== "string" || typeof survey.name !== "string") {
|
|
5485
5595
|
return adminError4(400, `surveys[${index}]: id and name are required`);
|
|
5486
5596
|
}
|
|
5487
5597
|
}
|
|
@@ -5501,16 +5611,16 @@ var adminRoutes = (runtime) => ({
|
|
|
5501
5611
|
contacts: runtime.instance(namespace).state.contacts.list({ order: "oldest" }).map((row) => row.value)
|
|
5502
5612
|
}),
|
|
5503
5613
|
"PUT /contacts": ({ body, namespace }) => {
|
|
5504
|
-
const list = Array.isArray(body) ? body :
|
|
5614
|
+
const list = Array.isArray(body) ? body : isRecord7(body) ? body.contacts : void 0;
|
|
5505
5615
|
if (!Array.isArray(list)) return adminError4(400, "expected [contact] or {contacts: [contact]}");
|
|
5506
5616
|
const state = runtime.instance(namespace).state;
|
|
5507
5617
|
for (const [index, contact] of list.entries()) {
|
|
5508
|
-
if (!
|
|
5618
|
+
if (!isRecord7(contact) || typeof contact.id !== "string") {
|
|
5509
5619
|
return adminError4(400, `contacts[${index}]: id is required`);
|
|
5510
5620
|
}
|
|
5511
5621
|
}
|
|
5512
5622
|
for (const contact of list) {
|
|
5513
|
-
const attributes =
|
|
5623
|
+
const attributes = isRecord7(contact.attributes) ? Object.fromEntries(Object.entries(contact.attributes).map(([k, v]) => [k, String(v)])) : {};
|
|
5514
5624
|
if (typeof contact.userId === "string") attributes.userId = contact.userId;
|
|
5515
5625
|
state.contacts.insert(contact.id, {
|
|
5516
5626
|
id: contact.id,
|
|
@@ -5522,10 +5632,10 @@ var adminRoutes = (runtime) => ({
|
|
|
5522
5632
|
},
|
|
5523
5633
|
"GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
|
|
5524
5634
|
"PUT /settings": ({ body, namespace }) => {
|
|
5525
|
-
if (!
|
|
5635
|
+
if (!isRecord7(body)) return adminError4(400, "expected a JSON object");
|
|
5526
5636
|
const patch = {};
|
|
5527
5637
|
if (Array.isArray(body.workspaces)) patch.workspaces = body.workspaces.map(String);
|
|
5528
|
-
if (
|
|
5638
|
+
if (isRecord7(body.legacyEnvironmentIds)) {
|
|
5529
5639
|
patch.legacyEnvironmentIds = Object.fromEntries(
|
|
5530
5640
|
Object.entries(body.legacyEnvironmentIds).map(([k, v]) => [k, String(v)])
|
|
5531
5641
|
);
|
|
@@ -5533,6 +5643,12 @@ var adminRoutes = (runtime) => ({
|
|
|
5533
5643
|
if (Array.isArray(body.apiKeys)) patch.apiKeys = body.apiKeys.map(String);
|
|
5534
5644
|
if (typeof body.webhookId === "string") patch.webhookId = body.webhookId;
|
|
5535
5645
|
if (typeof body.contactsEnabled === "boolean") patch.contactsEnabled = body.contactsEnabled;
|
|
5646
|
+
const validation = body.validation;
|
|
5647
|
+
if (validation === "present-only" || validation === "finished-validates-all") {
|
|
5648
|
+
patch.validation = validation;
|
|
5649
|
+
} else if (validation !== void 0) {
|
|
5650
|
+
return adminError4(400, 'validation must be "present-only" or "finished-validates-all"');
|
|
5651
|
+
}
|
|
5536
5652
|
return json5(200, runtime.instance(namespace).state.update(patch));
|
|
5537
5653
|
}
|
|
5538
5654
|
});
|
|
@@ -5964,11 +6080,18 @@ var FormbricksAPI = class {
|
|
|
5964
6080
|
surveyId: survey.id
|
|
5965
6081
|
});
|
|
5966
6082
|
}
|
|
5967
|
-
const
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
6083
|
+
const finished = body.finished;
|
|
6084
|
+
const responseLanguage = typeof body.language === "string" ? body.language : void 0;
|
|
6085
|
+
const tooLong = otherOptionOverLimit(survey, data, responseLanguage);
|
|
6086
|
+
if (tooLong) {
|
|
6087
|
+
return formbricksError(400, "bad_request", "Response exceeds character limit", {
|
|
6088
|
+
questionId: tooLong
|
|
6089
|
+
});
|
|
6090
|
+
}
|
|
6091
|
+
const errors = validateResponseData(survey, data, responseLanguage ?? "en", {
|
|
6092
|
+
validation: this.state.current().validation ?? "present-only",
|
|
6093
|
+
finished
|
|
6094
|
+
});
|
|
5972
6095
|
if (errors) {
|
|
5973
6096
|
const details = {};
|
|
5974
6097
|
for (const [elementId, messages] of Object.entries(errors)) {
|
|
@@ -5977,7 +6100,6 @@ var FormbricksAPI = class {
|
|
|
5977
6100
|
return formbricksError(400, "bad_request", "Validation failed", details);
|
|
5978
6101
|
}
|
|
5979
6102
|
const now = this.iso();
|
|
5980
|
-
const finished = body.finished;
|
|
5981
6103
|
const input = body.meta ?? {};
|
|
5982
6104
|
const contact = contactId ? this.state.contactOf(contactId, workspaceId) : void 0;
|
|
5983
6105
|
const ttcIn = body.ttc ?? {};
|
|
@@ -6200,4 +6322,4 @@ export {
|
|
|
6200
6322
|
formbricksError,
|
|
6201
6323
|
FormbricksAPI
|
|
6202
6324
|
};
|
|
6203
|
-
//# sourceMappingURL=chunk-
|
|
6325
|
+
//# sourceMappingURL=chunk-JAVNDNOU.js.map
|