@marktiderman/genesis-forms 0.5.0 → 0.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/README.md +128 -0
- package/dist/destinations.cjs +55 -3
- package/dist/destinations.cjs.map +1 -1
- package/dist/destinations.d.cts +1 -1
- package/dist/destinations.d.ts +1 -1
- package/dist/destinations.js +55 -3
- package/dist/destinations.js.map +1 -1
- package/dist/{engine-BDq0XJ21.d.cts → engine-CpfOz8FN.d.ts} +10 -3
- package/dist/{engine-Qzp6bX6O.d.ts → engine-DC4YTPdr.d.cts} +10 -3
- package/dist/index.cjs +484 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -7
- package/dist/index.d.ts +29 -7
- package/dist/index.js +461 -63
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +541 -21
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +193 -3
- package/dist/server.d.ts +193 -3
- package/dist/server.js +522 -22
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +314 -21
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +4 -4
- package/dist/testing.d.ts +4 -4
- package/dist/testing.js +314 -21
- package/dist/testing.js.map +1 -1
- package/dist/{types-BhYUbLJ-.d.cts → types-oAckGR_Z.d.cts} +10 -0
- package/dist/{types-BhYUbLJ-.d.ts → types-oAckGR_Z.d.ts} +10 -0
- package/dist/validate-B5WKHbst.d.cts +322 -0
- package/dist/validate-GpTXPwyl.d.ts +322 -0
- package/package.json +1 -1
- package/dist/validate-Bs1lyoOl.d.ts +0 -131
- package/dist/validate-BsZ0YTQS.d.cts +0 -131
package/dist/server.cjs
CHANGED
|
@@ -831,6 +831,65 @@ function summarizeValue(value, field) {
|
|
|
831
831
|
if (Array.isArray(value)) return value.join(", ");
|
|
832
832
|
return String(value);
|
|
833
833
|
}
|
|
834
|
+
var OPTION_LIMITS = {
|
|
835
|
+
/** Characters in a description. */
|
|
836
|
+
description: 500,
|
|
837
|
+
/** Characters in an image URL. */
|
|
838
|
+
image: 2048,
|
|
839
|
+
/** Attributes on one option. */
|
|
840
|
+
attributes: 20,
|
|
841
|
+
/** Characters in an attribute's name. */
|
|
842
|
+
attributeName: 64,
|
|
843
|
+
/** Characters in one attribute value. */
|
|
844
|
+
attributeValue: 200,
|
|
845
|
+
/** Values in one attribute's list. */
|
|
846
|
+
attributeItems: 50
|
|
847
|
+
};
|
|
848
|
+
function isHttpsUrl(url) {
|
|
849
|
+
return typeof url === "string" && url.length <= OPTION_LIMITS.image && /^https:\/\/[^\s/?#@\\]+(?:[/?#][^\s]*)?$/i.test(url) && // No control characters anywhere (a line break would end a CSV row or an HTML attribute).
|
|
850
|
+
![...url].some((c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127);
|
|
851
|
+
}
|
|
852
|
+
function attributeName(name) {
|
|
853
|
+
return name.trim() !== "" && name.length <= OPTION_LIMITS.attributeName && !(name in Object.prototype);
|
|
854
|
+
}
|
|
855
|
+
function readAttributes(raw) {
|
|
856
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
857
|
+
const out = {};
|
|
858
|
+
let count = 0;
|
|
859
|
+
for (const name of Object.keys(raw)) {
|
|
860
|
+
if (count >= OPTION_LIMITS.attributes) break;
|
|
861
|
+
if (!attributeName(name)) continue;
|
|
862
|
+
const v = raw[name];
|
|
863
|
+
const fits = (x) => typeof x === "string" && x.length <= OPTION_LIMITS.attributeValue;
|
|
864
|
+
if (fits(v)) out[name] = v;
|
|
865
|
+
else if (Array.isArray(v) && v.length <= OPTION_LIMITS.attributeItems && v.every(fits)) out[name] = [...v];
|
|
866
|
+
else continue;
|
|
867
|
+
count++;
|
|
868
|
+
}
|
|
869
|
+
return count ? out : void 0;
|
|
870
|
+
}
|
|
871
|
+
function readOption(raw) {
|
|
872
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
873
|
+
const o = raw;
|
|
874
|
+
if (typeof o.value !== "string") return null;
|
|
875
|
+
const out = { value: o.value };
|
|
876
|
+
if (typeof o.label === "string") out.label = o.label;
|
|
877
|
+
if (typeof o.description === "string" && o.description.trim() && o.description.length <= OPTION_LIMITS.description) {
|
|
878
|
+
out.description = o.description;
|
|
879
|
+
}
|
|
880
|
+
if (isHttpsUrl(o.image)) out.image = o.image;
|
|
881
|
+
const attributes = readAttributes(o.attributes);
|
|
882
|
+
if (attributes) out.attributes = attributes;
|
|
883
|
+
return out;
|
|
884
|
+
}
|
|
885
|
+
function optionJson(option) {
|
|
886
|
+
const out = { value: option.value };
|
|
887
|
+
if (option.label !== void 0) out.label = option.label;
|
|
888
|
+
if (option.description !== void 0) out.description = option.description;
|
|
889
|
+
if (option.image !== void 0) out.image = option.image;
|
|
890
|
+
if (option.attributes !== void 0) out.attributes = { ...option.attributes };
|
|
891
|
+
return out;
|
|
892
|
+
}
|
|
834
893
|
var OTHER_MAX_LENGTH = 200;
|
|
835
894
|
function otherOption(field) {
|
|
836
895
|
const other = field.props?.other;
|
|
@@ -848,14 +907,21 @@ var typedOther = (field, value) => isOtherValue(field, value) && value.trim() !=
|
|
|
848
907
|
function choiceOptions(field) {
|
|
849
908
|
const raw = field.props?.options;
|
|
850
909
|
if (!Array.isArray(raw)) return [];
|
|
851
|
-
return raw.flatMap(
|
|
852
|
-
|
|
853
|
-
|
|
910
|
+
return raw.flatMap((o) => {
|
|
911
|
+
const option = readOption(o);
|
|
912
|
+
return option ? [option] : [];
|
|
913
|
+
});
|
|
854
914
|
}
|
|
855
915
|
function optionsSource(field) {
|
|
856
916
|
const raw = field.props?.optionsFrom;
|
|
857
917
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
858
|
-
|
|
918
|
+
if (raw.source !== "host" || typeof raw.key !== "string" || !raw.key) return void 0;
|
|
919
|
+
const out = { source: "host", key: raw.key };
|
|
920
|
+
const filter = raw.filter;
|
|
921
|
+
if (filter && typeof filter === "object" && !Array.isArray(filter) && typeof filter.attribute === "string" && filter.attribute && typeof filter.field === "string" && filter.field) {
|
|
922
|
+
out.filter = { attribute: filter.attribute, field: filter.field };
|
|
923
|
+
}
|
|
924
|
+
return out;
|
|
859
925
|
}
|
|
860
926
|
function hostList(hostOptions, key) {
|
|
861
927
|
if (!hostOptions || !Object.prototype.hasOwnProperty.call(hostOptions, key)) return [];
|
|
@@ -864,11 +930,10 @@ function hostList(hostOptions, key) {
|
|
|
864
930
|
const seen = /* @__PURE__ */ new Set();
|
|
865
931
|
const out = [];
|
|
866
932
|
for (const o of list) {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
out.push(typeof label === "string" ? { value, label } : { value });
|
|
933
|
+
const option = readOption(o);
|
|
934
|
+
if (!option || !option.value || seen.has(option.value)) continue;
|
|
935
|
+
seen.add(option.value);
|
|
936
|
+
out.push(option);
|
|
872
937
|
}
|
|
873
938
|
return out;
|
|
874
939
|
}
|
|
@@ -883,7 +948,7 @@ function withHostOptions(definition, hostOptions) {
|
|
|
883
948
|
const added = hostList(hostOptions, from.key).filter((o) => !taken.has(o.value));
|
|
884
949
|
if (!added.length) return block;
|
|
885
950
|
changed = true;
|
|
886
|
-
const options = [...own2, ...added].map(
|
|
951
|
+
const options = [...own2, ...added].map(optionJson);
|
|
887
952
|
return { ...block, props: { ...block.props, options } };
|
|
888
953
|
});
|
|
889
954
|
return changed ? { ...definition, blocks } : definition;
|
|
@@ -1363,6 +1428,205 @@ function fieldMap(def) {
|
|
|
1363
1428
|
return map;
|
|
1364
1429
|
}
|
|
1365
1430
|
|
|
1431
|
+
// src/options.ts
|
|
1432
|
+
var CHOICE_FIELD_TYPES = /* @__PURE__ */ new Set(["single-choice", "multi-choice", "dropdown"]);
|
|
1433
|
+
var isChoice = (block) => !!block && block.kind === "field" && CHOICE_FIELD_TYPES.has(block.type);
|
|
1434
|
+
function put(record, key, value) {
|
|
1435
|
+
Object.defineProperty(record, key, { value, enumerable: true, writable: true, configurable: true });
|
|
1436
|
+
}
|
|
1437
|
+
function optionFilter(field) {
|
|
1438
|
+
return optionsSource(field)?.filter;
|
|
1439
|
+
}
|
|
1440
|
+
function answerValues(answer) {
|
|
1441
|
+
const one = (v) => typeof v === "string" ? [v] : typeof v === "number" || typeof v === "boolean" ? [String(v)] : [];
|
|
1442
|
+
if (answer === void 0 || answer === null) return [];
|
|
1443
|
+
return Array.isArray(answer) ? answer.flatMap(one) : one(answer);
|
|
1444
|
+
}
|
|
1445
|
+
function optionMatches(option, attribute, answer) {
|
|
1446
|
+
const have = option.attributes && Object.prototype.hasOwnProperty.call(option.attributes, attribute) ? option.attributes[attribute] : void 0;
|
|
1447
|
+
if (have === void 0) return false;
|
|
1448
|
+
const wanted = answerValues(answer);
|
|
1449
|
+
return (Array.isArray(have) ? have : [have]).some((v) => wanted.includes(v));
|
|
1450
|
+
}
|
|
1451
|
+
function filterSource(definition, key) {
|
|
1452
|
+
return definition.blocks.find((b) => b.kind === "field" && b.key === key);
|
|
1453
|
+
}
|
|
1454
|
+
function visibleOptions(definition, field, values) {
|
|
1455
|
+
const options = choiceOptions(field);
|
|
1456
|
+
const filter = optionFilter(field);
|
|
1457
|
+
if (!filter) return options;
|
|
1458
|
+
const source = filterSource(definition, filter.field);
|
|
1459
|
+
if (!source || source.id === field.id) return options;
|
|
1460
|
+
const answer = own(values, source.id);
|
|
1461
|
+
if (isEmptyValue(answer, source)) return options;
|
|
1462
|
+
return options.filter((o) => optionMatches(o, filter.attribute, answer));
|
|
1463
|
+
}
|
|
1464
|
+
function answersOnPath(state) {
|
|
1465
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
1466
|
+
for (const id of state.path) out[id] = own(state.values, id);
|
|
1467
|
+
return out;
|
|
1468
|
+
}
|
|
1469
|
+
var OUTSIDE_FILTER = "Choose one of the options.";
|
|
1470
|
+
function filteredChoiceError(definition, field, value, values) {
|
|
1471
|
+
if (!optionFilter(field) || isEmptyValue(value, field)) return null;
|
|
1472
|
+
const all = new Set(choiceOptions(field).map((o) => o.value));
|
|
1473
|
+
const shown2 = new Set(visibleOptions(definition, field, values).map((o) => o.value));
|
|
1474
|
+
const picked = answerValues(value);
|
|
1475
|
+
return picked.some((v) => all.has(v) && !shown2.has(v)) ? OUTSIDE_FILTER : null;
|
|
1476
|
+
}
|
|
1477
|
+
function hasOptionFilters(definition) {
|
|
1478
|
+
return definition.blocks.some((b) => isChoice(b) && optionFilter(b) !== void 0);
|
|
1479
|
+
}
|
|
1480
|
+
function clearFilteredAnswers(definition, values, changedId) {
|
|
1481
|
+
const changed = definition.blocks.find((b) => b.kind === "field" && b.id === changedId);
|
|
1482
|
+
if (!changed) return values;
|
|
1483
|
+
const keys = /* @__PURE__ */ new Set([changed.key]);
|
|
1484
|
+
let out = values;
|
|
1485
|
+
for (const block of definition.blocks) {
|
|
1486
|
+
if (!isChoice(block)) continue;
|
|
1487
|
+
const filter = optionFilter(block);
|
|
1488
|
+
if (!filter || !keys.has(filter.field)) continue;
|
|
1489
|
+
const value = own(out, block.id);
|
|
1490
|
+
if (value === void 0 || isEmptyValue(value, block)) continue;
|
|
1491
|
+
const all = new Set(choiceOptions(block).map((o) => o.value));
|
|
1492
|
+
const shown2 = new Set(visibleOptions(definition, block, out).map((o) => o.value));
|
|
1493
|
+
const keep = (v) => typeof v !== "string" || !all.has(v) || shown2.has(v);
|
|
1494
|
+
let next = value;
|
|
1495
|
+
if (Array.isArray(value)) {
|
|
1496
|
+
const kept = value.filter(keep);
|
|
1497
|
+
if (kept.length !== value.length) next = kept.length ? kept : void 0;
|
|
1498
|
+
} else if (!keep(value)) next = void 0;
|
|
1499
|
+
if (next === value) continue;
|
|
1500
|
+
if (out === values) out = { ...values };
|
|
1501
|
+
put(out, block.id, next);
|
|
1502
|
+
keys.add(block.key);
|
|
1503
|
+
}
|
|
1504
|
+
return out;
|
|
1505
|
+
}
|
|
1506
|
+
function fold(text) {
|
|
1507
|
+
return text.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
|
|
1508
|
+
}
|
|
1509
|
+
var MAX_PREFILL_PARAM_LENGTH = 64;
|
|
1510
|
+
function isPrefillParam(param) {
|
|
1511
|
+
return typeof param === "string" && param.length <= MAX_PREFILL_PARAM_LENGTH && /^[A-Za-z0-9_.-]+$/.test(param);
|
|
1512
|
+
}
|
|
1513
|
+
function prefillSetting(field) {
|
|
1514
|
+
if (!CHOICE_FIELD_TYPES.has(field.type)) return void 0;
|
|
1515
|
+
const raw = field.props?.prefill;
|
|
1516
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw) || !isPrefillParam(raw.param)) return void 0;
|
|
1517
|
+
return { param: raw.param, hideWhenPrefilled: raw.hideWhenPrefilled === true };
|
|
1518
|
+
}
|
|
1519
|
+
function paramValues(params, name) {
|
|
1520
|
+
let raw;
|
|
1521
|
+
if (typeof params.getAll === "function") {
|
|
1522
|
+
raw = params.getAll(name);
|
|
1523
|
+
} else if (Object.prototype.hasOwnProperty.call(params, name)) {
|
|
1524
|
+
raw = params[name];
|
|
1525
|
+
}
|
|
1526
|
+
const list = Array.isArray(raw) ? raw : raw === void 0 ? [] : [raw];
|
|
1527
|
+
return list.filter((v) => typeof v === "string" && v !== "" && v.length <= MAX_HIDDEN_STRING_LENGTH);
|
|
1528
|
+
}
|
|
1529
|
+
function prefillFromLink(definition, params, values = {}) {
|
|
1530
|
+
const out = {};
|
|
1531
|
+
if (!params || typeof params !== "object") return out;
|
|
1532
|
+
const seen = {};
|
|
1533
|
+
for (const id of Object.keys(values)) put(seen, id, values[id]);
|
|
1534
|
+
for (const block of definition.blocks) {
|
|
1535
|
+
if (!isChoice(block)) continue;
|
|
1536
|
+
const setting = prefillSetting(block);
|
|
1537
|
+
if (!setting || own(seen, block.id) !== void 0) continue;
|
|
1538
|
+
const raw = paramValues(params, setting.param);
|
|
1539
|
+
if (!raw.length) continue;
|
|
1540
|
+
const shown2 = new Set(visibleOptions(definition, block, seen).map((o) => o.value));
|
|
1541
|
+
let value;
|
|
1542
|
+
if (block.type === "multi-choice") {
|
|
1543
|
+
const picked = [];
|
|
1544
|
+
for (const r of raw) {
|
|
1545
|
+
for (const v of shown2.has(r) ? [r] : r.split(",").map((s) => s.trim())) {
|
|
1546
|
+
if (shown2.has(v) && !picked.includes(v)) picked.push(v);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
if (picked.length) value = picked;
|
|
1550
|
+
} else value = raw.find((v) => shown2.has(v));
|
|
1551
|
+
if (value === void 0) continue;
|
|
1552
|
+
put(out, block.id, value);
|
|
1553
|
+
put(seen, block.id, value);
|
|
1554
|
+
}
|
|
1555
|
+
return out;
|
|
1556
|
+
}
|
|
1557
|
+
function hiddenByPrefill(definition, state) {
|
|
1558
|
+
const hideable = /* @__PURE__ */ new Set();
|
|
1559
|
+
for (const b of definition.blocks) {
|
|
1560
|
+
if (isChoice(b) && prefillSetting(b)?.hideWhenPrefilled) hideable.add(b.id);
|
|
1561
|
+
}
|
|
1562
|
+
if (!hideable.size) return [];
|
|
1563
|
+
const prefilled = /* @__PURE__ */ new Set();
|
|
1564
|
+
for (const e of state.events) {
|
|
1565
|
+
if (e.type !== "answer" || !hideable.has(e.field)) continue;
|
|
1566
|
+
if (e.source === "prefill") prefilled.add(e.field);
|
|
1567
|
+
else prefilled.delete(e.field);
|
|
1568
|
+
}
|
|
1569
|
+
if (!prefilled.size) return [];
|
|
1570
|
+
const fields = new Map(definition.blocks.map((b) => [b.id, b]));
|
|
1571
|
+
return state.path.filter((id) => {
|
|
1572
|
+
if (!prefilled.has(id) || own(state.errors, id) !== void 0) return false;
|
|
1573
|
+
const field = fields.get(id);
|
|
1574
|
+
return !isEmptyValue(own(state.values, id), field);
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
function snapshotOf(byValue, value) {
|
|
1578
|
+
const option = byValue.get(value);
|
|
1579
|
+
if (option) return { value, label: option.label ?? value };
|
|
1580
|
+
return { value, label: value, other: true };
|
|
1581
|
+
}
|
|
1582
|
+
function answerSnapshots(definition, answers) {
|
|
1583
|
+
const out = {};
|
|
1584
|
+
for (const block of definition.blocks) {
|
|
1585
|
+
if (!isChoice(block) || !optionsSource(block)) continue;
|
|
1586
|
+
const value = own(answers, block.id);
|
|
1587
|
+
if (isEmptyValue(value, block)) continue;
|
|
1588
|
+
const byValue = new Map(choiceOptions(block).map((o) => [o.value, o]));
|
|
1589
|
+
let snapshot;
|
|
1590
|
+
if (Array.isArray(value)) {
|
|
1591
|
+
const items = value.filter((v) => typeof v === "string").map((v) => snapshotOf(byValue, v));
|
|
1592
|
+
if (items.length) snapshot = items;
|
|
1593
|
+
} else if (typeof value === "string") snapshot = snapshotOf(byValue, value);
|
|
1594
|
+
if (snapshot) put(out, block.id, snapshot);
|
|
1595
|
+
}
|
|
1596
|
+
return out;
|
|
1597
|
+
}
|
|
1598
|
+
function answersFromSnapshots(definition, snapshots) {
|
|
1599
|
+
const answers = {};
|
|
1600
|
+
const missing = {};
|
|
1601
|
+
for (const block of definition.blocks) {
|
|
1602
|
+
if (!isChoice(block)) continue;
|
|
1603
|
+
const raw = own(snapshots, block.id);
|
|
1604
|
+
const list = (Array.isArray(raw) ? raw : raw === void 0 ? [] : [raw]).flatMap((s) => {
|
|
1605
|
+
const o = readOption(s);
|
|
1606
|
+
if (!o) return [];
|
|
1607
|
+
const label = o.label ?? o.value;
|
|
1608
|
+
return [s.other === true ? { value: o.value, label, other: true } : { value: o.value, label }];
|
|
1609
|
+
});
|
|
1610
|
+
if (!list.length) continue;
|
|
1611
|
+
const options = choiceOptions(block);
|
|
1612
|
+
const byValue = new Set(options.map((o) => o.value));
|
|
1613
|
+
const allowsOther = block.props?.other !== void 0 && block.props?.other !== false;
|
|
1614
|
+
const found = [];
|
|
1615
|
+
const lost = [];
|
|
1616
|
+
for (const s of list) {
|
|
1617
|
+
let value;
|
|
1618
|
+
if (byValue.has(s.value)) value = s.value;
|
|
1619
|
+
else if (s.other) value = allowsOther ? s.value : void 0;
|
|
1620
|
+
else value = options.find((o) => fold(o.label ?? o.value) === fold(s.label))?.value;
|
|
1621
|
+
if (value === void 0) lost.push(s);
|
|
1622
|
+
else if (!found.includes(value)) found.push(value);
|
|
1623
|
+
}
|
|
1624
|
+
if (found.length) put(answers, block.id, block.type === "multi-choice" ? found : found[0]);
|
|
1625
|
+
if (lost.length) put(missing, block.id, lost);
|
|
1626
|
+
}
|
|
1627
|
+
return { answers, missing };
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1366
1630
|
// src/recall.ts
|
|
1367
1631
|
var TOKEN = /\{\{\s*(field|hidden):([A-Za-z0-9_-]+)\s*(?:\|([^}]*))?\}\}/g;
|
|
1368
1632
|
function recall(text, scope) {
|
|
@@ -1484,16 +1748,34 @@ function build(given, options, replaying = false, budget) {
|
|
|
1484
1748
|
return at === void 0 ? event : { ...event, at };
|
|
1485
1749
|
};
|
|
1486
1750
|
const isVisibleIn = (s, id) => s.path.includes(id);
|
|
1751
|
+
const filters = hasOptionFilters(definition);
|
|
1752
|
+
const clearFiltered = (values, id) => filters ? clearFilteredAnswers(definition, values, id) : values;
|
|
1487
1753
|
const errorsFor = (s, ids) => {
|
|
1488
1754
|
const errors = {};
|
|
1755
|
+
let shown2;
|
|
1489
1756
|
for (const id of ids) {
|
|
1490
1757
|
const field = fields.get(id);
|
|
1491
1758
|
if (!field || !isVisibleIn(s, id)) continue;
|
|
1492
|
-
const
|
|
1759
|
+
const value = own(s.values, id);
|
|
1760
|
+
let message = validateField(field, value, { custom: options.validators?.[id] });
|
|
1761
|
+
if (!message && filters) message = filteredChoiceError(definition, field, value, shown2 ??= answersOnPath(s));
|
|
1493
1762
|
if (message) errors[id] = message;
|
|
1494
1763
|
}
|
|
1495
1764
|
return errors;
|
|
1496
1765
|
};
|
|
1766
|
+
const skipped = (s) => new Set(hiddenByPrefill(definition, s));
|
|
1767
|
+
const shownFrom = (s, from, skip) => {
|
|
1768
|
+
for (let i = from; i < s.path.length; i++) if (!skip.has(s.path[i])) return s.path[i];
|
|
1769
|
+
return void 0;
|
|
1770
|
+
};
|
|
1771
|
+
const showCurrent = () => {
|
|
1772
|
+
if (!state.current) return;
|
|
1773
|
+
const skip = skipped(state);
|
|
1774
|
+
if (!skip.has(state.current)) return;
|
|
1775
|
+
const at = state.path.indexOf(state.current);
|
|
1776
|
+
const shown2 = at < 0 ? void 0 : shownFrom(state, at, skip);
|
|
1777
|
+
if (shown2) state = { ...state, current: shown2, furthest: state.furthest === state.current ? shown2 : state.furthest };
|
|
1778
|
+
};
|
|
1497
1779
|
let unsettled = false;
|
|
1498
1780
|
let formulasStale = false;
|
|
1499
1781
|
const settleNow = (needValues = true) => {
|
|
@@ -1522,13 +1804,13 @@ function build(given, options, replaying = false, budget) {
|
|
|
1522
1804
|
const errors = { ...state.errors };
|
|
1523
1805
|
delete errors[id];
|
|
1524
1806
|
if (batch) {
|
|
1525
|
-
state.values = { ...state.values, [id]: value };
|
|
1807
|
+
state.values = clearFiltered({ ...state.values, [id]: value }, id);
|
|
1526
1808
|
state.errors = errors;
|
|
1527
1809
|
log(event);
|
|
1528
1810
|
unsettled = true;
|
|
1529
1811
|
return;
|
|
1530
1812
|
}
|
|
1531
|
-
const { values, path } = settle({ ...state.values, [id]: value }, state.hidden);
|
|
1813
|
+
const { values, path } = settle(clearFiltered({ ...state.values, [id]: value }, id), state.hidden);
|
|
1532
1814
|
emit({ ...state, values, path, errors, events: log(event) });
|
|
1533
1815
|
};
|
|
1534
1816
|
const engine = {
|
|
@@ -1559,7 +1841,7 @@ function build(given, options, replaying = false, budget) {
|
|
|
1559
1841
|
return false;
|
|
1560
1842
|
}
|
|
1561
1843
|
const at = state.path.indexOf(state.current);
|
|
1562
|
-
const target = state
|
|
1844
|
+
const target = shownFrom(state, at + 1, skipped(state));
|
|
1563
1845
|
if (!target) {
|
|
1564
1846
|
emit({ ...state, events });
|
|
1565
1847
|
return true;
|
|
@@ -1571,8 +1853,9 @@ function build(given, options, replaying = false, budget) {
|
|
|
1571
1853
|
back() {
|
|
1572
1854
|
if (state.status === "completed") return;
|
|
1573
1855
|
const history = [...state.history];
|
|
1856
|
+
const skip = skipped(state);
|
|
1574
1857
|
let previous = history.pop();
|
|
1575
|
-
while (previous && !state.path.includes(previous)) previous = history.pop();
|
|
1858
|
+
while (previous && (!state.path.includes(previous) || skip.has(previous))) previous = history.pop();
|
|
1576
1859
|
if (!previous) return;
|
|
1577
1860
|
emit({ ...state, current: previous, history, events: log(stamp({ type: "back" })) });
|
|
1578
1861
|
},
|
|
@@ -1600,15 +1883,18 @@ function build(given, options, replaying = false, budget) {
|
|
|
1600
1883
|
return out;
|
|
1601
1884
|
}
|
|
1602
1885
|
};
|
|
1603
|
-
|
|
1886
|
+
const fromLink = options.linkParams ? prefillFromLink(definition, options.linkParams, options.initialValues) : {};
|
|
1887
|
+
const initialValues = Object.keys(fromLink).length ? { ...fromLink, ...options.initialValues } : options.initialValues;
|
|
1888
|
+
if (initialValues) {
|
|
1604
1889
|
const wasBatch = batch;
|
|
1605
1890
|
batch = true;
|
|
1606
|
-
for (const [id, value] of Object.entries(
|
|
1891
|
+
for (const [id, value] of Object.entries(initialValues)) {
|
|
1607
1892
|
if (!fields.has(id) || value === void 0) continue;
|
|
1608
1893
|
answer(id, value, stamp({ type: "answer", field: id, value, source: "prefill" }));
|
|
1609
1894
|
}
|
|
1610
1895
|
settleNow();
|
|
1611
1896
|
batch = wasBatch;
|
|
1897
|
+
showCurrent();
|
|
1612
1898
|
}
|
|
1613
1899
|
const resumeAt = options.resumeAt;
|
|
1614
1900
|
if (resumeAt && !replaying && state.path.includes(resumeAt)) {
|
|
@@ -1627,18 +1913,22 @@ function build(given, options, replaying = false, budget) {
|
|
|
1627
1913
|
}
|
|
1628
1914
|
} else if (event.type === "next") {
|
|
1629
1915
|
settleNow(false);
|
|
1916
|
+
showCurrent();
|
|
1630
1917
|
if (formulasStale && isCalculated(fields.get(state.current ?? ""))) settleNow(true);
|
|
1631
1918
|
engine.next();
|
|
1632
1919
|
} else if (event.type === "back") {
|
|
1633
1920
|
settleNow(false);
|
|
1921
|
+
showCurrent();
|
|
1634
1922
|
engine.back();
|
|
1635
1923
|
} else if (event.type === "submit") {
|
|
1636
1924
|
settleNow();
|
|
1925
|
+
showCurrent();
|
|
1637
1926
|
engine.submit();
|
|
1638
1927
|
}
|
|
1639
1928
|
};
|
|
1640
1929
|
const finish = () => {
|
|
1641
1930
|
settleNow();
|
|
1931
|
+
showCurrent();
|
|
1642
1932
|
batch = false;
|
|
1643
1933
|
state = { ...state, events: [...state.events] };
|
|
1644
1934
|
};
|
|
@@ -1646,7 +1936,12 @@ function build(given, options, replaying = false, budget) {
|
|
|
1646
1936
|
}
|
|
1647
1937
|
function replayWithin(definition, events, options, maxWork) {
|
|
1648
1938
|
const budget = maxWork === void 0 ? void 0 : { used: 0, max: maxWork };
|
|
1649
|
-
const { engine, apply, finish } = build(
|
|
1939
|
+
const { engine, apply, finish } = build(
|
|
1940
|
+
definition,
|
|
1941
|
+
{ ...options, initialValues: void 0, linkParams: void 0, clock: () => void 0 },
|
|
1942
|
+
true,
|
|
1943
|
+
budget
|
|
1944
|
+
);
|
|
1650
1945
|
try {
|
|
1651
1946
|
for (const event of events) apply(event);
|
|
1652
1947
|
finish();
|
|
@@ -1718,6 +2013,31 @@ function parseForm(input) {
|
|
|
1718
2013
|
const optionalBoolean = (v, path) => {
|
|
1719
2014
|
if (v !== void 0 && typeof v !== "boolean") issue(path, "Must be true or false.");
|
|
1720
2015
|
};
|
|
2016
|
+
const optionDetails = (o, at) => {
|
|
2017
|
+
if (o.description !== void 0 && (typeof o.description !== "string" || o.description.length > OPTION_LIMITS.description)) {
|
|
2018
|
+
issue(`${at}.description`, `Must be text of at most ${OPTION_LIMITS.description} characters.`);
|
|
2019
|
+
}
|
|
2020
|
+
if (o.image !== void 0 && !isHttpsUrl(o.image)) {
|
|
2021
|
+
issue(`${at}.image`, `Must be an https:// link of at most ${OPTION_LIMITS.image} characters.`);
|
|
2022
|
+
}
|
|
2023
|
+
if (o.attributes === void 0) return;
|
|
2024
|
+
const attributes = o.attributes;
|
|
2025
|
+
if (!isObject2(attributes) || Object.keys(attributes).length > OPTION_LIMITS.attributes) {
|
|
2026
|
+
issue(`${at}.attributes`, `Must be an object of at most ${OPTION_LIMITS.attributes} attributes.`);
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
const fits = (x) => typeof x === "string" && x.length <= OPTION_LIMITS.attributeValue;
|
|
2030
|
+
for (const [name2, v] of Object.entries(attributes)) {
|
|
2031
|
+
if (!name2.trim() || name2.length > OPTION_LIMITS.attributeName || RESERVED.has(name2)) {
|
|
2032
|
+
issue(`${at}.attributes`, `${shown(name2)} isn't a usable attribute name (at most ${OPTION_LIMITS.attributeName} characters).`);
|
|
2033
|
+
} else if (!fits(v) && !(Array.isArray(v) && v.length <= OPTION_LIMITS.attributeItems && v.every(fits))) {
|
|
2034
|
+
issue(
|
|
2035
|
+
`${at}.attributes.${name2}`,
|
|
2036
|
+
`Must be text, or a list of at most ${OPTION_LIMITS.attributeItems} texts, each at most ${OPTION_LIMITS.attributeValue} characters.`
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
1721
2041
|
const name = (v, path, what) => {
|
|
1722
2042
|
if (typeof v !== "string" || !v) {
|
|
1723
2043
|
issue(path, `${what} must be a non-empty string.`);
|
|
@@ -1890,7 +2210,31 @@ function parseForm(input) {
|
|
|
1890
2210
|
} else if (typeof block.type === "string" && !CHOICE_TYPES.has(block.type)) {
|
|
1891
2211
|
warnings.push({ path: `${path}.props.optionsFrom`, message: "Only single choice, multiple choice and dropdown questions take options from the app." });
|
|
1892
2212
|
}
|
|
2213
|
+
const filter = isObject2(from) ? from.filter : void 0;
|
|
2214
|
+
if (filter !== void 0) {
|
|
2215
|
+
const at = `${path}.props.optionsFrom.filter`;
|
|
2216
|
+
if (!isObject2(filter) || typeof filter.attribute !== "string" || !filter.attribute || filter.attribute.length > OPTION_LIMITS.attributeName || typeof filter.field !== "string" || !filter.field) {
|
|
2217
|
+
issue(at, `Must be { attribute, field }: an attribute name (at most ${OPTION_LIMITS.attributeName} characters) and an earlier question's key.`);
|
|
2218
|
+
} else if (!keys.has(filter.field)) {
|
|
2219
|
+
issue(`${at}.field`, `"${filter.field}" must be the key of an earlier question.`);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
1893
2222
|
}
|
|
2223
|
+
const prefill = isObject2(block.props) ? block.props.prefill : void 0;
|
|
2224
|
+
if (prefill !== void 0) {
|
|
2225
|
+
if (!isObject2(prefill) || !isPrefillParam(prefill.param)) {
|
|
2226
|
+
issue(
|
|
2227
|
+
`${path}.props.prefill`,
|
|
2228
|
+
`Must be { param, hideWhenPrefilled? }: a link parameter of letters, digits, "_", "-" or "." (at most ${MAX_PREFILL_PARAM_LENGTH} characters).`
|
|
2229
|
+
);
|
|
2230
|
+
} else {
|
|
2231
|
+
optionalBoolean(prefill.hideWhenPrefilled, `${path}.props.prefill.hideWhenPrefilled`);
|
|
2232
|
+
if (typeof block.type === "string" && !CHOICE_TYPES.has(block.type)) {
|
|
2233
|
+
warnings.push({ path: `${path}.props.prefill`, message: "Only single choice, multiple choice and dropdown questions are filled in from a link." });
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
if (isObject2(block.props)) optionalBoolean(block.props.searchable, `${path}.props.searchable`);
|
|
1894
2238
|
if (!isObject2(block.props) || !isJson(block.props)) issue(`${path}.props`, "Must be a JSON object.");
|
|
1895
2239
|
else if (block.props.options !== void 0) {
|
|
1896
2240
|
const options = block.props.options;
|
|
@@ -1899,7 +2243,10 @@ function parseForm(input) {
|
|
|
1899
2243
|
options.forEach((o, j) => {
|
|
1900
2244
|
const at = `${path}.props.options[${j}]`;
|
|
1901
2245
|
if (!isObject2(o) || typeof o.value !== "string") issue(at, "Each option needs a string value.");
|
|
1902
|
-
else
|
|
2246
|
+
else {
|
|
2247
|
+
optionalText(o.label, `${at}.label`);
|
|
2248
|
+
optionDetails(o, at);
|
|
2249
|
+
}
|
|
1903
2250
|
});
|
|
1904
2251
|
if (from === void 0) {
|
|
1905
2252
|
choices.set(
|
|
@@ -2192,6 +2539,158 @@ function parseForm(input) {
|
|
|
2192
2539
|
return issues.length ? fail() : { ok: true, form: input, warnings };
|
|
2193
2540
|
}
|
|
2194
2541
|
|
|
2542
|
+
// src/edit-link.ts
|
|
2543
|
+
var EDIT_LINK_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
2544
|
+
var MIN_EDIT_SECRET_BYTES = 32;
|
|
2545
|
+
var EDIT_LINK_PARAM = "edit";
|
|
2546
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2547
|
+
var NONCE = /^[A-Za-z0-9_-]{16,64}$/;
|
|
2548
|
+
var EXPIRES = /^[1-9][0-9]{0,11}$/;
|
|
2549
|
+
var SIG = /^[A-Za-z0-9_-]{43}$/;
|
|
2550
|
+
var DOMAIN = "edit-link:v2:";
|
|
2551
|
+
var MAX_TOKEN_LENGTH = 300;
|
|
2552
|
+
var encoder = new TextEncoder();
|
|
2553
|
+
function toBase64Url(bytes) {
|
|
2554
|
+
let binary = "";
|
|
2555
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
2556
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2557
|
+
}
|
|
2558
|
+
function fromBase64Url(text) {
|
|
2559
|
+
const binary = atob(text.replace(/-/g, "+").replace(/_/g, "/"));
|
|
2560
|
+
const bytes = new Uint8Array(binary.length);
|
|
2561
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
2562
|
+
return bytes;
|
|
2563
|
+
}
|
|
2564
|
+
function isUsableEditSecret(secret) {
|
|
2565
|
+
return typeof secret === "string" && secret.trim().length > 0 && encoder.encode(secret).length >= MIN_EDIT_SECRET_BYTES;
|
|
2566
|
+
}
|
|
2567
|
+
function hmacKey(secret, usage) {
|
|
2568
|
+
return crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, [usage]);
|
|
2569
|
+
}
|
|
2570
|
+
var payload = (id, nonce, expiresAt) => encoder.encode(`${DOMAIN}${id}.${nonce}.${expiresAt}`);
|
|
2571
|
+
async function signEditToken(link, secret, options = {}) {
|
|
2572
|
+
if (!isUsableEditSecret(secret) || !UUID.test(link.id) || !NONCE.test(link.nonce)) return null;
|
|
2573
|
+
const ttl = options.ttlSeconds ?? EDIT_LINK_TTL_SECONDS;
|
|
2574
|
+
if (!Number.isFinite(ttl) || ttl <= 0) return null;
|
|
2575
|
+
const id = link.id.toLowerCase();
|
|
2576
|
+
const expiresAt = Math.floor((options.now ?? Date.now()) / 1e3) + Math.floor(ttl);
|
|
2577
|
+
const sig = await crypto.subtle.sign("HMAC", await hmacKey(secret, "sign"), payload(id, link.nonce, expiresAt));
|
|
2578
|
+
return `${id}.${link.nonce}.${expiresAt}.${toBase64Url(new Uint8Array(sig))}`;
|
|
2579
|
+
}
|
|
2580
|
+
async function verifyEditToken(token, secret, options = {}) {
|
|
2581
|
+
if (!isUsableEditSecret(secret) || typeof token !== "string" || token.length > MAX_TOKEN_LENGTH) return null;
|
|
2582
|
+
const parts = token.split(".");
|
|
2583
|
+
if (parts.length !== 4) return null;
|
|
2584
|
+
const [id, nonce, expires, sig] = parts;
|
|
2585
|
+
if (!UUID.test(id) || id !== id.toLowerCase() || !NONCE.test(nonce) || !EXPIRES.test(expires)) return null;
|
|
2586
|
+
if (!SIG.test(sig)) return null;
|
|
2587
|
+
const expiresAt = Number(expires);
|
|
2588
|
+
const now = Math.floor((options.now ?? Date.now()) / 1e3);
|
|
2589
|
+
const ttl = options.ttlSeconds ?? EDIT_LINK_TTL_SECONDS;
|
|
2590
|
+
if (expiresAt <= now || expiresAt > now + ttl) return null;
|
|
2591
|
+
let bytes;
|
|
2592
|
+
try {
|
|
2593
|
+
bytes = fromBase64Url(sig);
|
|
2594
|
+
} catch {
|
|
2595
|
+
return null;
|
|
2596
|
+
}
|
|
2597
|
+
const ok = await crypto.subtle.verify("HMAC", await hmacKey(secret, "verify"), bytes, payload(id, nonce, expiresAt));
|
|
2598
|
+
return ok ? { id, nonce, expiresAt } : null;
|
|
2599
|
+
}
|
|
2600
|
+
function withEditToken(formUrl, token, param = EDIT_LINK_PARAM) {
|
|
2601
|
+
return `${formUrl}${formUrl.includes("?") ? "&" : "?"}${param}=${encodeURIComponent(token)}`;
|
|
2602
|
+
}
|
|
2603
|
+
async function editLinkUrl(formUrl, link, secret, options = {}) {
|
|
2604
|
+
const token = await signEditToken(link, secret, options);
|
|
2605
|
+
return token ? withEditToken(formUrl, token, options.param) : null;
|
|
2606
|
+
}
|
|
2607
|
+
function createEditSecretReader(options) {
|
|
2608
|
+
const { variable } = options;
|
|
2609
|
+
let warned = false;
|
|
2610
|
+
const read = (env, log) => {
|
|
2611
|
+
const secret = env[variable];
|
|
2612
|
+
if (isUsableEditSecret(secret)) return secret;
|
|
2613
|
+
if (!warned) {
|
|
2614
|
+
warned = true;
|
|
2615
|
+
(log ?? options.log ?? ((m) => console.warn(m)))(
|
|
2616
|
+
`${variable} is ${secret ? `shorter than ${MIN_EDIT_SECRET_BYTES} bytes` : "not set"}: edit links are off.`
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
return null;
|
|
2620
|
+
};
|
|
2621
|
+
return Object.assign(read, {
|
|
2622
|
+
reset: () => {
|
|
2623
|
+
warned = false;
|
|
2624
|
+
}
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
function isEditLinkRequest(url, options = {}) {
|
|
2628
|
+
const isFormPage = options.isFormPage ?? (() => true);
|
|
2629
|
+
return isFormPage(url) && url.searchParams.has(options.param ?? EDIT_LINK_PARAM);
|
|
2630
|
+
}
|
|
2631
|
+
function withEditLinkHeaders(request, response, options = {}) {
|
|
2632
|
+
let url;
|
|
2633
|
+
try {
|
|
2634
|
+
url = new URL(request.url);
|
|
2635
|
+
} catch {
|
|
2636
|
+
return response;
|
|
2637
|
+
}
|
|
2638
|
+
if (!isEditLinkRequest(url, options)) return response;
|
|
2639
|
+
const headers = new Headers(response.headers);
|
|
2640
|
+
headers.set("referrer-policy", "no-referrer");
|
|
2641
|
+
headers.set("cache-control", "private, no-store");
|
|
2642
|
+
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
// src/rate-limit.ts
|
|
2646
|
+
var DEFAULT_TRUSTED_IP_HEADERS = ["x-vercel-forwarded-for", "x-forwarded-for", "x-real-ip"];
|
|
2647
|
+
var RATE_LIMIT_DEFAULTS = {
|
|
2648
|
+
submit: { limit: 60, windowSeconds: 600 },
|
|
2649
|
+
progress: { limit: 900, windowSeconds: 600 },
|
|
2650
|
+
upload: { limit: 120, windowSeconds: 600 }
|
|
2651
|
+
};
|
|
2652
|
+
function clientIp(headers, trusted = DEFAULT_TRUSTED_IP_HEADERS) {
|
|
2653
|
+
for (const name of trusted) {
|
|
2654
|
+
const first = headers.get(name)?.split(",")[0]?.trim();
|
|
2655
|
+
if (first) return first;
|
|
2656
|
+
}
|
|
2657
|
+
return "unknown";
|
|
2658
|
+
}
|
|
2659
|
+
function addressPrefix(ip) {
|
|
2660
|
+
const bare = ip.replace(/^\[|\](:\d+)?$/g, "").split("%")[0].toLowerCase();
|
|
2661
|
+
if (!bare.includes(":")) return bare;
|
|
2662
|
+
const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(bare);
|
|
2663
|
+
if (mapped) return mapped[1];
|
|
2664
|
+
const [head = "", tail = ""] = bare.split("::");
|
|
2665
|
+
const left = head ? head.split(":") : [];
|
|
2666
|
+
const right = bare.includes("::") && tail ? tail.split(":") : [];
|
|
2667
|
+
const groups = bare.includes("::") ? [...left, ...Array(Math.max(0, 8 - left.length - right.length)).fill("0"), ...right] : left;
|
|
2668
|
+
return `${groups.slice(0, 4).map((g) => (g || "0").replace(/^0+(?=.)/, "")).join(":")}::/64`;
|
|
2669
|
+
}
|
|
2670
|
+
async function rateLimitKey(input) {
|
|
2671
|
+
const encoder2 = new TextEncoder();
|
|
2672
|
+
const key = await crypto.subtle.importKey("raw", encoder2.encode(input.secret), { name: "HMAC", hash: "SHA-256" }, false, [
|
|
2673
|
+
"sign"
|
|
2674
|
+
]);
|
|
2675
|
+
const mac = new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder2.encode(addressPrefix(input.ip))));
|
|
2676
|
+
let hex = "";
|
|
2677
|
+
for (const b of mac) hex += b.toString(16).padStart(2, "0");
|
|
2678
|
+
return `${input.kind}:${input.scope}:${hex}`;
|
|
2679
|
+
}
|
|
2680
|
+
async function allow(store, check, options = {}) {
|
|
2681
|
+
let allowed;
|
|
2682
|
+
try {
|
|
2683
|
+
allowed = await store.hit(check.key, check.limit, check.windowSeconds);
|
|
2684
|
+
} catch {
|
|
2685
|
+
allowed = null;
|
|
2686
|
+
}
|
|
2687
|
+
if (allowed !== null) return allowed;
|
|
2688
|
+
(options.log ?? ((m) => console.warn(m)))(
|
|
2689
|
+
options.message ?? "The rate limit could not be checked; letting the request through."
|
|
2690
|
+
);
|
|
2691
|
+
return true;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2195
2694
|
// src/server.ts
|
|
2196
2695
|
var MAX_EVENTS = 1e4;
|
|
2197
2696
|
var MAX_SUBMISSION_VALUES = 2e5;
|
|
@@ -2329,11 +2828,12 @@ function validateSubmission(published, submission, options = {}) {
|
|
|
2329
2828
|
}
|
|
2330
2829
|
} else {
|
|
2331
2830
|
const errors = {};
|
|
2332
|
-
|
|
2831
|
+
const kept = engine.answers();
|
|
2832
|
+
for (const [id, value] of Object.entries(kept)) {
|
|
2333
2833
|
const message = validateField(fields.get(id), value, {
|
|
2334
2834
|
requireAnswers: false,
|
|
2335
2835
|
custom: options.validators?.[id]
|
|
2336
|
-
});
|
|
2836
|
+
}) ?? filteredChoiceError(definition, fields.get(id), value, kept);
|
|
2337
2837
|
if (message) errors[id] = message;
|
|
2338
2838
|
}
|
|
2339
2839
|
if (Object.keys(errors).length) return { ok: false, errors };
|
|
@@ -2375,11 +2875,31 @@ function publicDefinition(definition) {
|
|
|
2375
2875
|
return JSON.parse(JSON.stringify(definition));
|
|
2376
2876
|
}
|
|
2377
2877
|
|
|
2878
|
+
exports.DEFAULT_TRUSTED_IP_HEADERS = DEFAULT_TRUSTED_IP_HEADERS;
|
|
2879
|
+
exports.EDIT_LINK_PARAM = EDIT_LINK_PARAM;
|
|
2880
|
+
exports.EDIT_LINK_TTL_SECONDS = EDIT_LINK_TTL_SECONDS;
|
|
2378
2881
|
exports.MAX_EVENTS = MAX_EVENTS;
|
|
2379
2882
|
exports.MAX_REPLAY_WORK = MAX_REPLAY_WORK;
|
|
2380
2883
|
exports.MAX_SUBMISSION_VALUES = MAX_SUBMISSION_VALUES;
|
|
2884
|
+
exports.MIN_EDIT_SECRET_BYTES = MIN_EDIT_SECRET_BYTES;
|
|
2885
|
+
exports.RATE_LIMIT_DEFAULTS = RATE_LIMIT_DEFAULTS;
|
|
2381
2886
|
exports.RAW_EVENTS_PER_EVENT = RAW_EVENTS_PER_EVENT;
|
|
2887
|
+
exports.addressPrefix = addressPrefix;
|
|
2888
|
+
exports.allow = allow;
|
|
2889
|
+
exports.answerSnapshots = answerSnapshots;
|
|
2890
|
+
exports.answersFromSnapshots = answersFromSnapshots;
|
|
2891
|
+
exports.clientIp = clientIp;
|
|
2892
|
+
exports.createEditSecretReader = createEditSecretReader;
|
|
2893
|
+
exports.editLinkUrl = editLinkUrl;
|
|
2894
|
+
exports.isEditLinkRequest = isEditLinkRequest;
|
|
2895
|
+
exports.isUsableEditSecret = isUsableEditSecret;
|
|
2382
2896
|
exports.publicDefinition = publicDefinition;
|
|
2897
|
+
exports.rateLimitKey = rateLimitKey;
|
|
2898
|
+
exports.signEditToken = signEditToken;
|
|
2383
2899
|
exports.validateSubmission = validateSubmission;
|
|
2900
|
+
exports.verifyEditToken = verifyEditToken;
|
|
2901
|
+
exports.withEditLinkHeaders = withEditLinkHeaders;
|
|
2902
|
+
exports.withEditToken = withEditToken;
|
|
2903
|
+
exports.withHostOptions = withHostOptions;
|
|
2384
2904
|
//# sourceMappingURL=server.cjs.map
|
|
2385
2905
|
//# sourceMappingURL=server.cjs.map
|